Bochspwn漏洞挖掘技术深究(1):Double Fetches 检测

虽然现在技术文章很少人看,大家都喜欢聊安全八卦,但技术文章输出是一种很好的学习方式。更重要的是,专业的文章是给专业的人看的,并非为了取悦所有人。

对于应用程序的代码插桩,有现成的Pin和DynamoRIO插桩框架,在Fuzzing中可以用来实现代码覆盖率的反馈驱动,这已经被应用到winafl,效果很好。除了挖洞,在逆向工程领域应用也很广泛。

上面都是针对应用层的,内核层的,上面的Pin和DynamoRIO就派不上用场了,对于这种系统内核级的指令插桩,有时就会采用虚拟化技术为实现,比如通过Qemu或Bochs虚拟机。

ProjectZero的j00ru大神就用bochs的插桩API为实现针对内核double fetches的监测,项目称为bochspwn,后来又采用污点追踪方式检测未初始化漏洞导致的内核信息泄露,叫bochspwn-reloaded。

Bochs Instrument API 文档参考:http://bochs.sourceforge.net/cgi-bin/lxr/source/instrument/instrumentation.txt,在编译bochs时指定插桩代码目录:

1
./configure [...] --enable-instrumentation="instrument/myinstrument"

下面是bochspwn中用到的API:

1
2
3
4
5
6
7
8
// Bochs初始化CPU对象时的回调函数
void bx_instr_initialize(unsigned cpu);
// Bochs析构CPU对象时的回调函数
void bx_instr_exit(unsigned cpu);
// Bochs访问线性内存时的回调函数
void bx_instr_lin_access(unsigned cpu, bx_address lin, bx_address phy,unsigned len, unsigned memtype, unsigned rw);
// Bochs执行指令前的回调函数
void bx_instr_before_execution(unsigned cpu, bxInstruction_c *i);

bx_instr_initialize用来加载配置信息,针对不同的系统环境设置不同的数据结构偏移地址,用来提供需要的进程/线程等重要信息:

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
[general]
trace_log_path = memlog.bin
modules_list_path = modules.bin

os = windows
bitness = 32
version = win10_32

min_read_size = 1
max_read_size = 16
min_write_size = 1
max_write_size = 16

callstack_length = 48
write_as_text = 0

symbolize = 0
symbol_path = <symbols path>

[win7_32]
kprcb = 0x120
current_thread = 0x04
tcb = 0x0
process = 0x150
client_id = 0x22c
process_id = 0
thread_id = 4
create_time = 0x200
image_filename = 0x16c
kdversionblock = 0x34
psloadedmodulelist = 0x18
loadorder_flink = 0x0
basedllname = 0x2c
baseaddress = 0x18
sizeofimage = 0x20
us_len = 0x0
us_buffer = 0x4
teb_cid = 0x20
irql = 0x24
previous_mode = 0x13a
exception_list = 0x0
next_exception = 0x0
try_level = 0xc
......

Bochspwn的核心功能实现就在于bx_instr_lin_accessbx_instr_before_execution两个函数。先看下bx_instr_before_execution的实现逻辑:

  1. 忽略实模式real mode
  2. 忽略无关的系统调用中断指令,仅允许int 0x2eint 0x80
  3. 获取当前进程/线程ID相关的信息,当发现漏洞时方便重现
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
void bx_instr_before_execution(unsigned cpu, bxInstruction_c *i) {
static client_id thread;
BX_CPU_C *pcpu = BX_CPU(cpu);
unsigned opcode;

// We're not interested in instructions executed in real mode.
if (!pcpu->protected_mode() && !pcpu->long64_mode()) {
return;
}

// If the system needs an additional invokement from here, call it now.
if (globals::has_instr_before_execution_handler) {
invoke_system_handler(BX_OS_EVENT_INSTR_BEFORE_EXECUTION, pcpu, i);
}

// Any system-call invoking instruction is interesting - this
// is mostly due to 64-bit Linux which allows various ways
// to be used for system-call invocation.
// Note: We're not checking for int1, int3 nor into instructions.
opcode = i->getIaOpcode();
if (opcode != BX_IA_SYSCALL && opcode != BX_IA_SYSENTER && opcode != BX_IA_INT_Ib) {
return;
}

// The only two allowed interrupts are int 0x2e and int 0x80, which are legacy
// ways to invoke system calls on Windows and linux, respectively.
if (opcode == BX_IA_INT_Ib && i->Ib() != 0x2e && i->Ib() != 0x80) {
return;
}

// Obtain information about the current process/thread IDs.
if (!invoke_system_handler(BX_OS_EVENT_FILL_CID, pcpu, &thread)) {
return;
}

// Process information about a new syscall depending on the current mode.
if (!events::event_new_syscall(pcpu, &thread)) {
return;
}
}

再看下bx_instr_lin_access实现逻辑:

  1. 忽略仅读写指令
  2. 检测CPU类型(32位或64位)
  3. 判断当前指令地址pc是否为内核地址,判断访问的线性内存地址是否为用户层地址
  4. 检测读取的内存长度是否处于0~16字节之间,长度大小范围在config.txt中配置,仅处理此范围内的指令操作
  5. 通过上述条件之后,就代表可能存在内核漏洞,然后反汇编指令,然后填充日志记录信息
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
void bx_instr_lin_access(unsigned cpu, bx_address lin, bx_address phy,
unsigned len, unsigned memtype, unsigned rw) {

BX_CPU_C *pcpu = BX_CPU(cpu);
// Not going to use physical memory address.
(void)phy;

// Read-write instructions are currently not interesting.
if (rw == BX_RW)
return;

// Is the CPU in protected or long mode?
unsigned mode = 0;

// Note: DO NOT change order of these ifs. long64_mode must be called
// before protected_mode, since it will also return "true" on protected_mode
// query (well, long mode is technically protected mode).

if (pcpu->long64_mode()) {
#if BX_SUPPORT_X86_64
mode = 64;
#else
return;
#endif // BX_SUPPORT_X86_64
} else if (pcpu->protected_mode()) {
// This is either protected 32-bit mode or 32-bit compat. long mode.
mode = 32;
} else {
// Nothing interesting.
// TODO(gynvael): Well actually there is the smm_mode(), which
// might be a little interesting, even if it's just the bochs BIOS
// SMM code.
return;
}

// Is pc in kernel memory area?
// Is lin in user memory area?
bx_address pc = pcpu->prev_rip;
if (!invoke_system_handler(BX_OS_EVENT_CHECK_KERNEL_ADDR, &pc, NULL) ||
!invoke_system_handler(BX_OS_EVENT_CHECK_USER_ADDR, &lin, NULL)) {
return; /* pc not in ring-0 or lin not in ring-3 */
}

// Check if the access meets specified operand length criteria.
if (rw == BX_READ) {
if (len < globals::config.min_read_size || len > globals::config.max_read_size) {
return;
}
} else {
if (len < globals::config.min_write_size || len > globals::config.max_write_size) {
return;
}
}

// Save basic information about the access.
log_data_st::mem_access_type access_type;
switch (rw) {
case BX_READ:
access_type = log_data_st::MEM_READ;
break;
case BX_WRITE:
access_type = log_data_st::MEM_WRITE;
break;
case BX_EXECUTE:
access_type = log_data_st::MEM_EXEC;
break;
case BX_RW:
access_type = log_data_st::MEM_RW;
break;
default: abort();
}

// Disassemble current instruction.
static Bit8u ibuf[32] = {0};
static char pc_disasm[64];
if (read_lin_mem(pcpu, pc, sizeof(ibuf), ibuf)) {
disassembler bx_disassemble;
bx_disassemble.disasm(mode == 32, mode == 64, 0, pc, ibuf, pc_disasm);
}

// With basic information filled in, process the access further.
process_mem_access(pcpu, lin, len, pc, access_type, pc_disasm);
}

信息记录方式都是通过invoke_system_handler函数去处理自定义系统事件,目前主要支持4种操作系统(windows\linux\freebsd\openbsd),macOS还没搞过,原作者是说想继续实现macOS,这个值得尝试开发下:

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
const struct tag_kSystemEventHandlers {
const char *system;
s_event_handler_func handlers[BX_OS_EVENT_MAX];
} kSystemEventHandlers[] = {
{"windows",
{(s_event_handler_func)windows::init,
(s_event_handler_func)windows::check_kernel_addr,
(s_event_handler_func)windows::check_user_addr,
(s_event_handler_func)windows::fill_cid, // 获取线程环境块TEB,读取进程/线程ID
(s_event_handler_func)windows::fill_info, // 基于config.txt中配置的进线程结构offset去读取进线程信息,包括进程文件名、创建时间、栈回溯等信息
(s_event_handler_func)NULL}
},
{"linux",
{(s_event_handler_func)linux::init,
(s_event_handler_func)linux::check_kernel_addr,
(s_event_handler_func)linux::check_user_addr,
(s_event_handler_func)linux::fill_cid,
(s_event_handler_func)linux::fill_info,
(s_event_handler_func)NULL}
},
{"freebsd",
{(s_event_handler_func)freebsd::init,
(s_event_handler_func)freebsd::check_kernel_addr,
(s_event_handler_func)freebsd::check_user_addr,
(s_event_handler_func)freebsd::fill_cid,
(s_event_handler_func)freebsd::fill_info,
(s_event_handler_func)freebsd::instr_before_execution}
},
{"openbsd",
{(s_event_handler_func)openbsd::init,
(s_event_handler_func)openbsd::check_kernel_addr,
(s_event_handler_func)openbsd::check_user_addr,
(s_event_handler_func)openbsd::fill_cid,
(s_event_handler_func)openbsd::fill_info,
(s_event_handler_func)openbsd::instr_before_execution}
},
{NULL, {NULL, NULL, NULL, NULL, NULL}}
};

最后就是输出记录的信息,比如作者发现的CVE-2018-0894漏洞信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
------------------------------ found uninit-copy of address fffff8a000a63010

[pid/tid: 000001a0/000001a4] { wininit.exe}
COPY of fffff8a000a63010 ---> 1afab8 (64 bytes), pc = fffff80002698600
[ mov r11, rcx ]
Allocation origin: 0xfffff80002a11101
(ntoskrnl.exe!IopQueryNameInternal+00000071)
--- Shadow memory:
00000000: 00 00 00 00 ff ff ff ff 00 00 00 00 00 00 00 00 ................
00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
--- Actual memory:
00000000: 2e 00 30 00 aa aa aa aa 20 30 a6 00 a0 f8 ff ff ..0..... 0......
00000010: 5c 00 44 00 65 00 76 00 69 00 63 00 65 00 5c 00 \.D.e.v.i.c.e.\.
00000020: 48 00 61 00 72 00 64 00 64 00 69 00 73 00 6b 00 H.a.r.d.d.i.s.k.
00000030: 56 00 6f 00 6c 00 75 00 6d 00 65 00 32 00 00 00 V.o.l.u.m.e.2...
--- Stack trace:
#0 0xfffff80002698600 (ntoskrnl.exe!memmove+00000000)
#1 0xfffff80002a11319 (ntoskrnl.exe!IopQueryNameInternal+00000289)
#2 0xfffff800028d4426 (ntoskrnl.exe!IopQueryName+00000026)
#3 0xfffff800028e8fa8 (ntoskrnl.exe!ObpQueryNameString+000000b0)
#4 0xfffff8000291313b (ntoskrnl.exe!NtQueryVirtualMemory+000005fb)
#5 0xfffff800026b9283 (ntoskrnl.exe!KiSystemServiceCopyEnd+00000013)

从杀软之殇谈产品

百度

前几天,百度杀毒、百度卫士宣布停止服务,祭出“百度杀毒感谢一路有你”的殇曲,就此终结!

PC杀毒软件的历史已经有35年,但在这移动互联网的时代,问津者能有几人呢?

今天就此事聊聊安全产品和安全从业人员。

面向系统的产品

杀毒软件的出现就是弥补系统自身安全能力的不足,算是面向系统的安全产品,不仅是PC,Android上也是如此。

当年Android病毒那么严重,第一款Android主动防御软件LBE诞生时也是风光无限,甚至后面还利用系统漏洞作防御。

再看看现在,随着Windows系统自主安全能力以及自家杀软Windows Defender的不断完善,并且提供给杀软的接口越来越少,权限越来越集中管理,使得PC杀软越来越无用武之地,甚至曾经的头牌杀软已沦为流氓软件。

而这几年Google在Android安全上的投入也是非常大,整个系统的安全性相比前几年提升很大,他们家的chrome也曾在pwn2own黑客大赛幸存过几年,虽然今年被破了。

以前iOS截图后是不能直接分享的,然后就有一些软件直接提供截图后的分享功能,但是现在苹果自己在iOS上做了,如果软件还保留原分享功能,就成为一种干扰,有些软件就又不得不去掉:

image-20181124123301131

所以,凡是面向系统的产品,自系统厂商打算自己做时,就没你啥事了,无论是现今的PC,还是未来的移动端,均是如此。

面向业务的产品

以前,很多企业为了维护业务安全,都是从安全公司买服务或产品。

后来,随着企业的壮大,很多安全工作都企业自己做,自己开发扫描器、防火墙等等,然后各安全公司就开始被收购,乙方安全人员开始向甲方流动。

再后来,企业内部的业务部门也逐渐壮大,很多业务也开始自己做安全,他们也怕被安全部门捅漏洞丢面子,一些容易做的安全产品会被优先替代掉,一些需要长期投入并维护的安全产品,就得看业务部门是否有此人力,有些安全产品没有多年的积累一时也是无法替代的。与此同时,一些安全部门人员开始向业务部门流动。

所以,凡是面向业务的产品,自业务打算自己做时,就没你啥事了

面向人的产品

人,可以分为商户(2B)与用户(2C),比如阿里就是典型的2B基因,腾讯就是典型的2C基因,虽然他们各自都不信这邪。正如当年阿里做来往,腾讯做拍拍一般,结局都是相当地凄惨。

杀软2C的道路基本已经凉凉的,但是2B之路还有点残羹剩饭,尤其是天朝机构,出于国家安全考虑,在抵制国外安全产品的同时,就需要国产安全产品替代。所以现在很多国内杀软都是逐步转向做2B业务,但肯定都是不如从前了。

人总有各种各样的需求,面对人的产品就是为了解决这些需求。比如人与人的沟通,以前飞鸽传书、快马加急,到现在的电话短信、微信视频等方式,需求亘古不变,只是产品在进化,在被另一种更高效、更低成本的产品替代。

所以,面向人的产品,产品要解决的需求形态可能长期存在,但产品终会被更高效、更低成本的产品替代

安全人员的生存之道

最近一年,在微信群里,仍然还有一些做Windows病毒分析的同学,还在聊Windows病毒,看着都觉得无力。如果是结合漏洞的病毒样本分析,还可以高谈APT混口饭吃,而其它类型的病毒都已经没什么市场了。

从一方面讲,移动时代不求思变转型的安全人员,有时也挺惨的,杀软产品不做了,这帮开发和分析人员又该何去何从。

就连招二进制安全的,很多人只会windows平台,不懂移动端,经常无法满足业务安全需求。当然,如果是深入Windows系统漏洞攻防的,那现在还是相当有市场的。

杀软这事也只是系统自身进化导致的,如果是系统颠覆的,如Android灭掉了Symbian,一身超神的塞班技能也无济于事。

我现在特别希望Android被新系统颠覆,所有安全人员又打回同一起跑线,重新开始研究新系统的漏洞攻防。当年虽赶上Android安全,但只搞了应用安全,没有深入系统安全,后来上车晚了,还是觉悟不够。

所以,安全人员还是得居安思危,不然哪天就真没饭吃了。

读《态度》

image-20181118184524117

《态度》是吴军在今年新出的书籍,是他写给女儿的40封家书。第一次读吴军的书是《见识》,读完觉得不错,因此这次“双十一”买了好几本他写的书,包括《浪潮之颠》、《大学之路》。

在新书中,主要分6部分来讲,分别讲了一些做人做事的原则、对待金钱和人际关系的态度、还有学习和人生哲学的一些观点。

关于教育

作者说”教育改变命运“,其实多数读书人应该都认同这一观点,他在书中讲述了3个观点:

  1. ”教育改变命运“已成为全世界大部分国家的共识

    欧洲很早就意识到教育对人一生的帮助,所以很早就开始兴办免费教育,让交不起学费的贫家子弟都可以到”官办“学校读书,牛顿就是靠这种免费的公立教育完成中学学业,进入剑桥大学的。很多来自中国农村的贫穷人家,为了摆脱贫穷,让自己子女接受良好的教育,通过一代人的努力摆脱贫穷,改变命运。

  2. 衣食无忧的富家子弟也有必要接受教育

    中国著名作家吴晓波,就是那个写了《腾讯传》的人,调查了早期在股市上发财的几十人,发现除了一两个之外,其他人的结局无一例外都很惨,有破产的,有坐牢的,有被仇家杀的,有正在被追杀的。这些人都有两个共同点:第一,敢于冒险;第二,受教育水平低,最高中学学历。因此,他们在有了钱之后,没有更高的理想和追求。

    【PS】:在中国,尤其是农村,一些没接受过多少教育的暴发户,有时会对年轻人或读书人说:“读书没有用的,你读那么多书赚的还不是没我多”!如果此时你对他说:“让你的孩子退学吧!“,估计他就没话了。还有一些在外工作多年的人,包括腾讯人,有时过年同学聚会,发现一些曾经读书不好或者辍学的老同学现在混得相当不错,比自己好太多的时候,通常都会反问自己:“读那么多书真的有用吗“?这种情况都是”幸存者偏差“的认识而已(顺便给大家推荐另一本书《思考,快与慢》,里面就讲了很多这种思考或认识的陷阱)。记得,腾讯内部论坛就有人发过这样感慨。

  3. 对退学创业的误解

    互联网行业的退学创业代表主要有5人:比尔盖茨、乔布斯、佩奇、布林,以及扎克伯格。佩奇和布林是进入斯坦福读博士之前退学的,盖茨和扎克伯格情况类似,人家都是上过哈佛的,同样比绝大部分年轻人都出色,并接受过良好的教育,而且他们都是创业成功之后才退学的。乔布斯是因为不忍花父母的钱才没有读大学的,但至少是在斯坦福大学接受过教育的,如果他来自一个相对富有的家庭,或许会读完大学。

####关于格局

决策时格局要大,做事时境界要高。用作者通俗一点的话来讲,就是要长远考虑,尽可能往最好的目标努力。

书中举例几个MIT和哈佛毕业生组成的创业团队,他们打算做高频交易(在股市中通过快速操作以赚取低买高卖时差价)的创业项目,作者认为他们都是非常聪明的人,但做事的格局不大,因为这赚不到大钱,属于小打小闹,而且这事仅是提高股市交易量,并无其它意义,对世界更不可能产生任何重大影响,而且这件事的公司已经很多了。

这种看似只赚不赔的高频交易公司为何做不到像Google、Apple那样赚更多钱呢?因为前者以改变世界为目的,后者以赚小钱、小富即安为目的。

很多亚裔家长一直在纠结孩子上名校是否有用?作者对此的观点是,如果格局提升不上去,上了也没有用,还达不到谷歌员工的平均水平。

关于贫穷

一个生于富有家庭的孩子不怕别人说他穷的,而一个贫家子弟通常反而怕别人说他穷,看不起他。卖肾买iPhone的人,一定是贫家子弟,怕别人说他穷。于是,很多穷人最后还是选择与穷人,甚至比自己穷的人为伍,成绩差的孩子还是选择扎堆一块玩。久而久之,那些人就无法摆脱原属阶层了。

大家常说:”物以类聚,人以群分“,因为这样对人来说,交际成本是最低的,也是最舒适的方式。一般人都不太愿意跳出自己的舒适区,去尝试更有难度且有助提升自我的挑战。

不管贫穷与否,如果事情做不好,那么你所处的地位可能与贫穷无异。通常情况下,人的心理能否接受自己不如别人,在可能会被别人嘲笑的情况下,是否还能努力往前走,直到改变自己的状态,这就是一种考验,与走出贫困差不多。

其实多数人都知道,要多向学习好的、能力强的人为伍,这些道理小学老师都经常说。但有时要去向比自己牛逼的人学习请教,也是需要一点勇气,以及改变自我的努力和决心。

关于投资

作者在书中挺推荐买标准普尔500指数,还让他女儿拿1/3的钱直接购买标普500指数ETF,每过两三个月就继续定投,不管涨跌。说实话,我也没关注这指数,今天赶紧加个关注先。

作者对投资给出了4条建议:

  1. 永远不要觉得自己能够打败市场

    我觉得下面这张图已经足够说明一切了,最近在微信和朋友圈上传得挺火的。

    image-20181118191352193

  2. 对市场要有信心

作者说,股市有涨有跌,但要相信股市在较长的时间里是往上走的。

我曾经对腾讯700也是迷之自信,但你看看现在,这一年都跌成啥样了,当然如果你说再等个一两年,那应该还是涨的,看谁命长了。还好作者后面补了个第3点。

  1. 虽然股市在下跌后总会涨回来,但是单一股票未必。

在2000年股价到达顶点的英特尔和思科,今天的股价不足当年的1/4,而且可能永远没有机会回到当时的峰值了。也就是说,投资单一的股票,即使遇到明星公司,也未必能长期赚钱。

  1. 时间是你的朋友,而时机不是。

投资要有耐心,不要急于求成。聪明的投资人永远在股市上投资,而不是试图投机挑选最低点和最高点。因此,走出坏运气的关键是耐心,让时间成为我们的朋友。

除以上建议之外,作者还给出了3条禁忌:

  1. 不要进行过于冒险,会导致来顶之灾的投资

    比如做空股票和使用杠杆投资,你们再回头看看“格雷厄姆的微笑”吧!

  2. 不要进行自己不懂的投资

    拿自己的短处和别人的长处比,胜算微乎其微。同时,你看不懂的投资里面常常有很多陷阱。

  3. 不要被那些所谓的失去了的投机机会乱了方寸

    经常有人说:”如果我当初买了比特币,今天能赚100倍“。这种话是没有意义的,如同中彩票一般,是运气,遇不上,也不必在意,因为人生的机会还有很多。

关于友情

作者在书中讲到一个在腾讯的经历,大家感受下,其它就不啰嗦了:

IMG_6440

关于交际

与我们接触的人当中,可以分为4种:

  1. 与自己关系好,能力强;
  2. 与自己关系好,能力有限;
  3. 并非自己朋友,但能力强;
  4. 与自己关系不好,能力不强。

第1种好办,第4种交集不多,可以忽略,所以为人处世方面,我们需要比较留意的第2和第3种人。

对于这些人,我们必须理性对待,避免依据个人喜好来判断人和事:符合自己喜好的人,无论他们做什么都觉得好;不符合自己喜好的人,无论他们做什么都要挑毛病。

比如特朗普,很多人讨厌他,其实我也讨厌他,然后就有很多人对他做的任何事都持反对意见,无论对错,这就有点失去理想了。

在社会上,个人生活和事业有时是需要他人的支持和帮助,需要我们能够团结大多数人,把事情做好。

关于拒绝

作者举例说曾有朋友找他帮忙,就是帮个孩子联系谷歌或者腾讯的实习机会,但看了孩子材料之后就直接回绝了。那朋友也表示理解,就没有再提此事。

这种事太常见了,尤其是在腾讯这类公司,很多人会找过来帮忙找个工作之类,现在基本每次回家都有人找我。以前有人让我帮忙投简历,我一般都直接帮投了,即使有些简历或者学历问题,基本都拿不到面试机会的,我也帮忙投了,所以在公司那个”伯乐“平台上,结果全是拒绝的。后来,我学聪明了,不再当”老好人“。一些简历有问题,有明显水平不行的,我都委婉地回绝掉。一方面,投了也是白投,另一方面HR又不傻,我老帮人投这类简历,别人还会觉得我看人的水平太次太有问题了。

对于别人请帮忙的事,作者总结出4点,按不同情况采取不同的方法处理:

  1. 能力不及,不能帮上忙,直接在第一时间委婉拒绝。
  2. 能帮上忙,但是自己代价太大,不想帮的,就不要勉强自己,但也要及早通知对方。
  3. 不论多困难都愿意帮,而且极有可能办成,这时就答应对方,然后全力去做。
  4. 虽然愿意帮,但有可能帮上,也可能帮不上,这时要将实际情况说明,千万不要轻易许诺。

结语

这书整体上我觉得还不错,虽然有些是对中学生或大学生说的道理,很多事情自己经历过也都明白,但也有一些适合不同年龄段的人学习的知识点,所以还是推荐阅读。

喜欢吴军写的书,并非崇拜对方,而他写的书确实很好,也很用心。在这种浮躁的社会,出书也不容易,写好书更不容易,自己出过书的人应该更有体会。

读《风格感觉:21世纪写作指南》

1、节俭使用元话语(语句中标示话语结构的标记语言,即用来提醒读者应该注意什么),可以用提问代替元话语,比如:

【改前】:这一章讨论引起名字流行程度上升和下降的因素。

【改后】:一个名字流行或不流行的原因是什么?

2、放弃专家腔、更自然地对话

【改前】:近年来,越来越多的心理学家和语言学家将注意力转向儿童语言习得的问题。本文将评述这一过程近年来的研究。

【改后】:小孩子不用专门上课,就能获得说一门语言的能力。他们怎么做到的?

3、写作清晰有力,少用模糊词汇

【改前】:杰克是个诚实的人。

【改后】:杰克是个特别诚实的人。

加了“特别”反而让人产生困惑,甚至以为说的是反话,所以少用“非常”、“十分”、“特别”这种过犹不及的强调词。

4、少用抽象词,比如“xx性”、“xx观”、“xx力”,这种特别容易出现在工作中,俗称“官僚体”,腾讯内部就有一堆此类名词(闭环、打法、自控力、大局观、专注度……)

5、去掉僵尸名词:比如“做出确认”变成“确认”、“做出辞职的决定”变成“决定辞职”。

6、采用主动和互动风格,即站在读者角度或者自己主动的角度。

【改前】:我们很高兴地宣布,本实验室的新设施将对外开放,随时准备承接各类脑科学实验。

【改后】:你将有机会使用本实验室,来做你的脑科学实验。

7、【有序地称呼反复出现的事物】避免读者过多思考,免得他们需要思考是否为新事物

一个英国人、一个法国人和一个犹太人坐在一起,这个英国人说……,这个法国人说……,这个犹太人说……

少用他/她/它去代替前面说过的人物事等,减少读者疑惑和思考,避免产生歧义,比如:

愧疚、复仇和苦痛会从感情上毁掉你和你的孩子。必须摆脱他们。

上面的是摆脱你的孩子,还是摆脱愧疚、复仇和苦痛?

8、避免使用过多的连接词,否则文章就会变得臃肿不堪,比如下句中的因为就是多余的,因为前面的“原因”已经暗示我们正在做出解释:

太多人生活在黑暗之中的原因是因为他们想那样生活。

9、【谨慎地运用否定】正如克林顿辩解道“我没有和那个女人发生过性关系”的时候,并没有使各种传言平息下来,有时甚至起到“此地无银三百两“的反作用。

10、正确的写作用法只是促成良好写作最小的因素,其重要性远远比不过保持连贯性、使用古典风格、克服知识的诅咒,更不用说在智力上维护勤恳了。假如真的希望提高写作质量,或者怒斥他人的文章,最需要关注的不是那些语法规则,而是那些支配批判性思考和发现事实的规则:

​ a. 查资料

​ b.确保论证有理有据

​ c. 不要把轶事或个人经历当作世界的常态,避免把特殊个例当作显著现象

​ d. 谨防虚假的二分法:把复杂问题化为两种思想之间的战争,几乎无法帮助我们增进理解

​ e.论证应当基于理性,而非个人。即“不要证明自己对,而要弄清什么是对的”

最后说一句最重要的话:

热文或畅销书之所以流行,关键在于其内容,而非华丽的辞藻文法。

我的挑书手段

双十一,一年一度的”屯书日“又到了。

鄙人生平无其它购物嗜好,唯有购书。

在电子书横行的时代,各类读书应用都在崛起,比如”微信读书“、”QQ阅读“、”掌阅“等,个人习惯用微信读书,因为有社交好友的阅读推荐,以及公司送的读书券,所以基本也都是免费的,这个才是重点。

对于懒人,也有听书应用,比如”懒人听书“、”喜马拉雅“,特别适合听小说、历史人文,之前我就在上面听过《明朝那些事儿》、《盗墓笔记》等,特别是一些小说类的配音,如电影一般,甚是精彩声动。

开篇聊完,该说说”挑书”这件事儿了……

首先,对于非技术书,如果在上述读书app中有免费的,一般我不会去买实体书,直接在app上翻来看看,有兴趣就继续,没兴趣就结束了。

所以,这里所说的挑书,挑的是技术书以及无电子版的非技术书,或者其它想收藏的经典书籍(如书帖、史书、经管心理名著等等)。

挑书第一式:查作者翻目录

翻开当当网信息安全书籍总榜,在前3名,永远有一本书占其中,书名永远是《黑客xx从入门到精通》,而且还是个系列,好多本,也不得不承认,越初级越入门,越受欢迎,毕竟菜鸟永远比专业人士多……

image-20181110093819437

挑书第一眼,自然是作者与目录,但对它们的判断,又常常要求读者具备一定的专业知识,因为:

“只有具备犯罪能力的人才能洞察他人的犯罪行为。”

先看作者,经常署名“xx工作室”,也有个人,此时都可以查看他们出过的书,对比各书目录,经常可以发现“换汤不换药”的行为,把旧书内容重新包装进新书再出版。

有时出版社编辑也会找我帮忙评价下某些新书目录,我就经常这么搞,偶而就会发现存在这种情况。

image-20181110095640534

现在很多书籍都可以在线试读了,也一种不错的挑书方式。

挑书第二式:利用工具查价

每到购物节,各网站都会推出“满多少减多少”的优惠,但有时查下历史价格趋势会发现,都是先抬高价格再打折的。

对于这种情况都一些工具可以查询,如果是在电脑上可以用“油猴“脚本:购物党自动比价工具:

image-20181110100630299

如果是手机上,可以使用”慢慢买“、”历史价值查询“,比如京东《态度》这本书,昨天就从32.5抬高到59:

image-20181110100956404

挑书第三式:看评价

购物网站上面都有评价可以看,还有豆瓣读书上,都可以看到一些书籍的评价,作为自己的一些参考。虽然豆瓣读书的评分不如电影那般准确,但评语还是可以参考的。

还有网上也有一些推荐书籍的文章,经典的比如”C语言之四书五经“、“Linux内核学习四库全书”,还有最近左耳朵耗子在微博上推荐的“程序员必读经典书籍”,都是可以借鉴参考的。

这些文章网上都有,大家自行搜索,对于信息安全从业人员,之前我也列了个书单“信息安全从业者书单推荐”:https://github.com/riusksk/secbook,好坏自行判断,毕竟不同人对同一本书的看法也是不一样的。

挑书第四式:预估对个人的实用价值

“买书如山倒,看书如抽丝”是多数人的真实写照,所以有时我也特别能理解女生为何喜欢整天买化妆品衣服包包之类的。对于这种情况,我一般这样选择:

  • 在未来一年自己用不上的技术知识,不买相关书籍。

  • 未来一年可能过时或淘汰的技术知识(比如Flash、塞班等),不买相关书籍

  • 挑选与当前自身能力要求相近或高一点的书籍,至少能看懂半本书的,当年初学二进制逆向,看《网络渗透技术》一书就跟天书一般,没几年的技术功底积累,也根本看不懂此书

  • 可能绝版的好书,也可考虑提前购买,即使当前看不懂,还说《网络渗透技术》这书,后来我在淘宝双倍价购买打印版了,现在china-pub上也可以双倍价购买此书,不过封面已换

挑书第五式:打铁仍需自身硬

遥想当年,我看的第一本安全书籍叫《黑客入门》,当时挑书的标准是:必须带有“黑客”两个字,否则不看。

过一段时间,发现这些书籍对提高技术并没有什么作用,然后开始找大学计算机课程开始从基础学起。

这个转折点,要从《深入理解计算机系统》开始,然后又开始学汇编、C/C++、数据结构与算法等等基本课。

当自己积累得越来越多的时候,能看懂的书就越多,对技术书籍的好坏判断,自然就有自己的评判标准。

WX20180707-203543

honggfuzz漏洞挖掘技术深究系列(5)—— Intel Processor Trace

对于闭源程序的反馈驱动Fuzzing,通常有3种方式:

  • 二进制插桩:使用Pin或DynamoRIO动态插桩监控代码覆盖率,比如winafl

  • 虚拟化技术:使用Qemu或Boch等虚拟化技术实现应用层和内核层的代码覆盖率监控,比如afl、bochpwn

  • 硬件级技术:使用Intel Processor Trace(PT)技术,比如honggfuzz

Intel PT

Intel® Processor Trace (Intel® PT) 是在i5/i7 5000以上型号上加入的功能,由于它是硬件级的特性,相比Qemu或Boch,在性能上和代码工作量会占有一定优势。在Linux上可以通过perf来使用PT,可以先简单看是否支持PT:

1
2
3
4
5
查看是否支持PT:
ls /sys/devices/intel_pt/format

追踪程序执行:
perf record -e intel_pt// program

也可以使用开源工具simple-pt中的ptfeature命令行工具来检测:

1
2
./ptfeature pt
Supports PT

最新版GDB也支持pt功能了:

1
2
3
4
5
6
7
gdb program
start
record btrace pt
cont

record instruction-history /m # show instructions
record function-history # show functions executed

honggfuzz perf_event_open

在程序内通过perf_event_openhttp://man7.org/linux/man-pages/man2/perf_event_open.2.html)函数可以使用PT实现BB基本块的覆盖率追踪,传递给指定进程pid来实现监控:

将返回的文件描述符传递给mmap映射为可读写的用户内存空间,以便从中读取PT记录的追踪数据:

PT记录的追踪数据采用压缩的二进制格式输出,每秒每个CPU都会持续记录并输出,由于是硬件记录的,最早自然是出现在内核空间,为了使用它,就需要将其导出到用户空间,即通过前面mmap方法映射到用户可写的内存空间,然后再去定位数据解码。PT导出的追踪数据被存储在一个叫AUX space的内存区域,它相对perfMmapBuf的偏移记录在perf_event_mmap_page->aux_offset,大小为perf_event_mmap_page->aux_size,上面代码的第二步mmap就是去映射AUX space

接下来就是利用libpt来解码捕获到追踪数据,实现函数位于perf_ptAnalyzePkt中:

最后将执行到的BB基本块信息更新到feedback map,之后的实现步骤就跟本系列第1篇驱动反馈中所讲的一致。

跑起来的效果如下图:

image-20181201105755833

到这里,关于《honggfuzz漏洞挖掘技术深究系列》的文章先暂告一段落了,它就相当于是自己的学习笔记,也可以留作日后查询。

本系列的其它文章如下:

honggfuzz漏洞挖掘技术深究系列(1)——反馈驱动(Feedback-Driven)

honggfuzz漏洞挖掘技术深究系列(2)—— Persistent Fuzzing

honggfuzz漏洞挖掘技术深究系列(3)——Fuzz策略

honggfuzz漏洞挖掘技术深究系列(4)—— 扩展Fuzzer

honggfuzz漏洞挖掘技术深究系列(4)—— 扩展Fuzzer

对于一些复合文件格式,如果只是单纯的暴力Fuzzing,会导致生成很多无法被解析的文件,因此需要对文件变异作一些定制化的工作,比如docx、doc等office复合文件,docx是个压缩包,doc是个OLE格式,如果fuzz docx自然需要将其zip解压,再针对感兴趣的文件作变异,对于doc最好是作文件格式解析,只对感兴趣的stream作文件变异,这样的fuzzing的有效性才会更高。

庆幸地是,honggfuzz提供-c参数用于扩展变异规则以代替原有变异方式,同时提供有--pprocess_cmd在原有的文件变异后再作处理:

1
2
3
4
--mutate_cmd|-c VALUE
External command producing fuzz files (instead of internal mutators)
--pprocess_cmd VALUE
External command postprocessing files produced by internal mutators

-c功能比较有用,也是我用得比较多的,另一个--pprocess_cmd基本我没用过。

当你通过-f提供输入样本目录后,在fuzzing时,随机提取的文件会直接传递给-c参数指定的扩展命令作变异。

比如想针对某文件特定offset范围内的内容进行变异,下面是针对macOS/iOS字体文件中的虚拟指令作Fuzzing时写的脚本:

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
#!/usr/bin/env python

import mmap
import os
from random import randint
import sys

RANGE_START = 0x16D8
RANGE_END = 0x304D
MIN_BYTES_TO_FLIP = 1
MAX_BYTES_TO_FLIP = 5

if ".DS_Store" in sys.argv[1]:
exit(1)

with open(sys.argv[1], "r+b") as f:
mapped = mmap.mmap(f.fileno(), 0)
#print "file size: 0x%x" % len(mapped)
bytes_to_flip = randint(MIN_BYTES_TO_FLIP, MAX_BYTES_TO_FLIP)
bytes_flipped = 0

while bytes_flipped < bytes_to_flip:
byte_pos = randint(RANGE_START, RANGE_END)
#print "byte_pos: 0x%x" %byte_pos
byte_new = chr(randint(0, 255))
mapped[byte_pos] = byte_new
bytes_flipped += 1

mapped.close()

变异效果:

最后挖到一个TTF字体虚拟指令漏洞:

1
2
3
4
5
6
7
orig file:
2F90h: 00 3F C5 CD 2B 10 C1 10 DE 3F C5 【CD】 2B 10 C5 10

poc file:
2F90h: 00 3F C5 CD 2B 10 C1 10 DE 3F C5 【DD】 2B 10 C5 10

glyf table -> SimpleGlyf[] -> Instructions('0xCD' => ‘0xDD') -> MDRP指令

同样的,你也可以写个doc、docx等office文件格式解析并变异的扩展fuzzer,比如利用olefile库(但只支持修改同等大小不变的doc,要插入或删除需要自行实现),或者通过COM接口来实现操作。

比如之前有段时间doc中的公式编辑器存在很多漏洞,你就可以专门针对Equation Native流作fuzzing。

最后放两张图(riufuzz是自己对honggfuzz二次开发的版本,后面有机会再讲):

honggfuzz漏洞挖掘技术深究系列(3)——Fuzz策略

honggfuzz在对输入文件进行变异前,会先创建个临时文件名(honggfuzz+pid+time),然后将输入数据变异后写入临时文件。

fuzz策略的实现主要集中在mangle.c中,在循环的fuzzloop函数中,会根据用户的选择的fuzz方式来调用动态fuzz或者静态fuzz的方法,但最后都是调用mangle_mangleContent来变异文件数据:

跟进mangle_mangleContent函数:

重点就在于后半部分,它会随机选择变异函数进行处理,更改的字节数也是随机的,根据用户指定的mutation变异率来定,即允许变异文件大小的百分比,变异函数列表如下:

这些函数都是在mangle_init中初始化,各函数之间也会相互调用:

把这些函数过一遍就是honggfuzz中所有的文件变异规则了,如果想实现自己的fuzzer,这些规则来扣出来用Python实现一遍,顺便把afl的规则也扣过来就更完美了,下面是我之前写office fuzzer时的半成品代码,最后偷懒直接用radamas去实现变异了:

再回到刚才的变异函数列表,我们一个个走读源码。

1、mangle_Resize函数:

用空格填充随机位置

2、mangle_Byte函数:

向随机位置写随机的uint8类型的数据

3、mangle_Bit函数:

取随机位置的数值做位翻转

4、mangle_Bytes函数:

在随机位置覆盖写2~4字节数据

5、mangle_Magic函数:

取各种边界值进行覆写,这些边界值部分跟AFL还不一样,我在自己的fuzzer里面把它们作了整合。由于边幅所限,我省略了不少边界值:

6、mangle_IncByte函数:

取随机位置的数据加1

7、mangle_DecByte函数:

取随机位置的数据减1

8、mangle_NegByte函数:

取随机位置的数据取反

9、mangle_AddSub函数:

取随机位置的1、2、4或8字节的数据长度作加减操作,操作数取 rand(0~8192)-4096

10、mangle_Dictionary函数:

变异目录名,也是随机取文件夹名称进行变异,如果有多个目录,那被变异的目录数也是随机的

11、mangle_DictionaryInsert函数:

在目录的随机位置中插入随机数据

12、mangle_MemMove函数:

取随机位置的数据拷贝随机长度的数据,里面就是调用memmove函数实现的

13、mangle_MemSet函数:

取随机位置、随机大小,用UINT8_MAX数值填充

14、mangle_Random函数:

取随机位置、随机大小的缓冲区,用随机数填充

15、mangle_CloneByte函数:

取两处随机位置的作数据交换

16、mangle_Expand函数:

文件末尾扩展随机长度的空间,用空格填充,然后在随机位置,取前面的随机长度作数据拷贝

17、mangle_Shrink函数:

删除随机长度的文件内容

18、mangle_InsertRnd函数:

在文件的随机位置插入随机长度的数据

19、mangle_ASCIIVal函数:

在随机位置覆盖32字节的随机数

总结

在Fuzzing过程中,很多变异规则是共用的,可以参考一些主源的开源软件,比如afl\peach\honggfuzz\libfuzzer,提取规则作整合,然后写个自己的fuzzing框架,在后面作针对的fuzzer时,可以直接套用。

从上面的fuzz策略可以总结出常规的变异规则:

  • 随机数据替换
  • 数据值增减
  • 已知边界值替换
  • 插入随机数据
  • 删减文件内容
  • 目录变异
  • 数据拷贝覆盖
  • ……

知识的诅咒

一、过于装逼的演讲 = 废话

无论是演讲还是写文章,你对一帮不懂安全技术的人讲溢出,讲UAF漏洞利用,那纯粹是浪费时间。

早年觉得分享就该讲一些多数人不懂的东西,尤其是那些自认为高深的技术,让大家听得云里雾里的,这样才显得自己技术牛逼。

但实际上,别人听不懂的东西,对其而言,均是废话。

当我们对一件事物过于了解之后,往往意识不到自己对它的思考有多么的抽象。

比如“辐射”一词,大家常常说手机有辐射,X线有辐射,但又有几人能解释清楚什么叫“辐射”呢?

正是我们高估了一般读者/听众对我们所处知识世界的熟悉程度,才造就了“知识的诅咒”。

二、高学历造就知识面的狭窄

鄙人深深地觉得,高中时代就是我们知识面最丰富的时代,那时候真的是上知天文,下知地理,中知英汉语,左手历史,右手政治,无所不会,无所不能,虽然也常常只是勉强及格,但回首这二三十年,知识面依然还是没广过高中啊!

之前也面试过一些博士生,有些博士几年只搞个TLS,或者只搞个android app的数据流找隐私泄露之类,这种搞学术研究并无问题,但一旦想进入企业就很难。

这里也不是鄙视博士生,只是知识的深度和广度的选择而已,但这得看你未来选择的方向而定了。

三、学医历程:从“一身病”到“百病不侵”

学医的前一两年,学到啥病就觉得自己可能就有这病,最后就是感觉自己一身病,我们一般叫这为:“学医综合征”。

到后来下临床了,见过的病多了,更多恶心的、血腥的、神经的病症都见了个遍,自己抗恶心能力提高的不是一两万点啊,而且慢慢地认识到,其实世上很多疾病都是没法治的,如果你们有认识肾内科的医生朋友,问问他们就知道啥叫“挫败感”!

慢慢地,就觉得只要不死不残的病,都不算事。

四、贩卖知识焦虑

最近关注的一批公众号,开始文末各种课程广告,学英语、学写作、学开发各类广告满天飞,利用的正是人们对知识的焦虑。

之前的文章也提到过,多数的付费知识很多系统化的学习到知识,之前也买过一些讲书的、或者一些通用技能课程,一开始觉得挺有道理的,最后细想下,其实几乎都没有任何可实践性的方法,很多讲的最后还是废话。当然,有些个别付费知识还是不错,不过我所依赖的付费知识只有书本。

五、知识学习中的套路与陷阱

以前还没接触过电脑的时候,就听说要先练五笔打字,要练五笔就得先背字根口诀:
“王旁青头戋(兼)五一, 土士二干十寸雨……”

不过最后我没背也练成了五笔,全靠实际打字练习练出来的。

熟练之后,所有文字均会变成脑海中的一个手指的动作,所有关于五笔的知识全变成一种抽象化动作,无什么字根,无什么键盘位……
所以,很多时候网上说:学习A就得先学习B不一定靠谱,只有实践出真知。

记得以前大学时打算学习数据结构与算法,网上查了下,说需要先学习离散数学,然后我就得真去图书馆翻离散数学,最后没看几页就放弃了。

这事跟别人说“学Java要先看《Java编程思想》”一样,谁会在无基础的情况下硬看得下去啊!

可见找到一种适合自己的学习途径才是最好的方法。自己学得哪本书好,就学哪本书,哪来那么多规矩。

能够为己所用,则为知识;但倘若被知识所用、所卖、所困,则为诅咒。

honggfuzz漏洞挖掘技术深究系列(2)—— Persistent Fuzzing

上篇《honggfuzz漏洞挖掘技术深究系列(1)——反馈驱动(Feedback-Driven)》讲到基于软件的代码覆盖率驱动fuzzing的方式,除了软件还有硬件方式,即基于Intel BTS (Branch Trace Store) 或Intel PT (Processor Tracing) 去计算代码覆盖率,同时要求Linux内核>=4.2,这种方式的最大好处是完全由硬件支配,无所谓软件是闭源还是开源。由于硬件环境受限,我也一直未使用过,有此条件的同学可以试下。

本篇主要讲下持久型fuzzing(Persistent Fuzzing),即fuzzing API,这种方式会更精准和高效的。

先看使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
$ cat test.c
#include <inttypes.h>
#include <testlib.h>
extern int LLVMFuzzerTestOneInput(uint8_t **buf, size_t *len);

int LLVMFuzzerTestOneInput(uint8_t *buf, size_t len) {
_FuncFromFuzzedLib_(buf, len); // 目标函数
return 0;
}
$ clang-4.0 -fsanitize-coverage=trace-pc,indirect-calls,trace-cmp fuzzedlib.c -o fuzzedlib.o
$ clang-4.0 test.c fuzzedlib.o honggfuzz/libhfuzz/libhfuzz.a -o test
$ honggfuzz -z -P -f INPUT.corpus -- ./test

这里用到几个编译选项:

  • trace-pc:追踪执行过的基本块BB,在每个edge中插入__saitizer_cov_trace_pc()函数,可定义该函数作为相应的回调处理
  • indirect-calls:在每个间接调用中添加PC追踪,与前面的trace-pc或trace-pc-guard联合使用,回调函数:__sanitizer_cov_trace_pc_indir
  • trace-cmp:追踪每个比较指令和swith语句

以trace-pc为例,测试代码如下:


用trace_pc编译:

可以看到自定义的函数被执行,输出执行过的不同pc地址,其它编译选项的用法同上。
下面是honggfuzz对各个回调函数的定义情况:

然后就是记录代码覆盖率情况并进行统计,跟驱动反馈的方式一样了。

再回头看使用示例中的LLVMFuzzerTestOneInput函数,honggfuzz是如何处理它的呢?

通过for无限循环调用目标函数进行Fuzzing,其中参数buf,即样本文件内容,len是数据长度。

最后根据发现的新路径,将相应的样本作为新样本继续fuzzing。