【分享副业】30岁,裸辞创业,从卖白酒开始

hi,大家好!我是Chorder,这是我第一次在V2EX发帖。
一直觉得这里是一片净土,但是在这里潜水,倒不太敢发言,因为感觉这里的都是大佬。

以前嘛总是害怕暴露自己的ID,也就是 Chorder 这个单词啦。现在想想完全没必要,其实没啥可怕的,我又不是什么名人。

反正都暴露了,不如先来解释一下ID。Chorder是我自创的单词,Chord嘛是和弦的意思,那Chorder我就称之为“和弦者”。
我如今最喜欢的LN·PARTY的《索拉里斯星》里有句歌词:

1
琴的弦,一波动,便有了存在

这句歌词我觉得是在说弦论。过去我喜欢音乐,也喜欢物理。了解到弦论之后,就更加觉得客观世界充满了美感和规律。
所以我想要静静地做一个感受规律的人,就像音乐中构造和弦那样,因此取名Chorder。

这句歌词是唐映枫写的,这是如今我喜欢的作家之一,我觉得他的歌词(或者叫诗歌)十分具有美感。

对我影响比较大的作家有韩寒、尤瓦尔·赫拉利、大刘、马亲王等等。还有一个我很佩服的人他只写140字的内容,还擅长引用、重复、语调、标点符号,他的微博十分具有逻辑和观赏效果。

所以我也算喜欢文字吧,也喜欢在有安全感的前提下写一些东西,过去我有一个博客,写些不三不四的内容,有段时间我将它下线了,今年又重新上线了,所以你现在主页上一篇文章还是写在2021年末尾,其次就是这篇文章。

网站的网址是 https://chorder.net

过去我是一个很闷的人,但其实也可以说我是闷骚。我有时是故意闷,相比真正社恐的人,他们也许觉得和别人相处的时候,如果不聊点什么调和气氛就会尬。我不是,我不喜欢为了附和气氛说一些话。虽然我擅于此道,不过除非是遇到了让我想说点什么让他们一起开心下的人,否则我宁愿沉默。所以我身边的朋友们都还挺喜欢跟我一起喝酒吹牛逼,人缘不差。

大概是曾经的话实在太多是个话痨,我最终切实感受到语言这种工具其实还是过于匮乏,表达能力有限。
我甚至觉得语言有的时候反而限制了人类的思考,我们的思考都是基于语言符号的集合的,而语言之外的更广阔的世界,常常被忽视了。
这就好比光谱之中只有较少一段是可见光,而更多的频率是不可见光。
所以为什么我们还需要音乐,需要舞蹈,需要各种艺术,需要山水画,需要印象派。
是的,从山水画到印象派,我喜欢的是永远是写意。写意就是无声胜有声,留白,感悟,舞台交给想象力。

V2EX可能有一些认识我的人,因为我有不少小伙伴都在这里玩耍,甚至如今我的一群很要好的朋友,就是通过这里认识的,通过 这个帖子,加了一个很吊的【在南京的程序员们】,这是故事的开始,后来我们又开始一起搞起了摩托,办起了“会所”,不过这都是后话了。
所以我想说,打开圈子,广交朋友,真的挺有趣,很快乐,人间值得。

我人生的最大转变我想是从去年(2023)开始的。主要原因是因为我很主观地想要寻求一种的变化,次要原因是我也刚好在去年离职。主观的原因让我做出离职的决定,而后的生活也正如我所料,也发生了一些事情,让我更加清醒,对生活的热情相比上班时期也更加饱满。

为啥想要做出变化呢,因为我觉得上班既无法实现财富,也无法拥有自由,当然我很早就知道没有绝对的自由,我其实比较认可的是“自律即是自由”,我如果自己表述可能冗长的同时也表达不好,等以后有机会可以勉强谈谈。曾经在知乎上看到一段话,下面的内容就是我想说的。

1
2
3
4
5
6
7
8
9
作者:匿名用户
链接:https://www.zhihu.com/question/276128062/answer/883014360
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

为什么自律即自由,不自律就不自由,自律和自由之间有什么必然关系?自律指的是什么?自由又是什么定义?自律和自由难道不是相反的含义?没有人怀疑过随心所欲想吃就吃难道不是一种自由吗?活在当下所体现的思想是不是和这句话矛盾?
首先自律即自由是康德说的,根据康德二元论的哲学思想,他把世界划分为感性的现象界和理智的本体界,现象是指我们的感觉表象,现象界是指被知性规范化了的现象总体,物自体是指超出经验,不被知性规定的存在,也就是精神。人不仅存在于现象界,也存在于本体界,具有双重性。在现象界中,事物间的因果关系是恒定的,一切都是机械必然的,在感性的世界中,人不可避免地受物质欲望、个人爱好的诱惑而实施行为,此时他的行为的原因是欲望与冲动,是外界的原因,是他律的,是被动的决定的,是自然的必然性,并非由其道德意志决定,因此不存在自由。
在这里,“自律”有了它的对立面“他律”,他律的定义是实施外界因素所决定的行为(外在的必然性决定),那么自律的定义就是根据自己的意志,自觉选择、自主决定,排除感性的欲望与冲动,行为的原因产生于对道德法则的服从,道德法则由自己制定(内在的必然性决定)。因此“想吃就吃、想睡就睡”的行为,看似是“自我选择”,是“自由”,实际上是由于自然的原因,是人在服从于感性的欲望与冲动,服从于自然的法则。
而康德所说的自由指的是,人意识到自己是目的,意识到自身的价值,而且意识到自身的意志是自由的。在道德的领域,人能够超越自然的限制,无条件地遵从理性自身的法则,因而是自由的。同时,人为自己颁布道德法则,并且使自己意识到服从这些法则的责任,通过自己创造的道德准则来约束自己,真正服从于自己的意志,并且只服从于自己的意志,这也便是自律,因此解释了为什么“自律即自由”。

有一段时间我把自由的概念搞的很清楚,自由不是为所欲为,而是自己主观地寻找到了一种状态。我在上班的过程中其实已经足够自由了,从事的是网络安全研究,能够主导一些事情,一定程度上也能决定自己的工作方向和内容,但我知道那不是真正的自由。

自由其实不和财富直接挂钩,但上班如果既获取不到财富,又获取不到自由,那就必须做出改变,结束一望无际的枯燥谋生。

所以我辞职了,在现在这种看起来似乎经济不是太好的环境下裸辞。这其实是我第二次裸辞了,第一次裸辞是在2018年,休息了几个月之后因为朋友的推荐,进入到了新的公司,一干就是五年。

辞职是如何规划的?辞职之后又该如何谋生呢?

这里我把我的经验也分享分享。首先我裸辞两次,但是我个人一直有着非常保守的储蓄和理财习惯。可能是小时候比较穷,习惯了开销不大的生活,所以上班这些年能存得下来钱,我也早就停止了风险过大的投资,比如比特币、股票。以至于虽然裸辞,但不至于很快就要到需要找工作的程度。每次裸辞我也都是做了最坏的打算去做准备的。比如我算了下自己目前的开支,一年至少10万左右,那我就按照这个预算来准备裸辞后的生活,按照规律去消费,避免不必要的大额支出,把每年的大额支出,比如换手机、买保险、礼物、应酬、摩托车等等这些都按月拉个表统计好,同时留有一些冗余来对抗未知风险,因为没有风险大的投资所以也几乎没有什么大规模损失资金的可能。在日常的生活中避免随机的大额消费、冲动消费,这样看着自己未来3到5年甚至5到10年的一个明确的资金预算,心里无比清晰,平时对待无止境的消费主义洗脑也不为所动,非常理智。

不知道你们有没有看过罗振宇2024年的跨年演讲,他把他到六七十岁的每一天要做的工作都安排好了,这一种对于人生的掌控力让我非常佩服,我做的预算就是想要对自己的财务上有一种这样的规律的掌控。

关于辞职之后的谋生,目前主要有三项计划来源。

第一项是延续老本行,编程、网络安全这个领域还是可以继续接到不少私活和小项目的,也会帮开公司的朋友做一些支撑。

其次就是各种撸羊毛,低收高卖,同时也在不断探索其他的事情,比如自媒体,我也尝试做过头条号、B站等等,也还在持续探索中。

第三项就是今天要说的,也是自己比较看好的,会用自己余生来长期做的事情,就是白酒。

为什么选择白酒?其实家里父母目前的就是靠卖酒为生计的,卖酒卖了十几年。另外自己本身也很喜欢喝酒,我是安徽人,也很喜欢喝老家那边产的酒。

白酒通过良好的保存手段可以存续很久,甚至老酒会更受欢迎,屯这种商品,可以穿越经济周期,不会过期。

虽然现在喝白酒的人少了,但是目前自己主要在卖的这款白酒,家人和酒厂有着紧密的联系,从生产、贮存,一直到销售终端,自己都亲眼所见,同时家人和朋友喝了很多年,了解这款酒的品质,虽然价格比较亲民,但绝不是低端酒、劣质酒。

可能很多人不了解白酒,这里简单来普及普及。其实白酒很简单,建国后,明确了很多白酒的标准,规定了一些香型和酿造工艺(参考GB/T 15109-2021 ),其中常见的就是酱香型,浓香型,清香型,酱香不用多说大家都知道,茅台是其中的代表。浓香的代表是五粮液,清香的代表汾酒等等,这些都是比较有特色的酒,尤其是酱香、浓香、清香三种放在一起分别品,就能感受到其中的味道的不同,这种靠味觉和嗅觉来体会的事物更多地需要自己去体会。茅台不一定非要喝飞天,便宜的茅台王子也不过一百多一瓶,五粮液入门款也是同样,而53度玻汾只要五十多一瓶,总共花上不到三百元,就可以把这三种香型的酒对比一遍,从此以后闻着味道就知道是什么香型,也就算是入了门啦。入门之后,后面还有凤香、米香、豉香、董香、芝麻香、老白干香等着充盈你的味蕾。而我做的这款属于兼香,这是一种浓酱兼香型白酒(GB/T 10781.8-2021),具有浓香和酱香共同的特点,是来自皖西大别山的风味。兼香这个品质让我想起墨子的兼爱,曾经墨家的理想是建立和平兼爱的世界,和平对于目前的世界来说真的很需要。跑题了,这些以后喝了酒再扯。

白酒、威士忌、伏特加,这都属于烈酒,也就是酒精度数高的酒,通常都是三四十度以上。凡是烈酒,基本都是蒸馏酒。因为发酵酒是酒精混合在其他液体里的,而蒸馏酒是酒水一起蒸馏再冷凝提取,所以能把酒精浓度变高。蒸馏酒蒸馏的难点是要控制“杂醇”的参与,古法酿酒需要“掐头去尾”,就是最原始的做法。酒厂一般高薪聘请老师傅来从事这些工序,现代的蒸馏更不仅仅是凭借经验,而是要结合专业的仪器和科学的方法来控制发酵和蒸馏。酒精我们都知道,就是乙醇,酿酒就是发酵获得可食用的乙醇。如果酒水当中混入杂醇比较多,酒的风味就会变。但是有些杂醇会让酒的口感变差,甚至产生危害,比如喝完头疼、醒酒需要更长的时间、对身体的刺激性更大等等。“酒是粮食精”,但有些酒是“妖精”。。。

关于酒的篇幅,简单就说这些,同时因为打算余生一直卖酒,我也还在不断地学习更多关于白酒的知识,并且更多地深入实地在考察。以后有机会再和大家分享更多。

我决定在这件事情上All In自己的下半生,来慢慢投入、慢慢打磨自己的这个爱好,并在这个爱好中追求自己渴望的自律和自由,寻觅中国大地上的好酒,寻找经销渠道,纳入自己的白酒销售版图当中。先从小规模开始做起,只卖自己亲自尝过、好喝、优质、价格实惠的白酒,就从自己目前正在做的这款酒开始。相比日新月异的世界变化,我希望以白酒这个爱好,来追求一点不变的东西,只要我还活着,就会一直屯酒,一直喝酒,一直卖酒。

白酒大家都知道,利润很高,但是挣钱的多少,取决于酒厂的良心。纯粮食酿造的酒,粮食成本可能就要划到30~50,有些品质好的酒,出厂的价格就已经很高,当然低端的,出厂价很低的酒也有,但是白酒的价格普遍普遍卖到少则几十,多则上百,甚至上千,甚至上万。上万这个我不说大家也知道是什么酒,其实就没意思了,纯粹是金融游戏,我很不喜欢这种,我只想搜罗各种好喝的有品质的白酒,慢慢屯,慢慢卖,不做金融游戏,屯酒卖酒,只要能勉强跟上通货膨胀的速度,我就知足了。V站的帖子删不掉,这个帖子永远在这里,我庄严承诺,我的酒永远不卖天价,天价的酒我也不卖,因为我希望真正爱喝酒的人有酒喝。三十岁了,人生后半场能挣多少钱,发多大的财也多多少少心里有点逼数,不求着暴富,只求安安稳稳做事业谋生,结合爱好做点让自己开心的事情。
我们生来就是孤独,酒可以给我们安慰,可以给世界和平,可以给灵魂减速。开心不开心,都可以小酌一口,聊以慰藉(但反对酗酒!反对酗酒!反对酗酒!尤其不建议饮酒过量!尽兴是最好的!)。不同白酒厂商的销售策略不同,对于经销商的资质要求也不同。所以有些白酒还不是我想卖就能卖的了的,比如汾酒我就很想卖,但如果我想拿到汾酒厂商的经销资质,我得满足一定的条件。所以这个生意要想做丰富,就要卖很多很多,拥有自己的经销渠道网络,有一定的销售能力,投入少则百万,多则千万。这也是我来这里发帖的目的,我要找到更多志同道合的人一起卖酒,帮我分销。如果你也对白酒有兴趣,对喝酒有兴趣,对这份“永不过期”的永恒事业有兴趣,欢迎一起交流,一起卖酒,交个朋友,用下半生的时间一起慢慢品酒,慢慢卖酒,慢慢积累,用余生来做这件漫长的事。

如果你对卖酒有兴趣,想业余卖酒,做个副业的话,请添加我的微信。如果不想卖酒纯粹想加个微信吹吹牛逼也行,相识即是缘分。

最后就是发一下自己和这个酒的故事啦,发几张图吧。目前在做的这个酒的名字叫临水玉泉洞藏酒,除了这个酒还想做汾酒、西凤酒,当然茅台(普通的,寻常百姓能喝的款)也想卖,但是对酱香目前不是很懂,还需要再学习学习。

2021年,新冠疫情还是在继续。全世界都变得更加不一样了。

一些曾经的所谓规则,不过是人类缔造的笑话,一切都显得很荒诞。

我仍然在继续苟着,在混沌的世间缓缓行走着。

这是平静,但是也有收获的一年。

我的生活持续平淡,平淡并非坏事。虽然但是。

拿了摩托的驾照,在今年大多数的日子里,非常渴望在路上的感觉。

应该很快会购买人生的第一辆坐骑,希望它会是一匹良驹,一起纵横千里。

如果以后有机会,再专门写写摩托车,写写骑行在路上的故事。

今年开始逐渐减少写代码。倘若问我,什么时候开始放弃用代码影响世界的想法,回答是从2021年开始的。

一些更加重要的事物开始凸显,生而为人,需要学会研究人类,而不仅仅是机器。

今年更加意识到自己并不是一个聪明的人,甚至也许连体力都不如常人。

更加认清现实之后多多少少会先自卑,自卑之后会脸皮厚,脸皮一厚就能渐入佳境。

但给自己画的底线是,无论如何不能胡扯,可以不一定要为这个世界创造美好,但求尽量不给这个世界添堵。

不懂的事情不要胡说,不会的东西不要乱来,戒骄戒躁,真诚地对待生命中每一位过客,留意身边的善良和美好。

这个博客还是会继续开着,翻看以前的文章,因为认知的原因,也许有过一些错误的结论。也不删了,看客自行体会,我在成长着。

2021的最后我成为一名奶爸,在育娃的道路上开始了新的探索。

希望等孩子长大了,他也可以看到这些文字,也让他从字里行间,了解爸爸昔日平凡的生活。

最后也写一写对2022年的期待:

希望新的一年自己可以更加适应社会,更加和睦友善的与人相处。

可以为身边的人和环境创造些价值,能够不辜负那些帮助、带领和指引过我的人。

希望和梦想家们一起做梦,和冒险家们一起冒险。

一个进程的意义,或在于,它有它要执行的任务。

有的为了调度,有的为了计算,有的为了IO,有的为了传输。

一个进程的意义,或在于,它要为后续的进程创造条件。

在时空里埋下线索,为后续的crash保留好进程上下文。Debug的过程或许痛苦,但是螺旋式上升(或者下沉)必须经历。

一个进程的意义,或在于,它要代表一颗星星,静静地挂在内存的夜空。

动态链接与静态链接的区别

链接通俗来说就是C语言程序在编译过程中,编译器将所有的资源“拼接”到二进制文件中,从而形成最终的可执行文件的过程。

链接库文件通常就是一些专门存放一些通用函数的文件,动态和静态库的区别就在于,动态库是随着程序的发布一起发布的,作为独立的模块发布。

程序需要用到其中的函数时,动态地加载这些模块,从而调用其中的功能。

而静态库则是程序在编译的时候,从库中将所需要用到的函数、类等提取出来,复制到自身的可执行文件中。

在Linux中,静态链接库文件的后缀名通常是.a;在Windows系统中,静态链接库文件的后缀名为.lib

在Linux中,动态链接库文件的后缀名通常是.so;在Windows系统中,动态链接库文件的后缀名为.dll

Linux动态链接库和静态链接库

Linux动态链接

一个简单的Linux动态链接创建实例:

源文件:func1.c和func2.c编译生成testlib.so,testlib.h中声明了testlib.so的函数,main.c调用testlib.so中的函数。

func1.c:

1
2
3
4
5
6
#include "testlib.h"

int add(int a,int b){
return a + b;
}

func2.c:

1
2
3
4
5
#include "testlib.h"

int sub(int a,int b){
return a - b;
}

testlib.h:

1
2
3
4
5
6
7
#ifndef __TEST_H_
#define __TEST_H_

int add(int a,int b);
int sub(int a,int b);

#endif

编译:

编译生成testlib.so:

1
gcc -fpic -shared func1.c func2.c -o testlib.so

Linux动态链接的隐式调用(静态调用)

Linux动态链接库的隐式调用,就是将生成的so文件直接放到系统的Lib路径中,可执行程序在执行调用之前系统会自动将库文件加载到内存中,而不需要手动去加载。

main.c:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include "testlib.h"

int main(){
int m, n;
printf("Input two numbers: ");
scanf("%d %d", &m, &n);
printf("%d+%d=%d\n", m, n, add(m, n));
printf("%d-%d=%d\n", m, n, sub(m, n));
return 0;
}

编译生成main:

1
gcc main.c testlib.so -o main

生成之后如果直接执行main会报错:

1
./main: error while loading shared libraries: testlib.so: cannot open shared object file: No such file or directory

因为此时系统不知道应该从哪里加载testlib.so:

1
2
3
4
5
6
chorder@debian:~/testlib$ldd main
linux-vdso.so.1 (0x00007fff979c7000)
testlib.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa0e5d8c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa0e5f70000)

只需要将testlib.so拷贝到/usr/lib/即可:

1
2
3
4
5
6
chorder@debian:~/testlib$sudo cp testlib.so /usr/lib/
chorder@debian:~/testlib$./main
Input two numbers: 3
2
3+2=5
3-2=1

Linux动态链接的显式调用(动态调用)

main_d.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <dlfcn.h>
#include <stddef.h>
#include <stdio.h>

int main(){
int m,n;
//打开库文件,需要提供testlib.so的绝对路径,否则会无法加载
void* handler = dlopen("./testlib.so",RTLD_LAZY);
if(dlerror() != NULL){
printf("%s",dlerror());
}

//获取库文件中的 add() 函数
int(*add)(int,int)=dlsym(handler,"add");
if(dlerror()!=NULL){
printf("%s",dlerror());
}

//获取库文件中的 sub() 函数
int(*sub)(int,int)=dlsym(handler,"sub");
if(dlerror()!=NULL){
printf("%s",dlerror());
}

//使用库文件中的函数实现相关功能
printf("Input two numbers: ");
scanf("%d %d", &m, &n);
printf("%d+%d=%d\n", m, n, add(m, n));
printf("%d-%d=%d\n", m, n, sub(m, n));
//关闭库文件
dlclose(handler);
return 0;
}

编译生成main_d:

1
gcc main_d.c -ldl -o main_d

可以直接运行main_d:

1
2
3
4
5
chorder@debian:~/testlib$gcc main_d.c -ldl -o main_d
chorder@debian:~/testlib$./main_d
Input two numbers: 3 2
3+2=5
3-2=1

Linux静态链接

静态链接相对来说比较简单,只需要在编译的时候加上相应的参数即可将函数编译到可执行文件中。

一个简单的Linux下的静态链接库的例子:

add.c和sub.c编译生成libtest.a,libtest.h中声明了相应的函数,main.c调用libtest.a中的函数。

add.c

1
2
3
int add(int a, int b){
return a + b;
}

sub.c

1
2
3
int sub(int a, int b){
return a - b;
}

libtest.h

1
2
3
4
5
6
7
#ifndef __TEST_H_
#define __TEST_H_

int add(int, int);
int sub(int, int);

#endif

main.c

1
2
3
4
5
6
7
#include <stdio.h>
#include "libtest.h"

int main(){
printf("%d, %d\n", add(3, 2), sub(3, 2));
return 0;
}

编译:

编译add.c和sub.c,生成目标文件 add.o sub.o

1
gcc -c add.c sub.c

归档目标文件,生成静态链接库 libtest.a

使用 ar 命令将目标文件压缩到一起,并且对其进行编号和索引,以便于查找和检索

1
ar rcv libtest.a add.o sub.o

编译main.c:

1
gcc main.c -L. -ltest -o main

Windows动态链接库和静态链接库

Windows动态链接

Windows的动态链接库一般是DLL,这里创建一个DLL工程,func1.c和func2.c组合编译成testlib_win.dll,并通过两种方法分别调用testlib_win.dll中的函数。

第一种称之为显式链接,只需提供DLL文件和知晓函数名即可;第二种称之为隐式链接,需要提供lib,头文件和dll。

首先在VS2019中创建DLL项目:

func1.c:

1
2
3
4
5
6
#include "testlib_win.h"

int add(int a, int b) {
return a + b;
}

func2.c:

1
2
3
4
5
#include "testlib_win.h"

int sub(int a, int b) {
return a - b;
}

testlib_win.h

1
2
3
4
5
6
7
8
#ifndef __TEST_H_
#define __TEST_H_

int add(int a, int b);
int sub(int a, int b);

#endif

直接编译会报错,需要修改一下项目属性,不使用预编译头:

点击生成解决方案,即可编译出DLL:

此时生成的文件中只有dll,并没有lib文件,如果想要生成lib文件,则需要对testlib_win.h文件进行修改:

testlib_win.h

1
2
3
4
5
6
7
#ifndef __TEST_H_
#define __TEST_H_

extern __declspec(dllexport) int add(int a, int b);
extern __declspec(dllexport) int sub(int a, int b);

#endif

此时编译出的文件中,就包含了testlib_win.lib:

Windows DLL显式调用

创建工程,包含testlib_win.h头文件,在其中加载DLL并调用add和sub函数:

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <Windows.h>
#include <stdio.h>

#include "testlib_win.h"

typedef int(*FUNC)(int, int);

int main() {

HINSTANCE hInstLibrary = LoadLibrary(L"testlib_win.dll");
FUNC add,sub;

if (hInstLibrary == NULL) {
printf("无法加载DLL");
}
else {
printf("DLL加载成功 %p\n", hInstLibrary);
}

add = (FUNC)GetProcAddress(hInstLibrary, "add");
sub = (FUNC)GetProcAddress(hInstLibrary, "sub");

if (add == NULL || sub == NULL) {
FreeLibrary(hInstLibrary);
printf("获取函数地址失败\n");
}
else {
printf("函数add地址获取成功 %p!\n", add);
printf("函数sub地址获取成功 %p!\n", sub);
printf("Call add: %d\n", add(1,2) );
printf("Call sub: %d\n", sub(3,2));
}

FreeLibrary(hInstLibrary);
return 0;
}

将testlib_win.h加入到源码目录,将testlib_win.dll加入到编译输出的文件所在目录:

运行即可:

Windows DLL隐式调用

由于不常用,以后再补充。

Windows静态链接

由于不常用,以后再补充。

(待续)

参考资料

GCC使用静态链接库和动态链接库
C/C++动态链接库的显式调用(动态调用)
linux静态链接库
什么是 DLL
windows LoadLibrary使用示例
Visual Studio 2013中.dll文件的显式调用方法
Windows DLL调用实例
VS2017生成一个简单的DLL文件 和 LIB文件——C语言

今年27了,终于明白副业是个伪命题。

从我大三实习那年起,在维持主业的同时,我就一直热衷于探索副业。

最开始在淘宝上卖乐器,在58同城倒腾二手物品,后来尝试做CPA、淘宝客、建站做Adsense,以及出教程、搞培训,炒股、炒币、买基金…

除了炒股以外,别的基本上没亏过钱。但也没有因此赚过什么钱。

过去的我,太急于开辟一条新的赛道了,以至于在主业(指网络安全)的道路上还没有做出什么成绩,就想着赶紧脱离这一条一眼忘不到头的技术路。

幻想着能够有朝一日把副业从小做大,慢慢扶正,让主业真正成为一个爱好。

然而幻想终究是一种幻想,相信许多人都会有这种幻想。

这种幻想太幼稚了。如今我终于认识到,以大部分人的天资,能够在主业的道路上有所建树,已经是莫大的成功了。

更何况我们这些搞安全的技术小子,如今能凭借这一身牢底坐穿的手艺有口饭吃,绝对要感谢老天爷。

要好好珍惜主业的机会,踏踏实实继续突破。

以后不谈副业了。

疫情

发展

转折

故事

再启程

Windows系统下C语言中使用自定义包的方法:

首先明确GOPATH的路径,执行go env | findstr GOPATH

1
2
3
4
C:\Users\CHORDER>go env | findstr GOPATH
set GOPATH=C:\Users\CHORDER\go

C:\Users\CHORDER>

接着在GOPATH目录中创建相应的自定义包,这里定义一个example包,并创建两个源码文件src1.gosrc2.go,源码内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* C:\Users\CHORDER\go\src\chorder.net\example\src1.go */

package example

import "fmt"

func init(){
fmt.Printf("\nchorder.net/example/src1.go init() has been called.")
}

func Src1(){
fmt.Printf("\nchorder.net/example/src1.go Src1() has been called.")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/*C:\Users\CHORDER\go\src\chorder.net\example\src2.go*/

package example

import "fmt"

func init(){
fmt.Printf("\nchorder.net/example/src2.go init() has been called.")
}

func Src2(){
fmt.Printf("\nchorder.net/example/src2.go Src1() has been called.")
}

编写代码调用这两个包中的方法:

packge_test.go

1
2
3
4
5
6
7
8
package main

import "chorder.net/example"

func main(){
example.Src1()
example.Src2()
}

执行结果:

1
2
3
4
5
6
>go run packge_test.go

chorder.net/example/src1.go init() has been called.
chorder.net/example/src2.go init() has been called.
chorder.net/example/src1.go Src1() has been called.
chorder.net/example/src2.go Src1() has been called.

1 概述

在典型的IPv4网络下,由于NAT技术的广泛使用,端口映射是一种刚需。(下文所涉及“端口转发”、“端口映射”均指同一种技术。)

经典的端口映射方法例如iptables端口转发、SSH端口转发、webshell流量转发(regeorg)、socks代理等技术,是比较常见的用于建立内网通道的技术。本文分享一种利用内网网关设备开放的upnp协议进行端口映射的方法。

关于upnp协议的解释,参考百度百科-UPNP
upnp协议的使用场景很多,端口转发是upnp其中一个功能,但是想要使用该功能,需要网关设备的支持。

2 场景

使用upnp进行端口转发的场景,以下图网络拓扑为例:

内网服务器Server:内网地址192.168.1.2,开放端口1234
网关设备Gateway:内网地址192.168.1.1,公网地址x.x.x.x

常规情况下,如果需要通过公网地址x.x.x.x访问192.168.1.2中的开放端口1234,则可以在Gateway中配置端口转发,但此时至少要有192.168.1.1的(部分)权限。

如果Gateway具备unpn功能,并且启用了unpn服务,则可以在Gateway外部通过upnp指令,打开Gateway上的转发通道,从而完成端口转发。

Gateway开启upnp示例(一般位于路由等设备的配置界面中):

3.利用

metasploit

Metasplioit中有upnp相关的插件,但是不太好用,姿势问题?
插件是:

  • auxiliary/scanner/upnp/ssdp_msearch
  • auxiliary/admin/upnp/soap_portmapping

可以自行探索之

miranda-upnp

miranda-upnp是之前在Github上找到的一个工具,基于python2编写,可以用于内网upnp端口转发通道的建立。

miranda-upnp(github)

upnp协议

也可以根据upnp协议自行实现相关工具,协议使用可以参考这篇文章

4. 演示

以第2节中的拓扑为例,在内网IP为192.168.1.2的Server中运行miranda-upnp,打开转发通道,在Gateway的x.x.x.x:1234与Server 192.168.1.2:1234之间建立映射。

运行miranda-upnp后,首先执行msearch

1
2
3
4
5
6
7
8
9
10
11
12
upnp> msearch

Entering discovery mode for 'upnp:rootdevice', Ctl+C to stop...

****************************************************************
SSDP reply message from 192.168.1.1:49652
XML file is located at http://192.168.1.1:49652/49652gatedesc.xml
Device is running Linux/3.10.53-HULK2, UPnP/1.0, Portable SDK for UPnP devices/1.6.25
****************************************************************

^C
Discover mode halted...

此时已经获得Gateway中的SSDP描述文件,Ctrl-C停止SSDP搜寻。

执行host list,列出扫描到的主机,并执行host get 0选中主机0(Gateway):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
upnp> host list

[0] 192.168.1.1:49652

upnp> host get 0

Requesting device and service info for 192.168.1.1:49652 (this could take a few seconds)...

Host data enumeration complete!

upnp> host info 0 deviceList

InternetGatewayDevice : {}
WANDevice : {}
WANConnectionDevice : {}

执行deviceListservices命令分别查看设备列表和设备中的服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
upnp> host info 0 deviceList InternetGatewayDevice services

Layer3Forwarding : {}

upnp> host info 0 deviceList WANDevice services

WANCommonInterfaceConfig : {}

upnp> host info 0 deviceList WANConnectionDevice services

WANIPConnection : {}

upnp> host info 0 deviceList WANConnectionDevice services

WANIPConnection : {}

upnp>

常规的端口转发功能我们要使用的是WANConnectionDevice设备的WANIPConnection服务,执行actions查看其动作,这里可以看到存在一个名为AddPortMapping的action,我们只要调用这个action,并填写相应参数,即可完成端口转发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
upnp> host info 0 deviceList WANConnectionDevice services WANIPConnection actions

AddPortMapping : {}
GetNATRSIPStatus : {}
GetGenericPortMappingEntry : {}
GetSpecificPortMappingEntry : {}
ForceTermination : {}
GetExternalIPAddress : {}
GetConnectionTypeInfo : {}
GetStatusInfo : {}
SetConnectionType : {}
DeletePortMapping : {}
RequestConnection : {}

upnp> host send 0 WANConnectionDevice WANIPConnection AddPortMapping

Required argument:
Argument Name: NewPortMappingDescription
Data Type: string
Allowed Values: []
Set NewPortMappingDescription value to: XXXXX

Required argument:
Argument Name: NewLeaseDuration
Data Type: ui4
Allowed Values: []
Set NewLeaseDuration value to: 36000

Required argument:
Argument Name: NewInternalClient
Data Type: string
Allowed Values: []
Set NewInternalClient value to: 192.168.1.2

Required argument:
Argument Name: NewEnabled
Data Type: boolean
Allowed Values: []
Set NewEnabled value to: 1

Required argument:
Argument Name: NewExternalPort
Data Type: ui2
Allowed Values: []
Set NewExternalPort value to: 1234

Required argument:
Argument Name: NewRemoteHost
Data Type: string
Allowed Values: []
Set NewRemoteHost value to: 0.0.0.0

Required argument:
Argument Name: NewProtocol
Data Type: string
Allowed Values: ['TCP', 'UDP']
Set NewProtocol value to: TCP

Required argument:
Argument Name: NewInternalPort
Data Type: ui2
Allowed Values: []
Set NewInternalPort value to: 1234

可以看到此时Gateway中已经激活了相应的转发通道:

在Windows中创建服务一般需要先编写服务程序的代码,然后使用SCM注册这个服务。
手动注册一个Windows服务的方法是:

1
2
3
4
5
6
7
8
9
# 注册服务
sc create TestService binpath= D:\\Programs\\TestService\\main.exe
# 删除服务
sc delete TestService
# 开启服务
net start TestService
# 停止服务
net stop TestService

这里实现了一个最基本的Windows服务程序,服务启动之后监听本地TCP 8888端口,接收和发送数据。

service.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/*service.cpp*/


#include <stdio.h>
#include <Windows.h>

#include "service.h"



#define SLEEP_TIME 5000



SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;

void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
int InitService();
int WriteToLog(char* str);

int main(int argc, char* argv[]){
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceName = "ServiceExample1";
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;

StartServiceCtrlDispatcher(ServiceTable);
return 0;
}


void ServiceMain(int argc, char** argv){
int error;

ServiceStatus.dwServiceType = SERVICE_WIN32;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;

hStatus = RegisterServiceCtrlHandler("ServiceExample1", (LPHANDLER_FUNCTION)ControlHandler);
if (hStatus == (SERVICE_STATUS_HANDLE)0){
// Registering Control Handler failed
return;
}
// Initialize Service
error = InitService();
if (!error){
// Initialization failed
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
// We report the running status to SCM.
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus (hStatus, &ServiceStatus);

int server = start_server(8888);
/*
MEMORYSTATUS memory;
// The worker loop of a service
while (ServiceStatus.dwCurrentState == SERVICE_RUNNING){
char buffer[16];
GlobalMemoryStatus(&memory);
sprintf(buffer, "%d", memory.dwAvailPhys);
int result = WriteToLog(buffer);
if (result){
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
Sleep(SLEEP_TIME);
}
*/


return;
}


void ControlHandler(DWORD request){
switch(request) {
case SERVICE_CONTROL_STOP:
WriteToLog("服务已停止");
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &ServiceStatus);
return;


case SERVICE_CONTROL_SHUTDOWN:
WriteToLog("服务已关闭");
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &ServiceStatus);
return;

default:
break;
}

// Report current status
SetServiceStatus (hStatus, &ServiceStatus);
return;
}





int InitService(){
WriteToLog("服务已启动");
return true;
}

server.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/*server.cpp*/
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

#include "service.h"

#pragma comment(lib,"ws2_32.lib")


int start_server(int bind_port){
//初始化WSA
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(sockVersion, &wsaData) != 0){
return 0;
}

//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (slisten == INVALID_SOCKET){
WriteToLog("Socket创建失败!");
return 0;
}

//绑定IP和端口
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(bind_port);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR){
WriteToLog("Socket绑定失败!");
}

//开始监听
if (listen(slisten, 5) == SOCKET_ERROR)
{
WriteToLog("Socket监听失败!");
return 0;
}

//循环接收数据
SOCKET sClient;
struct sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
//char revData[255];
WriteToLog("Socket监听成功,等待连接...");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
while (1){
char revData[255];

char * client_address = inet_ntoa(remoteAddr.sin_addr);

//printf("等待连接...\n");
//sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if (sClient == INVALID_SOCKET){
WriteToLog("数据接收错误!");
continue;
}
WriteToLog( client_address );

//接收数据
int ret = recv(sClient, revData, 255, 0);
//printf(ret);
if (ret > 0){
//revData[ret] = 0x00;
WriteToLog(revData);
}

//发送数据
char * sendData = revData;
send(sClient, sendData, strlen(sendData), 0);

}
closesocket(sClient);
closesocket(slisten);
WSACleanup();
return 0;
}

utils.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*utils.cpp*/

#include <time.h>
#include <stdio.h>
#include <stdlib.h>


#define LOGFILE "C://service_example_1_log.txt"


int WriteToLog(char* str){
FILE* log;
log = fopen(LOGFILE, "a+");
if (log == NULL)
return -1;

time_t rawtime;
struct tm * timeinfo;
char time_buffer [128];

time (&rawtime);
//printf("%ld\n", rawtime);

timeinfo = localtime (&rawtime);
strftime (time_buffer,sizeof(time_buffer),"%Y/%m/%d %H:%M:%S",timeinfo);
//printf("%s\n", buffer);

fprintf(log, "%s\t%s\n", time_buffer, str);
fclose(log);
return 0;
}


service.h

1
2
3
4
5
6
/*service.h*/


extern int WriteToLog(char* str);
extern int start_server(int bind_port);

参考资料

开发 Windows 服务应用
C语言开发Windows服务
C++编写Windows服务
C++编写Windows服务
Windows 服务程序(一)
Windows 服务程序(二)
windows环境下C语言socket编程

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×