RSS: 优秀的个人情报来源

早些年,关注了一些技术博客,但不知道它是否更新文章,就只好偶而去翻翻看。关注的博客量少的时候,还应付得来,一旦多了,就觉得甚是费时间。

后来才发现有RSS(聚合内容)这款神器,大大地节省时间,不用再不停地翻看别人的博客,一有更新,在自己的订阅网站或app上就可以查看到。

image-20190330095736471

以前用Google Reader,现在用Inoreader(https://www.inoreader.com),还提供手机APP,特别适合利用碎片时间查看和学习。

我现在基本保持每天翻看,一定要把未读消息消灭掉。

由于多年订阅源的收集积累,每天都有不少更新的消息,即使前天刚把失效的订阅源清理掉,也还有700多个。

以前曾看到某同事的RSS订阅,上千条未读消息,平时看得少,结果越堆越多,导致未能发挥RSS应有的价值。

我觉得RSS应该是每个想保持学习进步的人应该必备的工具。

对于我个人而言,RSS有以下作用:

  1. 应急响应:及时获取外部曝光的漏洞,包括公司产品漏洞,以及可能影响公司产品的第三方通用组件/开源项目漏洞,以便能够及时响应处理;
  2. 刷CVE:及时获知某些主流软件的攻击面,或者一些漏洞挖掘技巧,然后主动尝试去挖掘。以前有不少人在乌云上曝光某一通用漏洞,就经常有一大堆去刷SRC,或者在乌云上不停地刷别人的漏洞,这种行为我觉得挺无聊的。这里刷CVE主要是指Microsoft、Apple、Adobe等一系列主流厂商的产品的0day,而不是以往乌云上这种相同漏洞在不同平台刷洞的行为。
  3. 技术与工具的收集:包括技术文章和工具的收集与学习,对于好的工具,会下载学习其源码,并应用实践;对于好的文章,会保存到印象笔记,方便以后查询复习。
  4. 安全资讯:看看一些发安全界发生哪些安全事件,比如入侵事件、facebook信息泄露事件等等,也学习下别人如何就应对此类事件。除此之外,当然也包括安全界的一些八卦。
  5. 新书资讯:专门订阅一些出版社的相关博客/官网,以便能够及时获取即将出版的新书。
  6. 其它:更多的用途靠自己去挖掘,毕竟每个人的期望的目标不一样。

对于那些未提供RSS功能,又非常不错的网站,推荐使用Feed43(https://feed43.com/feed.html?action=new)自定义规则来生成RSS,可直接导入到Inoreader,使用教程参考:《利用 Feed43,将任意网页制作成 RSS 订阅源》https://sspai.com/post/34320

最后分享一下个人收集的RSS,共731个,Inoreader免费版最多支持500个,不想付费的可以找下其它免费RSS工具,或者选择部分订阅。

下载地址:http://riusksk.me/media/riusksk_RSS_20190330.xml

image-20190330103252935

今年的OffensiveCon大会议题质量不错

年前曾在微博上推荐过OffensiveCon 2019大会议题,议题列表与介绍可参见官网(https://www.offensivecon.org/agenda/),很多专注于漏洞挖掘与利用的干货分享,目前只有部分议题ppt公开,文末附打包下载链接(包含8个议题),包括ppt、paper和code。

会议结束后,Twitter上赞声一片,议题质量很赞。

本文主要聊聊已公开的一些议题,学习下思路。

议题列表

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    1. Modern Source Fuzzing
    2. IPC You Outside the Sandbox: One bug to Rule the Chrome Broker
    3. 3D Accelerated Exploitation
    4. Bugs so Nice they Patched them Twice! A (Continuing)? Story About Failed Patches
    5. Attacking Hardware Root of Trust from UEFI Firmware
    6. OSX XPC Revisited - 3rd Party Application Flaws
    7. Growing Hypervisor 0day with Hyperseed
    8. Attacking Edge Through the JavaScript Just-In-Time compiler
    9. Coverage-Guided USB Fuzzing with Syzkaller
    10. Updated Analysis of PatchGuard on Windows RS4: Is the Mouse Finally Caught?
    11. iOS Dual Booting Demystified
    12. macOS: How to Gain Root with CVE-2018-4193 in < 10s
    13. Reverse Engineering of Error-Correcting Codes
    14. Glitch in the Matrix: Exploiting Bitcoin Hardware Wallets
    15. Attack Surface of a Connected Vehicle
    16. Bypass Windows Exploit Guard ASR
    17. FuzzIL: Guided Fuzzing for JavaScript Engines

Modern Source Fuzzing

这是作者Ned Willliamson在353c大会上的《Attack Chrome IPC》议题的扩展补充,我之前还写过《安全研究者的自我修养》一文,里面介绍的就是作者提及的二进制漏洞研究的学习思路。

目前作者没公开这次会议的ppt,大家还是看353C的演讲视频吧:https://media.ccc.de/v/35c3-9579-attacking_chrome_ipc

##IPC You Outside the Sandbox: One bug to Rule the Chrome Broker

作者已经在github上公布此漏洞的利用代码hack2win-chrome,点击”阅读原文“可下载到。

本议题讲的是Chrome沙盒逃逸漏洞,漏洞位于应用缓存(AppCache)子系统上,主要方便从本地读取数据进行离线浏览,访问速度更快,还能缓解服务器压力。

img

AppCache位于沙盒之外的高权限进程browser,沙盒内低权限的renderer进程通过发送IPC消息与browser进程交互的,当AppCache出漏洞时,就有可能逃逸出沙盒。

漏洞成因

这次的ppt写得比较模糊,没那么清楚,还是直接看patch diff:

image-20190223095905435

移动CancelUpdate()函数到newest_complete_cache_=nullptr;之后,直接看看CancelUpdate里面的逻辑:

img

在调用AppCacheGroup::RemoveCache清除缓存时,newest_complete_cache_指向的是被销毁的对象,所以后面才要把它置空,但在销毁之前调用了CancelUpdate =>

AppCacheUPdateJob::~AppCacheUpdateJob => AppCacheGroup::SetUpdateAppCacheStatus => AppCacheHost::OnupdateComplete =>

SetSwappableCache

最后的SetSwappableCache用于设置新的交换缓存(swap cache),会引用到newest_complete_cache_,而此时它还未被置NULL,导致出现Use After Free漏洞。

####漏洞利用:

  1. 【泄露地址】:使用与AppCache对象大小相同的net::CanonicalCookie对象来占用释放对象的内存,而CanonicalCookie对象开头是个cookie名称,即字符串指针,再从浏览器中读取cookie信息来达到信息泄露的目的,从而拿到可控数据的堆地址绕过ASLR。

  2. 【代码执行】:使用与AppCache对象大小相同的Blob对象对占用释放内存,再伪造AppCacheGroup对象,当它析构释放时,在~AppCacheGroup中会调用到已被填充控制的虚函数指针,再结合ROP绕过DEP,从而达到代码执行。

整个过程还是需要自己动手调试一遍才比较清楚,估计足够调上几天了,国内似乎也没有一遍完整的文章分析过该漏洞的利用细节,期待有人分享。

3D Accelerated Exploitation

image-20190223123509759

image-20190223123527727

该议题主要介绍VirsualBox 3D加速器的攻击面和漏洞利用,由于VBox是开源的,因此可以直接使用AFL 去Fuzzing,fuzz目标就是通过发送畸形chromium messages来触发漏洞。他们应该是自己写个构造发送消息的程序,输入文件即chromium messages内容,样本可能是收集550操作码的信息去构造,也可能通过hook去直接抓取真实数据作为样本,然后用 afl去跑。更具体的实现方式,作者也没细说。

MWR Labs这几年经常曝光一些Pwn2Own级别的漏洞,分享很多经典文章,还开源了不少Fuzzer工具,连ppt都做得非常工整,具有独特风格,哪怕没logo,你看一眼都能猜出是他们写的。具备牛X的技能能力,又乐分享,这点是比较难得的。

Attacking Edge Through the JavaScript Just-In-Time compiler

一直以来,chakra被曝的漏洞非常多,导致微软最终还是放弃了。

从今年开始,微软将打算把Edge的Chakra引擎改用Google Chromium引擎,估计最近这两个月就会发布,以后就可能没什么人再搞Chakra内核了。

这议题里面讲了很多chakra的js对象内存结构等基础知识,重点讲了JIT优化编译器的漏洞原理与利用技巧,整个ppt有120页,很多。

我没搞过chakra,未来可能也用不上了,有兴趣的同学可以看下,作者把exploit代码也公布了,我已附在本文的打包资料里面。

Coverage-Guided USB Fuzzing with Syzkaller

image-20190223130009940

搞过Linux/Android内核漏洞挖掘的人,应该都知道Syzkaller这款神器,发现超过2500个内核bug,它是基于代码覆盖率+API调用模板来Fuzzing内核的工具,对于发现崩溃的漏洞,还能自动生成C代码帮助复现,是由Google的Dmitry Vyukov开发的,已在Github上开源多年(https://github.com/google/syzkaller)。

这次作者用syzkaller fuzz USB驱动共发现了80+个bug,它先开启kcov去收集代码覆盖率信息,写了两个usb操作的描述模板(vusb.txt用来生成usb消息,vusb_ids.txt用于提取与USB设备驱动相匹配的USB ID列表),ppt里面有链接,所有的usb fuzzer代码都已经嵌入到syzkaller项目里面了

image-20190223130949191

整个syzkaller的使用过程就是先去寻找内核的攻击面,然后构造api调用模板,剩下交由syzkaller基于代码覆盖驱动的方式去Fuzzing,有点类似api fuzzing。只是这里作者又写了个USB内核模块,方便通过用户层发送USB消息去测试。

作者还专门搞了个树莓派来重现漏洞,演示通过USB去让Windows/Linux系统崩溃。

FuzzIL: Guided Fuzzing for JavaScript Engines

image-20190223135223721

这议题最大的亮点在于:自定义一套中间语言IL,通过IL可以翻译成JS代码,然后通过变异IL来生成JS代码,与以往基于JS语法模板生成代码的方式不同。

image-20190223135445955

直接通过一行行删除IL的方式来验证是否崩溃或产生新路径,以此用来精简样本。

整个Fuzzing过程如下:

image-20190223140416987

作者未来会在github上开源(https://github.com/googleprojectzero/fuzzilli),拭目以待。

结语

访问 https://github.com/riusksk/SecConArchive可获取议题的打包资料,除上述推荐的议题资料外,还有3个议题,包括”Bypass_Windows_Defender_ASR“、”macOS-How to Gain Root with CVE-2018-4193“,以及”OSX Privileged Helper Tool“,有兴趣的同学自行下载阅读。

image-20190223141538809

winafl中基于插桩的覆盖率反馈原理

最近winafl增加支持对Intel PT的支持的,但是只支持x64,且覆盖率计算不全,比如条件跳转等,所以它现在还是不如直接用插桩去hook的方式来得准确完整,这里主要想分析也是基于 DynamoRIO插桩的覆盖率反馈原理。

之前曾有人在《初识 Fuzzing 工具 WinAFL》(https://paper.seebug.org/323/#32)中“3.2.2 插桩模块”一节中简单分析过其插桩原理,但没有找到我想要的答案,因此只好自动动手分析下源码。

比如,我想知道:

  1. 通过循环调用fuzzing的目标函数来提高速度,但DynamoRIO的覆盖率信息是如何同步给fuzzer主进程的?

  2. 具体是如何实现寄存器环境的记录与恢复,从而实现目标函数的不断循环?

  3. 覆盖率信息是如何记录与分析的?

覆盖率信息记录与分析原理

第3个问题发现已经有人分析过afl,可以参见这里《AFL内部实现细节小记》(http://rk700.github.io/2017/12/28/afl-internals/),简单总结下:

  1. AFL在编译源码时,为每个代码生成一个随机数,代表位置地址;

  2. 在二元组中记录分支跳转的源地址与目标地址,将两者异或的结果为该分支的key,保存每个分支的执行次数,用1字节来储存;

  3. 保存分支的执行次数实际上是一张大小为64K的哈希表,位于共享内存中,方便target进程与fuzzer进程之间共享,对应的伪代码如下:

    1
    2
    3
    cur_location = <COMPILE_TIME_RANDOM>;
    shared_mem[cur_location ^ prev_location]++;
    prev_location = cur_location >> 1;
  4. fuzzer进程通过buckets哈希桶来归类这些分支执行次数,如下结构定义,左边为执行次数,右边为记录值trace_bits:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    static const u8 count_class_lookup8[256] = {
    [0] = 0,
    [1] = 1,
    [2] = 2,
    [3] = 4,
    [4 ... 7] = 8,
    [8 ... 15] = 16,
    [16 ... 31] = 32,
    [32 ... 127] = 64,
    [128 ... 255] = 128
    };
  5. 对于是否触发新路径,主要通过计算各分支的trace_bits的hash值(算法:u32 cksum **=** hash32(trace_bits, MAP_SIZE常量, HASH_CONST常量);)是否发生变化来实现的

覆盖信息的传递原理

  1. 先在fuzzer进程中先创建命名管道,其中fuzzer_id为随机值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //afl-fuzz.c
    pipe_name = (char *)alloc_printf("\\\\.\\pipe\\afl_pipe_%s", fuzzer_id);

    pipe_handle = CreateNamedPipe(
    pipe_name, // pipe name
    PIPE_ACCESS_DUPLEX | // read/write access
    FILE_FLAG_OVERLAPPED, // overlapped mode
    0,
    1, // max. instances
    512, // output buffer size
    512, // input buffer size
    20000, // client time-out
    NULL); // default security attribute
  2. 创建drrun进程去运行目标程序并Hook,在childpid_(%fuzzer_id%).txt的文件中记录子进程id,即目标进程ID,然后等待管道连接,并通过读取上述txt文件以获取目标进程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
    41
    42
    43
    //afl-fuzz.c    
    pidfile = alloc_printf("childpid_%s.txt", fuzzer_id);
    if (persist_dr_cache) {
    cmd = alloc_printf(
    "%s\\drrun.exe -pidfile %s -no_follow_children -persist -persist_dir \"%s\\drcache\" -c winafl.dll %s -fuzzer_id %s -drpersist -- %s",
    dynamorio_dir, pidfile, out_dir, client_params, fuzzer_id, target_cmd);
    } else {
    cmd = alloc_printf(
    "%s\\drrun.exe -pidfile %s -no_follow_children -c winafl.dll %s -fuzzer_id %s -- %s",
    dynamorio_dir, pidfile, client_params, fuzzer_id, target_cmd);
    }
    ......
    if(!CreateProcess(NULL, cmd, NULL, NULL, inherit_handles, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
    FATAL("CreateProcess failed, GLE=%d.\n", GetLastError());
    }
    ......
    if(!OverlappedConnectNamedPipe(pipe_handle, &pipe_overlapped)) {
    FATAL("ConnectNamedPipe failed, GLE=%d.\n", GetLastError());
    }

    watchdog_enabled = 0;

    if(drioless == 0) {
    //by the time pipe has connected the pidfile must have been created
    fp = fopen(pidfile, "rb");
    if(!fp) {
    FATAL("Error opening pidfile.txt");
    }
    fseek(fp,0,SEEK_END);
    pidsize = ftell(fp);
    fseek(fp,0,SEEK_SET);
    buf = (char *)malloc(pidsize+1);
    fread(buf, pidsize, 1, fp);
    buf[pidsize] = 0;
    fclose(fp);
    remove(pidfile);
    child_pid = atoi(buf);
    free(buf);
    ck_free(pidfile);
    }
    else {
    child_pid = pi.dwProcessId;
    }
  3. 在插桩模块winafl.dll中打开前面创建的命名管道,然后通过管道与fuzzer主进程进行交互:

    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
    //winafl.c
    static void
    setup_pipe() {
    pipe = CreateFile(
    options.pipe_name, // pipe name
    GENERIC_READ | // read and write access
    GENERIC_WRITE,
    0, // no sharing
    NULL, // default security attributes
    OPEN_EXISTING, // opens existing pipe
    0, // default attributes
    NULL); // no template file

    if (pipe == INVALID_HANDLE_VALUE) DR_ASSERT_MSG(false, "error connecting to pipe");
    }
    ......
    char ReadCommandFromPipe()
    {
    DWORD num_read;
    char result;
    ReadFile(pipe, &result, 1, &num_read, NULL);
    return result;
    }

    void WriteCommandToPipe(char cmd)
    {
    DWORD num_written;
    WriteFile(pipe, &cmd, 1, &num_written, NULL);
    }
  4. 当插桩模块winafl.dll监测到程序首次运行至目标函数入口时,pre_fuzz_handler函数会被执行,然后通过管道写入’P’命令,代表开始进入目标函数,afl-fuzz.exe进程收到命令后,会向目标进程写入管道命令’F’,并监测超时时间和循环调用次数。afl-fuzz.exe与目标进程正是通过读写管道命令来交互的,主要有’F’(退出目标函数)、’P’(进入目标函数)、’K’(超时中断进程)、’C’(崩溃)、’Q’(退出进程)。覆盖信息通过文件映射方法(内存共享)写入winafl_data.afl_area

    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
    //winafl.c
    pre_fuzz_handler(void *wrapcxt, INOUT void **user_data)
    {
    ......
    if(!options.debug_mode) {
    WriteCommandToPipe('P');
    command = ReadCommandFromPipe();

    if(command != 'F') {
    if(command == 'Q') {
    dr_exit_process(0);
    } else {
    DR_ASSERT_MSG(false, "unrecognized command received over pipe");
    }
    }
    } else {
    debug_data.pre_hanlder_called++;
    dr_fprintf(winafl_data.log, "In pre_fuzz_handler\n");
    }
    ......
    memset(winafl_data.afl_area, 0, MAP_SIZE); // 用于存储覆盖率信息

    if(options.coverage_kind == COVERAGE_EDGE || options.thread_coverage) {
    void **thread_data = (void **)drmgr_get_tls_field(drcontext, winafl_tls_field);
    thread_data[0] = 0;
    thread_data[1] = winafl_data.afl_area; //如果开启-thread_coverage选项,则会将覆盖率信息写入线程TLS中
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //winafl.c
    static void
    setup_shmem() {
    HANDLE map_file;

    map_file = OpenFileMapping(
    FILE_MAP_ALL_ACCESS, // read/write access
    FALSE, // do not inherit the name
    options.shm_name); // name of mapping object

    if (map_file == NULL) DR_ASSERT_MSG(false, "error accesing shared memory");

    winafl_data.afl_area = (unsigned char *) MapViewOfFile(map_file, // handle to map object
    FILE_MAP_ALL_ACCESS, // read/write permission
    0,
    0,
    MAP_SIZE);

    if (winafl_data.afl_area == NULL) DR_ASSERT_MSG(false, "error accesing shared memory");
    }

篡改目标函数循环调用的原理

此步的关键就在于进入目标函数前调用的pre_fuzz_handler函数,以及函数退出后调用的post_fuzz_handler函数。

进入pre_fuzz_handler函数时,winafl.dll会先获取以下信息

1
2
3
4
5
6
app_pc target_to_fuzz = drwrap_get_func(wrapcxt);	//获取目标函数地址
dr_mcontext_t *mc = drwrap_get_mcontext_ex(wrapcxt, DR_MC_ALL); //获取目标函数当前内存上下文信息
drcontext = drwrap_get_drcontext(wrapcxt); //获取DynamoRIO上下文

fuzz_target.xsp = mc->xsp; // 保存栈指针,xsp是各平台下的通用标记变量
fuzz_target.func_pc = target_to_fuzz; // 目标函数地址

其中内存上下文信息支持各平台的寄存器记录:

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
typedef struct _dr_mcontext_t {
/**
* The size of this structure. This field must be set prior to filling
* in the fields to support forward compatibility.
*/
size_t size;
/**
* The valid fields of this structure. This field must be set prior to
* filling in the fields. For input requests (dr_get_mcontext()), this
* indicates which fields should be written. Writing the multimedia fields
* frequently can incur a performance hit. For output requests
* (dr_set_mcontext() and dr_redirect_execution()), this indicates which
* fields will be copied to the actual context.
*/
dr_mcontext_flags_t flags;

#ifdef AARCHXX
reg_t r0; /**< The r0 register. */
reg_t r1; /**< The r1 register. */
reg_t r2; /**< The r2 register. */
reg_t r3; /**< The r3 register. */
reg_t r4; /**< The r4 register. */
reg_t r5; /**< The r5 register. */
reg_t r6; /**< The r6 register. */
reg_t r7; /**< The r7 register. */
reg_t r8; /**< The r8 register. */
reg_t r9; /**< The r9 register. */
reg_t r10; /**< The r10 register. */
reg_t r11; /**< The r11 register. */
reg_t r12; /**< The r12 register. */
# ifdef X64 /* 64-bit */
reg_t r13; /**< The r13 register. */
reg_t r14; /**< The r14 register. */
reg_t r15; /**< The r15 register. */
reg_t r16; /**< The r16 register. \note For 64-bit DR builds only. */
reg_t r17; /**< The r17 register. \note For 64-bit DR builds only. */
reg_t r18; /**< The r18 register. \note For 64-bit DR builds only. */
reg_t r19; /**< The r19 register. \note For 64-bit DR builds only. */
reg_t r20; /**< The r20 register. \note For 64-bit DR builds only. */
reg_t r21; /**< The r21 register. \note For 64-bit DR builds only. */
reg_t r22; /**< The r22 register. \note For 64-bit DR builds only. */
reg_t r23; /**< The r23 register. \note For 64-bit DR builds only. */
reg_t r24; /**< The r24 register. \note For 64-bit DR builds only. */
reg_t r25; /**< The r25 register. \note For 64-bit DR builds only. */
reg_t r26; /**< The r26 register. \note For 64-bit DR builds only. */
reg_t r27; /**< The r27 register. \note For 64-bit DR builds only. */
reg_t r28; /**< The r28 register. \note For 64-bit DR builds only. */
reg_t r29; /**< The r29 register. \note For 64-bit DR builds only. */
union {
reg_t r30; /**< The r30 register. \note For 64-bit DR builds only. */
reg_t lr; /**< The link register. */
}; /**< The anonymous union of alternative names for r30/lr register. */
union {
reg_t r31; /**< The r31 register. \note For 64-bit DR builds only. */
reg_t sp; /**< The stack pointer register. */
reg_t xsp; /**< The platform-independent name for the stack pointer register. */
}; /**< The anonymous union of alternative names for r31/sp register. */
/**
* The program counter.
* \note This field is not always set or read by all API routines.
*/
byte *pc;
union {
uint xflags; /**< The platform-independent name for condition flags. */
struct {
uint nzcv; /**< Condition flags (status register). */
uint fpcr; /**< Floating-Point Control Register. */
uint fpsr; /**< Floating-Point Status Register. */
}; /**< AArch64 flag registers. */
}; /**< The anonymous union of alternative names for flag registers. */
# else /* 32-bit */
union {
reg_t r13; /**< The r13 register. */
reg_t sp; /**< The stack pointer register.*/
reg_t xsp; /**< The platform-independent name for the stack pointer register. */
}; /**< The anonymous union of alternative names for r13/sp register. */
union {
reg_t r14; /**< The r14 register. */
reg_t lr; /**< The link register. */
}; /**< The anonymous union of alternative names for r14/lr register. */
/**
* The anonymous union of alternative names for r15/pc register.
* \note This field is not always set or read by all API routines.
*/
union {
reg_t r15; /**< The r15 register. */
byte *pc; /**< The program counter. */
};
union {
uint xflags; /**< The platform-independent name for full APSR register. */
uint apsr; /**< The application program status registers in AArch32. */
uint cpsr; /**< The current program status registers in AArch32. */
}; /**< The anonymous union of alternative names for apsr/cpsr register. */
# endif /* 64/32-bit */
/**
* The SIMD registers. We would probably be ok if we did not preserve the
* callee-saved registers (q4-q7 == d8-d15) but to be safe we preserve them
* all. We do not need anything more than word alignment for OP_vldm/OP_vstm,
* and dr_simd_t has no fields larger than 32 bits, so we have no padding.
*/
dr_simd_t simd[NUM_SIMD_SLOTS];
#else /* X86 */
union {
reg_t xdi; /**< The platform-independent name for full rdi/edi register. */
reg_t IF_X64_ELSE(rdi, edi); /**< The platform-dependent name for
rdi/edi register. */
}; /**< The anonymous union of alternative names for rdi/edi register. */
union {
reg_t xsi; /**< The platform-independent name for full rsi/esi register. */
reg_t IF_X64_ELSE(rsi, esi); /**< The platform-dependent name for
rsi/esi register. */
}; /**< The anonymous union of alternative names for rsi/esi register. */
union {
reg_t xbp; /**< The platform-independent name for full rbp/ebp register. */
reg_t IF_X64_ELSE(rbp, ebp); /**< The platform-dependent name for
rbp/ebp register. */
}; /**< The anonymous union of alternative names for rbp/ebp register. */
union {
reg_t xsp; /**< The platform-independent name for full rsp/esp register. */
reg_t IF_X64_ELSE(rsp, esp); /**< The platform-dependent name for
rsp/esp register. */
}; /**< The anonymous union of alternative names for rsp/esp register. */
union {
reg_t xbx; /**< The platform-independent name for full rbx/ebx register. */
reg_t IF_X64_ELSE(rbx, ebx); /**< The platform-dependent name for
rbx/ebx register. */
}; /**< The anonymous union of alternative names for rbx/ebx register. */
union {
reg_t xdx; /**< The platform-independent name for full rdx/edx register. */
reg_t IF_X64_ELSE(rdx, edx); /**< The platform-dependent name for
rdx/edx register. */
}; /**< The anonymous union of alternative names for rdx/edx register. */
union {
reg_t xcx; /**< The platform-independent name for full rcx/ecx register. */
reg_t IF_X64_ELSE(rcx, ecx); /**< The platform-dependent name for
rcx/ecx register. */
}; /**< The anonymous union of alternative names for rcx/ecx register. */
union {
reg_t xax; /**< The platform-independent name for full rax/eax register. */
reg_t IF_X64_ELSE(rax, eax); /**< The platform-dependent name for
rax/eax register. */
}; /**< The anonymous union of alternative names for rax/eax register. */
# ifdef X64
reg_t r8; /**< The r8 register. \note For 64-bit DR builds only. */
reg_t r9; /**< The r9 register. \note For 64-bit DR builds only. */
reg_t r10; /**< The r10 register. \note For 64-bit DR builds only. */
reg_t r11; /**< The r11 register. \note For 64-bit DR builds only. */
reg_t r12; /**< The r12 register. \note For 64-bit DR builds only. */
reg_t r13; /**< The r13 register. \note For 64-bit DR builds only. */
reg_t r14; /**< The r14 register. \note For 64-bit DR builds only. */
reg_t r15; /**< The r15 register. \note For 64-bit DR builds only. */
# endif
union {
reg_t xflags; /**< The platform-independent name for
full rflags/eflags register. */
reg_t IF_X64_ELSE(rflags, eflags); /**< The platform-dependent name for
rflags/eflags register. */
}; /**< The anonymous union of alternative names for rflags/eflags register. */
/**
* Anonymous union of alternative names for the program counter /
* instruction pointer (eip/rip). \note This field is not always set or
* read by all API routines.
*/
union {
byte *xip; /**< The platform-independent name for full rip/eip register. */
byte *pc; /**< The platform-independent alt name for full rip/eip register. */
byte *IF_X64_ELSE(rip, eip); /**< The platform-dependent name for
rip/eip register. */
};
byte padding[PRE_XMM_PADDING]; /**< The padding to get ymm field 32-byte aligned. */
/**
* The SSE registers xmm0-xmm5 (-xmm15 on Linux) are volatile
* (caller-saved) for 64-bit and WOW64, and are actually zeroed out on
* Windows system calls. These fields are ignored for 32-bit processes
* that are not WOW64, or if the underlying processor does not support
* SSE. Use dr_mcontext_xmm_fields_valid() to determine whether the
* fields are valid.
*
* When the fields are valid, on processors with AVX enabled (i.e.,
* proc_has_feature(FEATURE_AVX) returns true), these fields will
* contain the full ymm register values; otherwise, the top 128
* bits of each slot will be undefined.
*/
dr_ymm_t ymm[NUM_SIMD_SLOTS];
#endif /* ARM/X86 */
} dr_mcontext_t;

接下来就是获取和设置fuzzed的目标函数参数:

1
2
3
4
5
6
7
8
9
10
//save or restore arguments
if (!options.no_loop) {
if (fuzz_target.iteration == 0) {
for (i = 0; i < options.num_fuz_args; i++)
options.func_args[i] = drwrap_get_arg(wrapcxt, i); //首次运行先获取运行参数
} else {
for (i = 0; i < options.num_fuz_args; i++)
drwrap_set_arg(wrapcxt, i, options.func_args[i]); //设置运行参数
}
}

当目标函数退出后,执行post_fuzz_handler函数,会恢复栈顶指针和pc地址,以此实现目标函数的循环调用:

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
static void
post_fuzz_handler(void *wrapcxt, void *user_data)
{
dr_mcontext_t *mc;
mc = drwrap_get_mcontext(wrapcxt); //获取上下文信息

if(!options.debug_mode) {
WriteCommandToPipe('K');
} else {
debug_data.post_handler_called++;
dr_fprintf(winafl_data.log, "In post_fuzz_handler\n");
}

/* We don't need to reload context in case of network-based fuzzing. */
if (options.no_loop)
return; //网络型Fuzzing无需重载上下文信息

//超过循环次数就退出进程
fuzz_target.iteration++;
if(fuzz_target.iteration == options.fuzz_iterations) {
dr_exit_process(0);
}

mc->xsp = fuzz_target.xsp; //恢复栈顶指针
mc->pc = fuzz_target.func_pc; //篡改pc地址加原目标函数地址
drwrap_redirect_execution(wrapcxt); //篡改执行流
}

总结

总结下整个winafl执行流程:

  1. afl-fuzz.exe通过创建命名管道与内存映射来实现与目标进程交互,其中管道用来发送和接收命令相互操作对方进程,内存映射主要用来记录覆盖率信息;
  2. 覆盖率记录主要通过drmgr_register_bb_instrumentation_event去设置BB执行的回调函数,通过instrument_bb_coverage或者instrument_edge_coverage来记录覆盖率情况,如果发现新的执行路径,就将样本放入队列目录中,用于后续文件变异,以提高代码覆盖率;
  3. 目标进程执行到目标函数后,会调用pre_fuzz_handler来存储上下文信息,包括寄存器和运行参数;
  4. 目标函数退出后,会调用post_fuzz_handler函数,记录恢复上下文信息,以执行回原目标函数,又回到第2步;
  5. 目录函数运行次数达到指定循环调用次数时,会中断进程退出。

聊聊那些黑客小说

系统宕,
资料泄,
挂马黑站何时了?
安全知多少!
告警昨夜又响起,
往事不堪回首月明中。
漏洞应犹在,
只是域名改。
问君能有几多愁?
恰似行行代码错误留。

黑客小说:培养对信息安全的兴趣

之前在《漏洞战争》的前言里面有提到,鄙人初入象牙塔之时,曾看过多本黑客小说,包括《黑客传说》、《地狱黑客》(后改名为《禁区之门》,参考凯文·米特尼克事迹写的,后来又出了第2部,但好像没写完)、《指间的黑客》等等,后来也因此对安全产生兴趣,入了这行道。

但,道归道,兴趣归兴趣!

因为曾有多少人,来了兴趣多年,却未曾自学过。

“我对安全很感兴趣,求师傅教教我!”这种老套路的提问,大家见得还少吗?

正如许多人宁愿被动受苦,也不愿主动吃苦学习一样,这个问题在知乎上也曾被热烈讨论过。被动受苦大多不用多动脑,久而久之,身体也会慢慢地适应,也就逐渐被生活、被制度所驯化。

不过,此处想说的重点是,黑客小说可以培养人们对信息安全的兴趣,而学习的过程本身就是孤独单调的,如有兴趣相伴,则更易独行久远!

行业人写小说:有始无终,多半夭折

安全圈里面其实也有一些人写过黑客小说,不乏某些知名人士,但基本上都是开了个头,却没多久就夭折收场。反正,我是没见过有人写完出版的。

于是,我上Google搜索了下起点网,通过一些安全专业名词进行搜索,找到几部相关小说:

  “第一节操作系统原理、第二节系统及命令详解、第三节溢出漏洞原理、第四节web原理、第五节http协议、tcp/ip协议第六节sql注入原理、上传漏洞、XSS、CSRF……”

​ ——《别说我是黑客》

  过了十多分钟,jsky的界面左侧刷出来许多asp文件和几个目录。右栏则是标注着绿色和红色叹号的几列英文。
  看到扫描结果出来,杨风面带微笑进行下一步操作。他的运气不错,得到了xss与sqlinjection漏洞,这是网站暴露出来的跨站脚本漏洞和sql注入漏洞。
  杨风转到育民高中主页,随手选取了一个注入地址,提交上引号。
  返回错误。
  接着提交and1=1语句。
  返回正常。
  继续提交and1=2语句。
  返回错误。
  杨风做这两步,主要是测试网站程序设计者是否在其中过滤了关键字,如果过滤就不能被注入。

​ ——《黑客记事本》

​ “尊敬的孙诚先生,请允许我对你和你的团队–塞伯坦工作室致以敬意。我们的检测人员已经验证了你所寄过来的数据,并证实了ios漏洞的存在,在对你们表示感谢的同时,Zerodium会按照约定,向你的团队支付共计五万四千美元的报酬,并希望你们能尽快将后续数据补完。在邮件中,你们提到找到了ios的两处极为隐秘的高危漏洞,Zerodium上下都对此非常感兴趣。希望能够尽快收到你的回复,报酬方面请不用担心,Zerodium是一个非常有信誉的平台!”

​ ——《从变形金刚开始》

“我明白了,灵根这个电源,就相当于是有着固定编码的反编译器,每个灵根都有一个固定编码,就是所谓的灵根属性。灵根可以把跟自己编码一样的灵气团,进行逆向反编译,把编译好的灵气团分解成单纯由0和1构成的信息流,就是所谓的灵力!”

​ ——《程序员修真之路》

从教你用jsky黑站(PS:为啥不推荐wvs呢),到黑客穿越、程序员修仙等,各种奇思妙想,在网络小说领域也算是另类的存在。

但是从这些专业名词看,作者即使不是安全圈的,至少也是IT技术行业的,对一些常见的安全事件、技术名词都比较了解。

文学与IT技术本身就是两个不同的领域,要同时兼顾就有一定难度。如果算程序员里面写的小说,比较火的,应该是那本《疯狂的程序员》吧!

懵懂之美:似懂非懂才是最大的乐趣

我已经很久没看黑客小说,尤其是入了行之后,就更不看了。

如果要推荐的话,还是开头提到的那3本小说:《黑客传说》、《禁区之门》、《指间的黑客》,不过那是我大学时的口味了,现在重新看也不一定就如当初那般喜欢。

因为当你对安全行业熟悉后,再去看这些小说的时候,可能就容易较真,少了些许乐趣。

我当初看上面的小说的时候,其实也还没入门安全,所以看得特别起劲。

所以,如果要看这类小说,就把那些行业知识都抛诸脑后,享受那当初的懵懂之美,才是读小说的乐趣所在。

后话

也许是性格使然,在文字世界里,鄙人老喜欢委婉地批判人和事(俗称:骂人)。不过文学世界里,一千个读者就有一千个哈姆雷特,应该支持下这种多元文化。不然就像娱乐圈里,谁演孙悟空,都要被六小龄童骂一般,少了胸怀,甚至阻碍了行业发展。

安全研究者的自我修养(续)

接上篇继续聊安全研究者的自我修养,上篇重点讲技术修炼,本篇聊聊行业现象、谈谈沉淀、情怀等等。

11、工具与方法论沉淀

虽说代码审计是项必备技能,但终究是项体力活。

有些漏洞(比如逻辑漏洞)可能就需要人工审计,但也有不少漏洞是可以自动化Fuzzing,一些能自动化或半自动化实现的,尽量写程序自动化。

因为,纯人工审计终究熬不过年纪,熬不过团队人员的离散变迁,熬不过互联网的快速发展……

比如,2012年刚开始写《漏洞战争》时,单身一人,从早上8点多起床吃饭,然后开始调代码、看代码,一直奋战到晚上12点,身体无压力。近7年过去了,现在要是这么折腾,身体就要散架了……

比如,团队里的人分工做不同领域的代码审计,若无工具和方法论沉淀,那么有人走的话,此人对应的领域可能就无法持续产出;若有新人加入,代码审计的技能又不好传承,很多得自己重头来。所以,一直觉得,好的团队应该是,即使人员离散变迁,依然能够独立运作、持续产出的。

比如,Linux内核在2018年净增87万行代码,很多类似复杂庞大的项目,看代码有时看都看不过来,一般都是针对性地挑模块作代码审计。

比如,Fuzzer开发里面就有很多共用功能是可以直接做成框架沉淀下来,文件变异、崩溃监控、样本去重精简等等,很多时候有个新的攻击面需要测试,就可以直接在框架的基础上写fuzzer,将会高效很多。下文提到的一个IE漏洞挖掘案例就是基于这思路挖到的。

我曾经想开发两个漏洞挖掘系统,一个二进制,一个Web,名字都想好了,合称”冰弓玄箭“,但业余一直都没什么时间开发,仅写了个界面,希望2019年能够完成:

image-20190112143451515

”冰弓“的Logo直接用的是“破甲弓”,感觉很酷……

再说说方法论,这词虽有点虚,但其实本质上就是一种技术方法的总结而已。

比如,渗透测试的时候,总有些人每次都能搞到RCE,无论啥网站,完全摆脱“随机挖洞”的命运。多数情况下,他们都会有一套自己测试方法,或者将一些经验转换成工具,测试时就拿自己的工具和以往总结的方法论开搞。

比如,STRIDE威胁建模本身就是一套方法论,一套简单的风险助记符,当然我这里不是说安全研究要用它,只是举个方法论的例子,它也没有那么万能。

写这么多,总结起来就一句话:多总结,多沉淀!

12、漏洞研究风向标:安全公告

如果大家有关注四大厂商(Google、Microsoft、Apple、Adobe)的安全公告的话,会发现有段时间会出现很多类似漏洞的公告,出现一个新的攻击面之后,一帮研究人员就蜂捅而上狂刷一波。

这种情况一向是先下手为强,而上文提到的工具和方法论就更显得尤为重要了,否则最后都只能捡剩的。

比如本周 Microsoft 安全公告出来后,我仔细分析了下,然后下班回家写了个Fuzzer,挂着跑了一天,出来个Crash,再用几分钟成功构造出PoC,实现IE浏览器的远程代码执行,可见也是个品相极佳的神洞:

image-20190112153428430

但不幸的是,我打了1月的补丁后,发现修复了,成功“撞洞”,真的是欲哭无泪……

但至少证明,通过安全公告寻找新的攻击面,然后挖掘一些类似漏洞,一直是一种高效的漏洞研究方式。

13、老一辈研究者都去哪儿了?

img

最近腾讯AILab张潼离职的事传得很火,还有之前各大厂聘请的AI科学家陆续辞职,回归学术界,很多人因此唱起科学家之于科技公司的无用论,主要有以下几点原因:

  1. 研究成果无法落地为产品:做安全研究也是如此,很多事情是无法落地的,圈内很多研究团队都是拿漏洞来打比赛赚影响力,真正能实现为公司营利的(打比赛赚奖金的忽略不计,因为那些都不够给研究者们的工资),我只知道有1个研究团队/实验室今年营利了。
  2. 长期无产出,KPI压力大:研究了很长时间,最后仍一无所获,那KPI咋办、PPT怎么写、晋级怎么答辩。安全行业有句老话来形容安全研究工作,叫“三年不开锅,开锅吃三年”,但多数个人和企业都等不到三年。之前同事说王小云为何能破解出MD5,是因为她在学校里很长时间没搞出东西的时候,领导没找她麻烦,没有KPI压力,以致能够长期专注于此。具体原因我不确定,但学术界自然是没有企业有这般KPI压力。
  3. 业务数据不共享:业务部门的产品数据基本不太可能共享给实验室作研究的,一般都是实验室以SDK的形式提供给业务用,数据由业务自主控制。这种情况对于安全研究的影响相对较少一些。

头两点是多数安全研究者的困境,也跟圈内同行讨论过,下面聊聊这帮老一代“知青”最后都去哪儿了?这里我主要总结一些圈内人的应对方法(其实多数都是转型),具体不作点评,总结为主,也欢迎私信讨论(新注册的公众号已不允许留言)。

  1. 坚持研究:这帮人主要还是那些研究能力较强的,且有一定研究成果的人,围观下各大实验室就知道个大概,不多说;
  2. 转型安全产品开发与运营:有产品就能解决落地问题,帮助企业解决实际问题,有不少人走这条道,去做威胁情报系统、漏洞扫描器、WAF、云安全产品等等;
  3. 转型业务安全:跟研究工作差异较大,因为业务安全的主要问题很多时候并非漏洞,而是跟业务产品相关的黑灰产对抗等等;
  4. 自由研究者:国外很多此类研究者,靠拿漏洞赏金过活,俗称“赏金猎人”,国内相对少一些,也有一些国内自由研究者后来又进企业做研究的,这里讲的几种转型都可以来回转换,有些人就干过。
  5. 创业:这里包括安全行业内的创业,也包括那些开淘宝店、奶茶店、服装生意、卖水果的……

14、个人终究干不过团队

有时想搞的研究太多了,但发现一个人根本搞不过来,需要多人协作才可能完成。但需要多人在研究领域上有交集,否则拉在一块也是各搞各的。

前篇第7点讲到“进入研究者团队或社区,互相学习”,也是一大影响因素,互相学习也是一种提高效率和产出的方式。

算了,不多说了!

后话

这次真的结束了,没有续篇了。

思考了很多,总结了很多,有些也是写了删,删了写。

安全研究领域一直也没人写过这些,出来唠叨几句,也欢迎大家私信讨论。

最后奉一首酒桌上的《苦行僧》结束本话题,听过这首歌很多个版本,包括原唱,但终究还是觉得视频里这位老哥唱得更具江湖气、更具情感、更具感染力……旁边一老哥听着听着都偷偷抹泪了!

之所以点这首歌,是因为:每一个研究者都是独立自行的苦行僧!

安全研究者的自我修养

在上篇文章《推荐今年C3黑客大会上的几个议题》中提到”Attacking Chrome IPC“这个议题,我觉得该议题最大的亮点是在前半场,作者nedwill是之前在hack2win大赛上因攻破Chrome浏览器而一战成名,他讲了如何训练漏洞研究能力的过程,讲述自己这几年在漏洞研究上的历程和心得,很励志,其建议也非常具有可操作性,值得效仿学习。我反复看了多遍,对其作了一些总结和补充。

1、刻意练习10000小时

这份“鸡汤”道理,想必大家都懂,就不解释了,不懂的自行百度,或者去读读《异类》这本经典书籍。

作者建议以月为单位来制定研究目标,他曾连续花了6个月的时间来研究Chrome Sandbox,但最终一无所获。

所以,有时坚持了不一定能达到目标,但不坚持,就更没戏了。

2、训练挖洞的双技能

(1)看洞:哪里看?历史漏洞的git log、bug报告、代码质量报告等等

(2)识洞:就是肉眼看代码找漏洞,即代码审计,难点也就是在这上面,训练方法继续往下看

3、代码审计训练

(1)根据自己目标定位,寻找相应的历史漏洞案例进行学习,比如要搞chrome就找chrome的历史漏洞

(2)掌握漏洞所在的模块或子系统,但不看完整的漏洞细节描述,尝试在漏洞版本中找出对应的漏洞

(3)如果(2)中未能找出漏洞,就去看漏洞细节描述,对比自己的审计过程,看遗漏了哪一步骤

(4)不断重复上述训练,直至相信:挖洞只是体力消耗,而非能力问题

这第4点说得,非常励志,因为挖洞挖久了,有时真的容易怀疑自己的能力,目标难度越大,越容易打击人。

作者第一次训练的漏洞是j00ru(Project Zero成员)的IDA漏洞:https://j00ru.vexillium.org/2014/10/secure-2014-slide-deck-and-hex-rays-ida-pro-advisories-published/,2014年的文章了

4、3~5年的训练计划

1~2年:做做 CTF 或 WarGames 题目,网上有很多CTF writeup可以参考学习

2~3年:简单点的目标,就是找相对容易挖的产品

3~5年:困难点的目标

目标的难易程度可以直接参考相应的产品的漏洞奖励计划或私有市场的价格,挑选出一份目标清单,按难易程度排序,逐一去实现它。

5、Fuzzing训练

作者代码审计2年后,才开始尝试Fuzzer开发。

(1)拿已公开的历史漏洞问自己:如何写fuzzer挖掘到此漏洞?

(2)如果自己不知道此漏洞,那又能够挖掘到呢?

(3)不断重复训练并改进fuzzer,相信会有更多漏洞被意外发现

6、努力往往比运气和天赋更重要

虽然挖洞也需要一定运气和天赋,但多数你认为的挖洞天才,其实只不过是花了比你多100倍,甚至更多的时间在这项技术研究上而已

7、进入研究者团队或社区,互相学习

国外的交流氛围会比国内的更好一些,也更愿意分享。

很多时候自己的交流圈,大多是一些熟识的同行,或者同事,一般可交流的人还是比较少的。

经常在网上看到不少人会问,如何认识xx大牛、黑客,但其实很多时候却是:

努力提高自己的专业能力,圈子最终会吸纳你进去认识更多圈内人。

8、建立自己的漏洞信息来源

RSS订阅无疑是自己最好的方式,这个需要依赖平时自己去不断收集订阅。

很多漏洞相关的博文,往往曝露出某些软件新的攻击面,抢占先机就显得尤为重要,比如当年Android stagefirght mp4漏洞、word公式编辑器、adobe图片转换器等等,如果能及时关注并尝试去挖掘,往往可以收获不少漏洞的。

9、收集和学习开源的漏洞挖掘工具

比如afl、honggfuzz、libfuzzer等很多优秀的漏洞挖掘工具,都是值得好好阅读代码,学习其中的fuzzing思路,可以更好地应用到未来的漏洞挖掘研究上。

10、很多不愿搞研究工作的挖洞人,只不过是为了权衡利弊

在《从0到1:开启商业与未来的秘密》一书中有一章叫做“秘密”,漏洞研究可以当作挖掘秘密,为什么人们不探索秘密呢?书中提到4种原因,我觉得同样适用于漏洞研究领域:

(1)渐进主义:把目标定得低一些,更容易取得好成绩;

(2)风险规避:人们害怕秘密是因为怕犯错,除此之外,可能也担心KPI没法完成,又或者挖洞拿到的奖金又该如何跟公司“分赃”呢?

(3)自满:很多时候,某些人可以坐享其成,又何必自己去挖掘秘密;国内研究氛围又喜欢搞营销吹牛逼,牛逼吹多了吹大了,有时连自己都信了;

(4)扁平化:任何一个拥有雄心壮志的人,在涉及某一研究领域之前都会问自己一个问题:如果有可能挖掘到漏洞,难道全球人才库中更加聪明、更加有技术能力的人还没有发现吗?这种怀疑的声音阻止了不少人去探索秘密,从事研究工作,因为身处的世界似乎大到任何个人都无法做出独特的贡献。

结语

今年因个人原因,已从安全研究转向业务安全,深知研究的不易。

相信安全领域有秘密的存在,虽会导致黑产的诞生,但肯定也会因此诞生一些优秀的研究者。

最后以白桦的《船》致谢所有仍在安全研究道路上前进的人:

我有过多次这样的奇遇,
从天堂到地狱只在瞬息之间:
每一朵可爱、温柔的浪花
都成了突然崛起、随即倾倒的高山。

每一滴海水都变脸色,
刚刚还是那样的美丽、蔚蓝;
旋涡纠缠着旋涡,
我被抛向高空又投进深渊……

当时我甚至想到过轻生,
眼前一片苦海无边;
放弃了希望就像放弃了舵柄,
在暴力之下只能沉默和哀叹。

今天我才有资格嘲笑昨天的自己,
为昨天落叶似的惶恐感到羞惭;
虚度了多少年华,
船身多次被礁石撞穿……

千万次在大洋里撒网,
才捕获到一点点生活的经验,
才恍然大悟,
啊!道理原是如此浅显;

你要航行吗
必然会有千妖百怪出来阻拦;
暴虐的欺凌是它们的游戏,
制造灭亡是它们唯一的才干。

命中注定我要常常和它们相逢,
因为我的名字叫做船;
面对强大于自身千万倍的对手,
能援救自己的只有清醒和勇敢。

恐惧只能使自己盲目,
盲目只能夸大魔鬼的狰狞嘴脸;
也许我的样子比它们更可怕,
当我以命相拼,一往无前!

只要我还有一根完整的龙骨,
绝不驶进避风的港湾;
把生命放在征途上,
让勇敢来决定道路的宽窄、长短。

我完完全全的自由了,
船头成为埋葬它们的铁铲;
我在波浪中有节奏地跳跃,
就像荡着一个巨大的秋千。

即使它们终于把我撕碎,
变成一些残破的木片,
我不会沉沦,决不!
我还会在浪尖上飞旋。

后来者还会在残片上认出我,
未来的诗人会唱然长叹:
“这里有一个幸福的灵魂,
它曾经是一艘前进着的航船……”

推荐今年C3黑客大会上的几个议题

最近几天在德国举办的 The 35th Chaos Communication Congress (35C3) 黑客大会,在Twitter上传得火热,在国内却无人问津。

从这可以看出同为微博的安全圈氛围是完全不同的,新浪微博还是偏娱乐些的,而且国外的圈子自然比天朝的要大得多,很多国家的人在上面交流。

所以,我现在经常混Twitter,当作获取安全资讯的途径,新浪微博就真的当作看新闻的了……

德国C3混沌黑客大会

Chaos Communication Congress(C3)大会是每年在德国举办的黑客大会,直译过来就是”混沌通信大会“,圈内通常叫”C3“,今年是第35届,所以叫35c3,今年还有CTF比赛,一些打过pwn2own的人出了一些浏览器实际漏洞的题目,也蛮具有实战价值的。

每年这大会都会以演讲视频的方式分享出各个议题,以前大多是聚焦在无线电安全,所以一些什么2G\3G\4G短信、电话窃听经常出自该会议。今年也有一些不错的软件安全相关的议题,下面推荐几个鄙人自认为不错的几个议题。

From Zero to Zero Day

image-20181230193447667

大会上有个议题叫 “From Zero to Zero Day”,演讲者是位高中生,讲述他自己如何在一年之内,从无任何安全基础到挖掘到第一枚Edge浏览器远程执行代码漏洞的经历。

总结来讲就是:

1、学习编程语言(C/C++、asm等等)

2、学习操作系统原理

3、学习常见二进制漏洞原理

4、打打CTF,写写write-up

5、学习并实践去分析真实的漏洞案例,就是直接看代码,调代码

6、不断重复上述练习

我直接帖几张关键截图,也推荐大家去听听(视频链接:https://media.ccc.de/v/35c3-9657-from_zero_to_zero_day):

image-20181230192405638

image-20181230192425929

image-20181230192131880

image-20181230192320182

image-20181230192254473

Attack Chrome IPC

image-20181230193950157

这个议题之前作者在韩国PoC大会上讲过,并在网上公布过pdf(https://data.hackinn.com/ppt/2018韩国POC安全大会/ned.pdf),主要讲他之前在Hack2Win bevx黑客大赛上用于黑掉Chrome浏览器的IPC漏洞,此次C3大会的演讲视频参见:https://media.ccc.de/v/35c3-9579-attacking_chrome_ipc

这议题里面讲了作者一些研究二进制漏洞的一些学习和研究的方法,比如做代码审计、打CTF,也介绍了他这几年的安全研究历程,比国内流行晒crash+CVE的方式更接地气。

最后介绍如何使用 libfuzzer+libprotobuf-mutator去fuzz chrome IPC,并开源fuzzer代码合并到chroium项目中。

image-20181230194648886

Jailbreaking iOS From past to present

image-20181230202552499

讲iOS越狱发展史的,可以学习到iOS上各种安全机制的原理以及绕过方法,画了不少原理图,通俗易懂不少。对于想了解整个iOS越狱技术发展历程的同学,这确实是份不错的资料。

作者这次也在推持上放出了pdf和视频:

pdf: https://api.tihmstar.net/35c3slides.pdf

video: https://media.ccc.de/v/35c3-9618-jailbreaking_ios

整个议题主要围绕以下几点展开:

  • 越狱类型(非完美越狱、完美越狱……)
  • Exploit mitigations (ASLR, iBoot-level AES, KPP, KTRR, PAC)
  • Kernel patches (h3lix)
  • Kppless jailbreaks
  • 越狱的未来趋势

The Layman’s Guide to Zero-Day Engineering

image-20181230211151217

Ret2公司分享如何寻找webkit攻击面并fuzz的方法,是基于MozillaSecurity的dharma语法生成框架写的js fuzzer,以及如何借助IDA+Lighthouse开源插件(正是Ret2团队开发的,曾获得过IDA插件比赛的二等奖)来分析代码覆盖率问题。

除此之外,也介绍如何利用frida去hook mach_msg,用来fuzz WindowServer,最后用WindowServer的漏洞来实现root提权。

他们在其博客(https://blog.ret2.io)上也分享过不少漏洞研究方面的干货,这次分享的内容也大部分就是来自博客上的一些文章。

里面还提到长亭的real world ctf比赛了。

这里有张比较有意思的图,发出来给大伙看下:

image-20181230213304029

视频链接:https://media.ccc.de/v/35c3-9979-the_layman_s_guide_to_zero-day_engineering

Modern Windows Userspace Exploitation

image-20181230214817405

视频链接:https://media.ccc.de/v/35c3-9660-modern_windows_userspace_exploitation

微软MSRC的人过来分享下windows平台下的一些二进制漏洞利用方法,包括ROP绕过DEP、信息泄露绕过ASLR、绕过CFG、ACG、CIG等等,并现场给了一些演示,不过看起来像是一些CTF赛题,演示的相关代码已放在作者的github上:https://github.com/saaramar

都是一些利用系统漏洞防御技术的原理与绕过技术的总结,连各种漏洞缓解机制绕过的微软奖励都给大家标注上了,其实有点类似上面Jailbreaking iOS议题的Windows版本。

最后来张Exploit Mitigations清单:

image-20181230215938958

结语

后面官方可能还会继续更新演讲视频,可以关注官方twitter(@c3voc_releases)获取消息。

另外,对于英语不好的同学,不妨下个”腾讯翻译“同声翻译试下,虽然中文翻译没那么准确,但看看显示的英文单词也能知道个大意:

image-20181230195201468

2018年读过的书

从2018年开始一直坚持每月读2本书以上,庆幸自己坚持下来了,共读了38本书,也希望明年能够继续坚持。

有些是实体书,有些是在微信读书上看的电子版,在手机app上看书确实会高效很多,今年有一半的书是在微信读书上看的,非常适合空闲时间阅读,以及像坐地铁、等地铁这种零碎的时间。

下面是我今年读过的书单,分别都打个分数,8分及以上的代表推荐,6分以下的别看:

  1. 《人性的弱点》(6分)
  2. 《代码整洁之道》(8分)
  3. 《如何阅读一本书》(8分)
  4. 《两晋南北朝那些事》(7分)
  5. 《软技能:代码之外的生存指南》(8分)
  6. 《秋叶:如何高效读懂一本书》(6分)
  7. 《横向领导力》(7分)
  8. 《程序员成长课》(8分)
  9. 《英语写作手册》(8分)
  10. 《爆款文案》(7分)
  11. 《运营之光》(8分)
  12. 《威胁建模》(7分)
  13. 《程序员的英语》(7分)
  14. 《冰鉴全鉴》(2分)
  15. 《内向者沟通圣经》(4分)
  16. 《灰度决策》(4分)
  17. 《启功行书技法》(8分)
  18. 《见识》(9分)
  19. 《半小时漫画中国史1、2》(7分)
  20. 《Web安全之机器学习入门》(6分)
  21. 《秦崩》(8分)
  22. 《启功给你讲书法》(8分)
  23. 《思考,快与慢》(10分)
  24. 《三国志》(8分)
  25. 《季羡林谈写作》(6分)
  26. 《Android应用安全防护与逆向分析》(7分)
  27. 《精进:如何成为一个很厉害的人》(8分)
  28. 《非暴力沟通》(9分)
  29. 《系统架构设计》(7分)
  30. 《人人都是产品经理》(8分)
  31. 《漏洞》(7分)
  32. 《风格感觉:21世纪写作指南》(8分)
  33. 《态度》(8分)
  34. 《高效阅读》(5分)
  35. 《写给大家看的设计书》(10分)
  36. 《硅谷钢铁侠:埃隆·马斯克的冒险人生》(8分)
  37. 《八卦医学史2》(6分)
  38. 《从0到1:开启商业与未来的秘密》(7分)

所以8分以上的推荐书籍有:

《代码整洁之道》(8分)

《如何阅读一本书》(8分)

《软技能:代码之外的生存指南》(8分)

《程序员成长课》(8分)

《英语写作手册》(8分)

《运营之光》(8分)

《启功行书技法》(8分)

《见识》(9分)

《秦崩》(8分)

《启功给你讲书法》(8分)

《思考,快与慢》(10分)

《三国志》(8分)

《精进:如何成为一个很厉害的人》(8分)

《非暴力沟通》(9分)

《人人都是产品经理》(8分)

《风格感觉:21世纪写作指南》(8分)

《态度》(8分)

《写给大家看的设计书》(10分)

《硅谷钢铁侠:埃隆·马斯克的冒险人生》(8分)

Bochspwn漏洞挖掘技术深究(2):内核未初始化漏洞检测

本文主要介绍Bochspwn Reloaded内核未初始化漏洞检测技术,它采用污点追踪对内核层向用户层泄露数据的行为进行检测。

关于bochs插桩技术参考《Bochspwn漏洞挖掘技术深究(1):Double Fetches 检测》,此处不再赘述。

直接先看下instrument.h中实现插桩函数有哪些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Bochs初始化CPU对象时的回调函数
void bx_instr_initialize(unsigned cpu);
// Bochs析构CPU对象时的回调函数
void bx_instr_exit(unsigned cpu);
//Bochs每次执行中断操作(软件中断、硬件中断或异常)时的回调函数
void bx_instr_interrupt(unsigned cpu, unsigned vector);
// Bochs执行指令前的回调函数
void bx_instr_before_execution(unsigned cpu, bxInstruction_c *i);
// Bochs执行指令后的回调函数
void bx_instr_after_execution(unsigned cpu, bxInstruction_c *i);
// Bochs访问线性内存时的回调函数
void bx_instr_lin_access(unsigned cpu, bx_address lin, bx_address phy,
unsigned len, unsigned memtype, unsigned rw);
// WRMSR指令(写模式定义寄存器)被执行时的回调函数,MSR寄存器数与值作为参数传递给回调函数
void bx_instr_wrmsr(unsigned cpu, unsigned addr, Bit64u value);

初始化工作

第一篇中讲过bx_instr_initialize主要用来加载配置信息,针对不同的系统环境设置不同的数据结构偏移地址,用来提供需要的进程/线程等重要信息。在这里它另外增加污点追踪功能的初始化工作:

1
2
3
4
5
6
7
8
9
// Initialize the taint subsystem.
taint::initialize();

// Initialize helper taint allocations.
globals::pool_taint_alloc = (uint8_t *)malloc(kTaintHelperAllocSize);
memset(globals::pool_taint_alloc, kPoolTaintByte, kTaintHelperAllocSize);

globals::stack_taint_alloc = (uint8_t *)malloc(kTaintHelperAllocSize);
memset(globals::stack_taint_alloc, kStackTaintByte, kTaintHelperAllocSize);

主要作一些用于污点信息记录的内存结构分配与VEH异常处理回调设置:

1
2
3
4
5
6
7
8
void initialize() {
// Reserve a memory region for the taint data.
taint_area = (uint8_t *)VirtualAlloc(NULL, kTaintAreaSize, MEM_RESERVE, PAGE_READWRITE);

// Register a VEH handler to commit taint memory touched in other taint
// functions.
AddVectoredExceptionHandler(/*FirstHandler=*/1, OvercommitHandler);
}

VEH回调函数实现如下,当发生访问违例时,若异常地址不在污点内存区域,则将其设置为可读写内存,然后继续执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static LONG CALLBACK OvercommitHandler(
_In_ PEXCEPTION_POINTERS ExceptionInfo
) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
const uint8_t *excp_address = (uint8_t *)ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
if (excp_address >= taint_area && excp_address < &taint_area[kTaintAreaSize]) {
if (VirtualAlloc((void *)((uint64_t)excp_address & (~0xffff)), 0x10000, MEM_COMMIT, PAGE_READWRITE)) {
return EXCEPTION_CONTINUE_EXECUTION;
}
}
}

return EXCEPTION_CONTINUE_SEARCH;
}

中断响应

再看下bx_instr_interrupt函数实现,主要是发生中断时,检测该中断地址是否可写,并设置全局标志:

1
2
3
4
5
6
7
8
void bx_instr_interrupt(unsigned cpu, unsigned vector) {
if (globals::bp_active && vector == 3) {
BX_CPU_C *pcpu = BX_CPU(cpu);
write_lin_mem(pcpu, globals::bp_address, 1, &globals::bp_orig_byte);

globals::bp_active = false;
}
}

污点标记与追踪

bochspwn-reloaded会对内核分配的stack/heap/pools作污点标记:

image-20181222092741926

1、栈污点标记

检测修改ESP寄存器的指令,比如:ADD ESP, ... SUB ESP, ... AND ESP, …,若在执行后(bx_instr_after_execution)ESP发生递减,则调用taint::set_taint(new_rsp, length, /*tainted=*/true)标记为污点

1
2
3
4
5
6
7
8
9
10
void bx_instr_before_execution(unsigned cpu, bxInstruction_c *i) {
...
const unsigned int opcode = i->getIaOpcode();
switch (opcode) {
case BX_IA_SUB_EqId:
case BX_IA_SUB_GqEq: /* Stack allocation handling */
...
case BX_IA_PUSH_Eq: /* Allocator prologue handling. */
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void bx_instr_after_execution(unsigned cpu, bxInstruction_c *i) {
globals::rep_movs = false;

if (globals::rsp_change) {
BX_CPU_C *pcpu = BX_CPU(cpu);
const uint64_t new_rsp = pcpu->gen_reg[BX_64BIT_REG_RSP].rrx;

if (new_rsp < globals::rsp_value) {
uint64_t length = globals::rsp_value - new_rsp;

if (length <= kTaintHelperAllocSize) {
taint::set_taint(new_rsp, length, /*tainted=*/true);
write_lin_mem(pcpu, new_rsp, length, (void *)globals::stack_taint_alloc);

if (globals::config.track_origins) {
taint::set_origin(new_rsp, length, pcpu->prev_rip);
}
}
}

globals::rsp_change = false;
globals::rsp_value = 0;
}
}

2、堆/Pools污点标记

检测内核内存分配操作的指令,则调用taint::set_taint(address, size, /*tainted=*/true)进行污点标记,主要通过bx_instr_wrmsr函数来实现,当写入的地址是MSR_LSTAR寄存器时,它代表着syscall调用:

1
#define MSR_LSTAR	0xc0000082 /* long mode SYSCALL target */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void bx_instr_wrmsr(unsigned cpu, unsigned addr, Bit64u value) {
if (addr == MSR_LSTAR) {
globals::nt_base = value - globals::config.KiSystemCall64_offset; // ntoskrnl.exe中nt!KiSystemCall64偏移地址,用于获取内核基址

for (size_t i = 0; i < globals::config.pool_alloc_prologues.size(); i++) {
globals::config.pool_alloc_prologues[i] += globals::nt_base;
}
set_breakpoints_bulk(globals::config.pool_alloc_prologues, BP_POOL_ALLOC_PROLOGUE);

for (size_t i = 0; i < globals::config.pool_alloc_epilogues.size(); i++) {
globals::config.pool_alloc_epilogues[i] += globals::nt_base;
}
set_breakpoints_bulk(globals::config.pool_alloc_epilogues, BP_POOL_ALLOC_EPILOGUE);
}
}

其中pool_alloc_prologuespool_alloc_epilogues分别代表alloc函数的前序与后序函数,以下是windows-x64系统配置下的地址:

1
2
pool_alloc_prologues  = 0x1E0590
pool_alloc_epilogues = 0x1E07AD

3、污点清除
当栈顶弹出或者堆块调用free函数前序指令(Linux下配置地址),以及内存拷贝的目标地址是内核地址时,均将其污点标记清除,如果是win平台则主要依靠bx_instr_lin_access来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
const uint64_t pc = pcpu->prev_rip;

if (rw != BX_WRITE && rw != BX_RW) {
return;
}

if (!pcpu->long_mode() || !windows::check_kernel_addr(pc) || !windows::check_kernel_addr(lin)) {
return;
}

if (globals::rep_movs) {
return;
}

const uint64_t rsp = pcpu->gen_reg[BX_64BIT_REG_RSP].rrx;
if (globals::rsp_locked.find(rsp) != globals::rsp_locked.end()) {
return;
}

taint::set_taint(lin, len, /*tainted=*/false);
}

4、污点传播
bx_instr_before_execution中主要对以下操作指令作检测,指令形式主要为 <REP> MOVS{B,D},用于污点传播追踪:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const unsigned int opcode = i->getIaOpcode();
switch (opcode) {
case BX_IA_MOV_GqEq: /* Standard library memcpy() prologue handling. */
...
case BX_IA_REP_MOVSB_YbXb:
case BX_IA_REP_MOVSW_YwXw:
case BX_IA_REP_MOVSD_YdXd:
case BX_IA_REP_MOVSQ_YqXq: /* Inline memcpy handling */
...
switch (opcode) {
case BX_IA_REP_MOVSB_YbXb: mult = 1; break;
case BX_IA_REP_MOVSW_YwXw: mult = 2; break;
case BX_IA_REP_MOVSD_YdXd: mult = 4; break;
case BX_IA_REP_MOVSQ_YqXq: mult = 8; break;
}
...

case BX_IA_RET_Op64: /* Allocator and memcpy() epilogue handling. */
...

对于非<REP> MOVS{B,D}指令的内存访问:

  • 写操作:清除内存污点标记,标记为已初始化;
  • 读操作:检测污点标记,如果shadow memory中标记为未初始化读取,则在guest memory中验证:标记不匹配则清除污点,否则若真为未初始化读取就当漏洞报告出来
1
2
3
4
5
6
7
8
9
10
/* src_in_kernel */ {
uint64_t tainted_offset = 0;
taint::access_type type = taint::check_taint(pcpu, src, size, &tainted_offset);

if (type == taint::METADATA_MARKER_MISMATCH) {
taint::set_taint(src, size, /*tainted=*/false);
} else if (type == taint::ACCESS_INVALID) {
process_bug_candidate(
pcpu, i, pcpu->prev_rip, src, size, dst, taint::get_origin(src + tainted_offset));
}

总结起来,是否为漏洞主要基于以下几点:

1、<REP> MOVS{B,D}中 源地址为内核,目标地址为用户地址,从内核输出数据到用户

2、源地址被标记为污点

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)