哈喽 大家好很高兴在这里能够跟大家一起分享一下我们在京东做的一些研究成果首先做个自我介绍我叫行星宇是上边的那个 斗号前面的那个 第一个人还有我的一个我们一起做这个研究工作的机密苏他可能也在现场吧或者说他就赶飞机了就可能我们是来自京东 美国的硅谷研究院然后我们在主要做的研究是二进制分析在这个演讲当中我们主要想跟大家分享的是怎么去用自动化的方法去更好的去分析这个软件的漏洞更好的去找到软件漏洞当中的这些根本原因我相信在做的每一位可能都写过程序当然也有可能大家对安全比较感兴趣但从来没有写过程序的我相信可能在做的也有那么我相信如果你写过程序你一定遇到过很多各种各样的问题比如说不管你花了多少的时间即使是在你上学的时候老师给你留了一个作业说今天回去写一个简单的小程序我相信你一定有debug的这样的情怀你不可能这个软件写一次它就一定会work多数情况下是你可能会写了很多很多次你会发现这个bug是不停地存在然后你会想办法去debug它然后最后让这个软件真正的work那么对于一个小程序如此那么对于一个大的程序大家可以想想像office这样的程序甚至对于操作系统这样的程序这个bug是不可避免的而这些bug为了消除这些bug当然各个公司各个企业软件开发人员往往一般都会花了很多的人力和物力在发布软件之前做各种各样的测试最终的目的就是希望在发布以后这个软件尽可能地运行得更加的稳定可是非常不幸的是不论你花了多少的力量花费了多少的时间花费了多少的钱那么最终你的软件都必不可少的会包含这样或那样的问题而当这些问题一旦被触发的时候它往往会带来很严重的后果比如说最简单的情况下当你一个bug被触发了以后我们的软件往往就不在工作了这就会导致用户体验变得非常差那么除此之外这不算一个很大的问题对于很多人来说大不了关机重启或者说把这个软件关掉再重新开一遍你会发现它可能就work的正常了如果你认为这不是个问题那我可以告诉你我相信在制作的各位都是对安全非常感兴趣的问题所以当有一些bug甚至可能会被黑客所利用这样的话能实现具体的工具如果这一种bug那可能对你的危害就比较大了那么在现实世界当中为了去分析bug那么有很多很多的方法现在已经被提了出来比如说最简单的办法就是大家可能听说当找到一个bug的时候如果有用windows的话它可能会跳个窗说现在你可以把你这个crush这个软件的崩溃出错的情况发还给windows 发还为微软微软可以在那边做分析或者说你会看到有这种连接的寻找方案等等那么但是很不幸的是就是即使你把这样的bug发还到最终的争端那么很多种情况下分析这个bug往往也会发很长很长的时间因为简简单的一个crush往往不能给你太多的信息所以在今天我将跟大家分享的是在历史上大家究竟怎样去做一个debug在公司里边怎么样去做debug以及现在现有的最好的方法是怎么样实现debug这样的工作的那么我想这些slice刚才我好像已经讲过了我们可以直接跳过那么为了能帮助软件的debug现在的双法主要有两种最常见的一种情况就称之为叫require replay所谓require replay就是说当你发现了一个bug以后sorry 是在你运行软件的同时你不断地去monitor去跟踪整个软件的执行过程把执行过的每一条指令内存的每一次变化都记录下来那么你想一想你把每一个时间程序所发生的事件都记录下来当程序出错的时候你一个最简单的办法就是你可以重放刚才的每一个动作这样的话你就可以更好的更快地去找到这样的软件所存在的根本问题但是这样的一个写约方案最大的问题在于你会付出很多的运算代价因为在你程序运行的过程当中你monitor每一条指令的执行记录下任何的很多的指令记录下每一个内存的操作这样的话你会想象你的运行时间可能是一秒钟可能有几万条指令已经运行过去了而如果想记录一个10分钟在过去10分钟所进行的这种状况的话你可能并没有足够的硬盘甚至你并没有足够的资源来去完成这件事所以Record Replay虽然说它很好它很美但是在现实世界当中并没有人真正地去使用它所以真的这样的问题绝大多数的企业包括应该说绝大多数的企业甚至在很多实验室环境下这样的Record Replay方法一般都不被使用那么除了Record Replay另外的一种方法也是传统的方法来实现debug这种称之为Log的分析方法所以Log的分析方法假如说我的程序突然崩溃了突然出错了那么我就把当前出错的状态下的内存做一个快照把现在当前的计存器的值也做一个快照然后把这些所有的信息发送给软件开发人员让他们进行整段这样的一个好处是你可以非常清晰地知道当程序在出现问题的时候当前的内存状态处于什么样的一种情况那么有了这样的信息往往可以帮助你更好的去完成debug但是这也带来了一个问题因为在这种情况下你只有一个当前的内存状态你并没有整个程序在过去一段时间里的运行状态所以你的信息会非常非常的少那么信息非常少带来的问题就是你在debug的时候你要做很多的推理做很多的猜测甚至有可能你会推理错误或者猜测错误导致你很难找到软件出错的根本原因所以你看到在这张表当中我们总结了像require replay这种方法它很好但是它的缺点就在于它的效率并不高那么对于这种普通的log的方法比如说这种基于core dump的分析方法那么它是轻量级的因为你不再需要去monit这个程序了超级总可以帮你很快地去把core dump给收集起来但是它的不好的地方就在于它的效率它的有效性可能会非常的差那么为了解决这样的问题为了解决这样问题我们在过去在京东这个美颜我们在过去的三年里就做了很多这样的工作我们的目标就是一个就是希望能够更快更好的去帮助我们的开发人员更快地去分析出一个软件出错的根本原因从2016年开始一直到2019年这是我们在京东美颜这边所完成的一些工作那么今天想跟大家分享的是其中的第二个工作和第三个工作简单来说我们的第一个工作一般就是要把一个程序的原代码作为输入然后同时再把这个core dump就是在程序出错的时候它的内存状态作为输入然后我们进行各种各样的分析然后去自动化地找到root cost这种方法今天我们就不在这里跟大家做工作分享了跟大家分享第二种和第三种那么它的好处是什么呢对于针对我们第二种的方法是说当程序在崩溃的时候我们可以快速地拿到这样的一个core dump同时我们还可以利用现有的硬件的一些特性能够收集一些程序的一些执行的trace待会我会给大家看那么然后再通过逆向执行我们去找到这个程序的出错点那待会我会给大家演示究竟这个工具是怎么work的那么我们每个工具起了名字叫pump但是在我后面的演讲当中我也可以告诉大家为什么这样的方法呢有的时候在我们企业当中在使用的过程当中非常的困难而在最近我们又怎么样去使用AI的技术去提升了这样的分析的手段好 在我讲到如何去做分析的之前我想给大家介绍一些背景知识所谓的做程序分析刚才我已经提到了当程序崩溃的时候往往操作系统我们去做一个非常的快照做一个快照这快照里往往存在有的是一个程序的内存状态同时也有当前的计存器的一些状态那么这个东西的总和称之为core dump所以基本上现在所有的操作系统都可以提供这样的能力只要程序出错都可以拿到一个在出错点时候我们所能看到的程序的内存状态以及它的计存器当中的值那么我刚才也提到了一个core dump里面的信息是非常非常有限的它仅仅是有部分信息因为你只知道程序在crush在出现问题的时候那一点的内存状态你并不知道在这之前任何一个点的内存信息所以说它的信息是非常非常有限的那么仅仅使用core dump去进行分析实际上是非常非常困难的但是在最近的intel出了一款新的这种硬件特性是应该从i7处理器以后所有intel的处理器都提供了这种processor trace的功能processor trace的是什么东西呢就是当你程序在执行的过程当中CPU可以把你过去执行一段时间里的所有的指令每一条指令都用一个最小以最小的代价把它记录下来然后当程序出错的时候它可以把之前执行的每一条指令包括当前的在程序出错时的内存状态把它一同都放在core dump里面那么这样做有一个好处什么好处呢就是除了你知道程序在出错的时候它的内存状态以外呢你还可以很快速的了解到在程序出错之前所执行的指令都包括哪些那么有了这样的指令包括程序出错的程序出错时的内存状态的话那么对于你的分析来说就变得相对简单请注意我的措辞我说的是相对简单并不是说变得非常非常简单一会我们会看到原因是什么所以在我们的工作当中我们采取的方法就是我们希望使用core dump也就是内存的快照以及pt所留给我们的执行的每一条指令的序列能快速的找到程序的出错点那么在如下的后面我们的演讲过程当中我将跟大家一步一步来分享我们是怎么来实现这个工作的那么在我开始之前我们来先看一段简单的程序这个程序非常非常的简单那么这个程序你会发现它crush它会在第19行出错那19行出错如果当把这个程序出错的程序交给你让你去做一个诊断的话你怎么来诊断呢你会发现r是一个指针这个指针是通过funk3就是函数3传递进来的那么我们就可以一步一步的看到说3是被谁调用的呢是被funk2调用的在funk2里这个指针r被复职在funk2当中然后如果我们再进一步的去分析我们可以很容易的去分析到一步一步往上走我们会找到funk1当中实际上这个r是从第8行传递过来的所以这个是一个non-pointer dereference那么你会发现我们在刚才的这个第8过程当中我们将每一个语句逆向的一步一步一步的往后走那这实际上给了我们一个ideal是什么呢就是如果当我们拿到一个程序的时候程序出错点的内存状态和它之前所执行的每一条指令的时候我们的第一件事就可以先把这个指令序列拿出来然后针对crush点就是出错点这条指令开始一条一条地倒着去执行它然后在这个过程当中我们可以恢复它的数据流然后根据这个数据流我们一步一步地去往回走直到找到真正程序出错的具体的那一条指令那么刚才我提到了逆向执行那么大家会想到说既然逆向执行看起来很简单你已经有了所有的指令的序列我们就把每一条指令倒着都执行一遍这似乎确实是这样的比方说大家看到这里有一些简单的例子比如说add eax-10它实际上进行的操作就是在一个基存器eax-10那么像逆向这条指令最简单的办法就是你把eax-10这条指令的逆作用你就马上就能找到了同样的对于sab eax-216你可以逆向它的时候你就直接把eax-216就可以了d可以eax-1你也可以把eax-1increase eax-1你就相应做eax-1那看起来会非常非常简单我们可以每一步都可以把每一条指令逆过来进行操作就可以了但事实上并不是这样因为有很多很多指令它是不可逆的比方说像这个对于xorecx它做的工作就是把eax-10清零那么一旦这条指令结束以后你没有任何的办法假如说仅仅看这条指令你没有任何的办法能知道在这条指令执行之前eax-10究竟等于多少因为这条指令不可逆同样对于上面那个例子move eax-5实际上这条指令也是不可逆的你并不知道在这之前你把这个5的这个值散给eax在这之前eax-10究竟等于多少你也没有办法对这条指令做逆向那么我们怎么办呢有一个好的办法那么就是我们可以通过数据留分析去针对刚才那种情况我们可以去猜每一个指令在执行之前它的具体的内存状态是什么来 我们看这样一个简单例子这里我们有5条指令非常简单的5条指令然后假如说这个程序这5条指令是在执行到第5条指令的时候它这个程序crush了crush的时候我们有一个quadump我们有个对内存有个快照我们这快照长成像我们右手边的表这样它的值都在里边那么内存快照除了有内存快照以外我们还有对应的计存器的值那么有了这样的一个quadump有了这样一个矩正序列我们怎样一步一步往回走呢首先我们来看到第5因为这是crush点所以我们要执行的是从最后一条往上走那么在这里我们知道EAX值我们也知道EBX值在执行完这条指令以后它的值都是在这个表当中都显示出来了那么如果通过数据留分析我们现在目标是想知道EBX值在执行第5条指令之前EBX它究竟等于多少那么一个很好的办法就是通过分析你会发现EBX实际上在第1条指令进行了复制第1条指令以外EBX进行了复制在第5条指令的时候EBX又被再次复制了所以我们可以很快地推出EBX在执行第5条指令之前它应该等于EDX也就是说EBX和EDX一定是在执行之前它一定是相等的所以这个很好办针对这条指令我们就可以把EDX的值拿出来复给EBX这样我们把整个的Program Counter PC往上挪这样的话我就恢复了程序在上一个状态的值看似得很简单然后我们继续往下现在我们在想做的是把第4条指令进行一个简单的逆向我们可以用相同的办法我们想知道ECX值在执行第4条指令的时候究竟是多少呢那么我们可以看到如果在座各位比较熟悉的话你可能会知道好像我看到在第二条指令的时候ECX被复职了它的复职是从EDI这个计存器所指向的内存里拿了一个数把这个数复给了ECX这个ECX一直可以传播到这个第4条指令所以你可能会很快的下一个结论说在执行第4条指令之前ECX实际上应该等于EDI这个计存器指向的内存里的那个数值那么如果你这样认为的话你就可以把这个EDI它是现在的它是45你就可以找到对应的45这个内存你把这个22这个值取出来你把这个值复给ECX你可以这样做这样做有问题吗这样做看似好像没有任何问题但是我想告诉大家的话是这样做实际上就出错了为什么呢我们还来看一下这个例子在执行之前在我们可以把假定ECX值就等于EDI所指向的那个内存值的时候我们做了一个假设我们的假设是说在第3条指令当中的ECX所指向的那个内存地址和EDI所指向的内存地址两个内存地址应该不是指向同样的一块内存也就是说这两个内存这两个指针并不是同名指针如果他们不是同名指针当然我们刚才可以大胆的说在第4条指令执行之前ECX值应该等于EDI所指向的内存值但是如果他们两个要是同名指针的话这个情况就完完全全相反了那么我们来看一看为什么是这样那么如果EDI等于ECX也就是说在执行L4之前EDI和ECX他们是同名指针那么我们就可以推出ECX应该跟EDI相等那么由此我们通过数据留分析的话我们就肯定很清楚的知道第二条指令的负给的ECX值是没有办法直接传播到第4条指令的所以我们应该把45这个值付给ECX而不是应该把EDI所指向的内存的22付给ECX我不知道大家能不能明白我的意思如果EDI等于ECX那么应该ECX值应该从EDI那边把这个值拿水就是45而不是22那么在现实世界当中我们怎么能知道究竟刚才我们看到这个例子我们怎么能知道究竟EDI和ECX究竟是不是同名指针呢这件事情实际上是非常难知道的一件事情那么为了解决这样的问题我们采取的手段是做了一个假设测试所谓假设测试其实很简单就是我们首先做两个假设当我们遇到两个内存访问当我们不确定他们是不是同名指针的时候是不是指向同样的内存地址的时候我们首先下两个假设第一个假设是我们假设两个内存引用不是同名地址另外一个我们假设它是同名地址然后根据我们在我们这两个假设条件下我们再去做进行数据流分析然后去推断通过数据流分析我们拿出对应的这种限制级然后通过限制级我们再去判断究竟哪一个假设是真假设是假那么我们来通过刚才这个例子来具体的来说明我们究竟怎么来完成这件事情首先第一件事情还是这条这个例子首先我们假设这个EDI和ECX它是同名指针那么另外一个假设我们假设他们不是同名指针那么有了这个假设以后我们可以把U-StefanChain给它构建出来因为这是具体的指令集合所以根对第一条指令我们可以把U-StefanChain和define通过这两个box把它描述出来那么根对第二条指令我们也可以通过这三个蓝色的box把它描述出来紧接着同样对于第四条指令第五条指令我们都可以把这样一个非常简单的把U-StefanChain给它定义出来有了这样一个U-StefanChain以后我们下一步要做的就是在U-StefanChain上我们可以进行数据流的传播那么因为我们第一个假设假定的是这两个内存访位ECX和EDI所指向的内存不是同名指认所以我们就知道这个blockEDI的use一直可以传播到U-StefanChain的结尾于是有此我们可以得到这样一个集合也就是说我们可以知道在第四条指令的时候ECX应该不等于EDIECX应该等于EDI所指向的内存里的数值那么ECX所指向的内存应该等于ESI我们可以得到这样一个constraint的集合那么针对第二个假定第二个假设里我们说EDI和ECX他们是同名指认那么如果是同名指认很显然这个蓝色的box是不能传播到整个这条链的结尾的那么由此我们也可以得到另外的一个集合我们可以知道在第四条指令执行之前ECX应该等于EDIECX也应该等于EDI所指向的内存地址那么ECX所指向的内存应该等于基存器ESI的值好,现在我们有了两个不一样的constraint的集合或者是叫限制条件的集合这两个集合分别基于两种不一样的假设第一种假设是说两个内存那个圆圈指向那两个内存他们不是同名指认而第二个假设是说两个内存他们是同名指认而跟对这两个不一样的假设条件我们得到了两组不一样的constraint的集合好,有了这两组集合以后下一步我们就要来进行测试了我们第一个测试首先我们来测试一下第一个假设我们测试非常简单那就是我们来验证一下首先我们看到ECX等于EDI所指向的内存值那么当前我们看到EDI所指向的内存应该是45这个值所指向的内存然后我们把值可以拿出来它等于22我们把22恢复给ECX也就是说ECX此时此刻应该等于22没有问题接着往下第二条ECX不应该等于EDI我们看看这个时候ECX等于22这个EDI现在应该等于45所以他们俩不相等也没有问题也是满足的然后我们再来看第三个条件第三条件是说ECX所指向的内存应该等于ESI那ECX所指向的内存现在是22那么在22这个位置它的值是18那么ESI现在的值是22所以我们可以很快地知道这组假设当中这个条件是不满足的所以我们可以很快地把这组假设把它拒绝掉那么这组假设既然是错的我想我们也不用验证第二组了因为一组是错的那另外一组肯定是对的所以我们可以通过这样的一个方法很快地知道刚才的这个两个内存EDI和ECX实际上在执行第四条指令之前他们两个是投明指针那么通过这样的方法我们可以知道这件事那么有了这样的一个能力的以后我们就可以很快地在刚才这个例子当中我们就可以进行逆向的执行把这个每一条指令都执行一遍这样同时把内存状态也恢复出来这样的一个好处是什么呢我们有了内存状态有了每一条指令在执行之前和之后的内存状态在debug的过程当中我们完完全全可以用一个自动化的工具现有的自动化工具完完全全直接就可以找到这个软件的这个root cost也就是说如果当一个程序崩溃了你可以直接找到它的根本原因它崩溃的根本原因是非常容易找到的那么刚才我们这个假设测试看起来非常的好但实际上它还有很多很多的问题那这里呢可以跟大家分享一下主要的问题在哪里那么第一个问题呢就是我刚才也提到了我们为了得到那些所有的指令序列之前执行的所有的指令序列我们采用的是interpt的这样的一个硬件特性而interptby default的默认是不会记录系统调用当中所执行的那些指令所以你可以想象那么一个用户台的程序它是会有系统调用它会跟操行之间是有交互的当这种情况出现的时候我们没有办法by default默认情况下我们是没有办法去记录下操的系统当时所执行的每一条指令所以这是它的一个缺陷那么第二个缺陷是当我们做数据流分析的时候这是第一个缺陷第二个缺陷是刚才我们的算法是做了假设分析那么大家可以想这样的一个例子如果说当我要判断内存1和内存2是不是同名指针的时候但是想判断它的前提是我必须要知道内存3和内存4是不是同名指针那么你可以想象一下如果在判断内存3和内存4是不是同名指针的时候我们又要判断内存5内存6是不是同名指针的话你可以想象刚才我们这个算法可能会是指数级的增长比如说举这样一个例子当我们要判断1和2是不是同名指针的时候我们可能要判断3和4是先判断3和4是不是同名指针然后当判断完了以后我们再要去在另一边再去继续执行你可以想象如果我们有很多对这样的内存访问都需要去做同名指针的检查的话那么这个算法的复杂度应该是N大O N的M四方那么如果这M等于2可能还好N的二次方可能还好但如果这个M等于5等于6等于100这可能都非常难处理了这样一个算法基本在工业界当中就没有办法使用了所以我们在京东美研我们就强制的要求这个M一定要等于2那么我可以强制让M等于2但M等于2是可以用的是这个算法还是合理的但是问题就带来你有可能没有办法再正确地去找到对应的那个程序真正终止的那个根本原因了那我们来看一下是不是真的这样呢我们做了一个简单的试验这是我们在京东美研收集的一些CVE这CVE当中大家可以看到这个字可能有点小了但是跟大家简单summarize一下绝大多数的CVE可能是sec overflow还有一些heap overflow有一些整形溢出有一些non pointerde reference还有use after free和invalid free我们用这31个CVE这个对应的CVE去触发那个有问题的软件然后得到对应的那个crushed dump我们折到那个crushed dump以后再用我们刚才提到的这些方法用我们的工具再去做诊断那么我们可以先跳过这个我们想给大家看一下我们的结果那么这张表里列出了一些有意思的东西第一个呢你可以看到的是说因为我们目标是做逆向执行然后我们找到那些导致这个crushed的根本原因的每一条指令所以通过我们的方法我们想看一看我们有没有over tint因为我们是backover tint去找到真正的rule cost我们想看看我们究竟over tint有多少你会看到在这张表当中我们确实有一些over tint也就是说去找到一些额外的指令但是这个over tint对我们的影响实际上非常小那么绝大多数情况下都是比如说Quantus是需要tent到6条指令那么我们的工具会tent到29条指令29条指令实际上是非常少的一个数字了因为比起真正的所有的trace在这边最左边这边这个数字你可能原来要分析3000多条指令才能找到rule cost那么通过我们这种方法你可能只需要去看29条指令你就可以把rule cost找到那么所以整体上来说这个效果还是可以的那我们再来看第二个我们第二个是这个我讲过了分析的数量不需要太多第三个观察是说当这个出现的时候我们究竟能不能找到真正的rule cost究竟真正导致这个crush那条指令究竟能不能被我找出来但我们看到31个CVE里边只有两个CVE我们是没有办法成功地找到rule cost的那我们来看一看原因是什么对于第一个来说它的原因就是刚才我已经提到了由于interpt在跟踪程序执行的过程当中它是不会去跟踪系统调用当中的所有的指令由于这些指令的丢失所以导致我们在有一些case上是没有办法成功的所以这个CVE2014832这个具体的CVE我们是没有办法处理的我们再来看下一个下一个最后下的这个CVE20062971这个也是我们没办法处理的这是一个整形意出整形意出我们不能处理一个主要原因是这样的是因为整形意出它会导致进入到一个循环它会不断地执行这条循环当中所有的这些指令那么你可以想象interpt它是把所有的指令都会存在一个环形的缓存区里边当这个缓存区满了它就会重写那么真正的当有一个loop的时候这个loop里边这个循环里边所有的指令都会包含在这个缓存区里也就是根本导致这个程序出错的根本原因的那条指令根本不在缓存区当中所以这也导致我们失败的一个原因但是整体来看31个 只有两个不能做所以整体来说还是不错的我们把我们这个工具在我们做完了以后就把它开圆了开圆了以后微软有一套工具他们也看到了我们他们开发团队也看到这套工具以后他们回去试了一下同时他们也问了我们一个问题大家看到在这张表当中当我们用我们的工具来处理这样的一个这没有CVE号但是它是一个Stack Overflow来处理这样的一个具体的例子的时候为了找到我们这个工具花了六个小时的时间微软拿回去以后跑我们这个工具跑到他们各种各样的夸食当中他也发现很多情况下我们这个工具可能要跑出两天才能真正找到一个真正的RootCos把这个软件出色的原因找到于是微软就跟我们有一个交流有一个接触他告诉他问我们说你这个工具为什么需要这么长的时间这在工业界当中是不能接受的因为在数据中心当中尤其是IBM他们包括微软他们的Data Center里面这个软件出色的情况是非常常见每天可能都有上千个这样的软件出色的情况那么用我们这个工具你可以想一想每天都有上千个处理一个如果平均需要六个小时的话基本上你需要很多很多的机器同时进行计算如果处理一个需要两天的话你可能有很多的处理都来不及分析所以这个工具在工业产品当中当我们一旦推出虽然在我们那一部测试还不错当我们把它推向到真正工业产品的时候我们发现它实际上并没有真正解决问题最主要的原因就是它的效率太低了那么后来我们就想到了这个问题以后我们就想把这个问题解决掉所以在今年的年初当然从去年的年底开始一直到今天的年初我们就想解决这个问题当然我们采取的手段是使用了AI的技术在这里不给大家做太多的介绍如果感兴趣的话我们可以在这个talk结束以后大家可以坐在一起我们可以再继续的探讨我只是给大家介绍一些简单的high-level idea那么在左边是指令序列那么在很多情况下当我们做逆向执行的时候像我刚才提到的我们很多情况下我们想知道两个内存之间它是不是同名对吧我们想做同名指认的分析那么很多情况下我们都要做刚才那种非常的computation cost非常高的那样的hyposit test就是假设测试那么时间花费的非常的多那如果我能够通过AI技术能够瞬间的判断比方说我通过GI技术我先把内存进行分区分块比如说我把它分成不一样的内存区块然后我用AI技术去预测用一个神经网络去预测一条指令当中涉及的内存访问究竟属于哪一个具体的区块的话如果深度学识能完成这样的工作的话那么我就可以做一个非常我就不再需要去进行检验假设了为什么呢因为你可以看到如果说ESP加0XEC这条指令的访问是属于第一区块而第二条指令EBP加C这条指令的访问是第三区块的话我们不需要去做检验假设我们就可以很你们也知道这两个指令并不是同名这两个指针并不是同名指针因为它们指向不同的内存区域那么有了这方面的能力如果有这样的能力的话那么我们很多检验假设就不用再做了所以我们和采取的技术就是首先我们讲内存进行分块然后我们用一个深度神经网络是一个recurrent neural network然后对这个指令进行学习然后把它map到这样的话我们用一个深度神经网络在执行每一条指令之前我们都可以对每一条指令所访问的内存区域做一个预判那么有了这样的能力的话我们就不再需要进行具体的这种检验假设了那么在这里我不想讨论更多的细节针对AI的细节但是我想跟大家分享一下有了我们这一个工具以后before after在之前有AI技术和没有AI技术之前它的运算的能力究竟有什么样的一个体现我这里有一个demo我希望能够帮我放一下好了在那边的屏幕上还有这边的屏幕上那么你可以看到有两个屏幕左边是一个而右边是一个那么你会看到在左边这个程序在不停地走实际上正在进行这种逆向执行而右边也是在进行逆向执行右边这个窗口是有AI技术来做帮助的左边是没有AI技术你看右边现在已经结束了逆向执行已经结束但左边现在实际上还在进行运算应该大约结束了应该总共花到的时间应该是40秒吧好像30多秒而右边的时间是17秒你可以看到有了AI技术以后我们的整个的程序在进行指针判断的时候不再需要那么长的时间而我们可以在很快很快的一段时间就可以把具体的route cost找到我们这个工具应该是已经发布了在github上再去搜这个名字dpvse应该能够找到那么这个工具当然了我们发布的这个工具里边也有很多的bug也希望在座的各位如果感兴趣那么帮助我们一起来维护这个代码那么目前来说我们还在进行测内部的测试我们希望这样的工具可以帮助我们平时日常的这种软件开发能够节省尽可能多的时间好 最后我来总结一下刚才的我们这个演讲那么我相信大家通过这个演讲应该都得到了这样的信息就是内存的如果想要恢复内存的每一条指令的话非常困难的一件事情但是它对内存的诊断对于debug是非常有帮助的而逆向执行确确实实可以对找到一个软件出错的根本原因有很大的帮助而使用AI技术包括使用程序分析的技术对逆向执行是有很大的帮助的而在今后未来这样的工具很可能在程序员会帮助程序员能更快地找到程序出错的根本原因好 非常感谢大家那么代码刚才我淘汰了两个系统那么第一个系统的代码大家可以通过这个地方下载而第二个系统的代码大家要先去做一下查找在github上应该也能做先用的下载好 非常感谢谢谢大家如果有问题可以继续讨论谢谢有问题你好刚才您说的后面改进的提高效率的时候是加勒姆什勒尼加勒姆什勒尼前后的准确性有没有测过就是加勒姆什勒尼之后的准确度有没有下降哦 准确率是吧对深度神经网络确实是有准确率的问题因为你在做内存区域判断的时候如果你有1%的错误的话你有可能去把这个错误propagate 传播出来我们当时把神经网络可以调到测试数据级可以调到99.1%我们跑过确实是有诚实的讲是有问题的但是情况非常少见因为同名指证毕竟是非常非常少的一种情况多数情况都不是同名指证所以即使当这个判断错误的时候它并不会真正的传播所以在真实环境下虽然我们会出错但是实际上并对分析并没有太多的影响好的然后另一个可能问的小问题就是这个摩什勒尼的训练中的模型的参数是指的用一个就比如说用很多Crush的或是用很多正常的程序结构然后来训练出的一个统一性的一个比较通用的一个模型还是对每一次每一个程序都要单独训练一个模型我们现在做的是做离线的训练就是对积极学习这个模型是离线训练我们用了大约有一千多个程序当然包括的指令是很多一条指令我不记得具体的数量所以我不能给出具体的数量但是肯定是上一条指令做离线的学习然后再部署但是我们确实考虑过今后有可能比如说通过Reinforcement Learning加强学习的方法来提升这样的这种运算的能力然后让我的模型变得更准在在线过程当中能够不断地去训练这个模型让它变得更好这是有可能的但现在是离线模型所以就是同一个模型因为在各种程序里面各个不同的程序都是用的同一套的模型参数很抱歉 我没听清是不是对不同类型或者是不同的程序在用训练出来的那个怎么说就是在用的Machine Learning的模型的参数都是同一套是的目前来看是即使我们用的训练的模型的用的数据和我们最终测试的时候所用的程序完全是不一样的当然确实我们的程序在运行的过程当中确实有这种情况就是你不能保证你的训练数据里面没有任何的数据和你最终的测试数据里面的那些指令没有重合这个是没办法保证的因为每个程序都会有这种Library Call当有这种Library Call的时候那个所有的指令序列都是一样的但是整体上来说其实差异性还是比较大的OK 好可能OK还有问题吗还有个问题就是说你做这个东西它实际上你是在假设就是说它是一个就是在存在是弯弯Mapping的时候就是一对一的Mapping的时候就是说它才是可以内存和指令的硬设是吗不是我说就是说你实际上是在就是说做一个reversely tracing 是吧但你可以把整个tracing过程想要整个执行execution flow想要成一个function想要成一个flow一个方向input and output然后实际上那个我意思就是说你实际上是在假设就说它如果是一个这个方程可立的话整个flow可立的话那么它一定是一个一对一的一个Mapping就是one-one Mapping你说的是指令当中的内存访问不是我的意思我再一个Matter Level我这个更抽象的一个层面层面我从数学角度上来解释就是说就是说它是这个整个方程是一个一对一的一个Mapping我可能没有太明白因为这个印象不认很好所以我听起来很逼近Ok NevermindWe can我们可以下来下来聊这个对 还有一点我想问一下就说你们有没有把它变形化再说一点SorryHave you ever parallelizedthe wholethe entire processbecause I see thatI mean it's a binary treebasically我还是没听太清楚我可以离近一点ok 我离近一点就听听说就说那个它实际上就是因为我看它是一个就是一个理事一个binary tree是吧就是一个二维的一个树我就想就说因为每个树的那个分支每个树的那个理事上在做一个Depth就是那个深度优先的一个搜索对便利然后你实际上这样每条便利的那个那个track你都是很非常独立的在这种独立的情况下面我就说它可以可以变形化就说你们有没有尝试过把它变形化懂了 懂了对因为你可以因为你如果那个数据中心大的话你可以把它很快我们那个是是在做那个测试的时候就是做那个假设测试的时候我们确实是把它因为你的指令越多同名指令越多那么你的数就越深然后实际上是可以变形了就和佛奥执行是一样的佛奥执行就可以变形这一步是可以变形的但是这个cost也很高这个当然你有足够多的机器你当然就会足够多的快但是我们在现实环境里目前还没有做这方面的工作但是是可行的你说的这个是对的对非常好谢谢还有吗好如果再有问题这个线下讨论非常感谢大家