AL-A13-751-v6.1这个主板型号号是什么牌子的平板电脑

【MODICON QUANTUM 140CPUCPU11302PLC厦门天络纬】价格_厂家_图片 -Hc360慧聪网
您是不是在找:
买家还在看:
商品数量:
福建省&厦门市
手机访问店铺
MODICON QUANTUM 140CPUCPU11302PLC厦门天络纬
卖家承诺&60天发货
买家正在看
相关商品推荐
&560.00/台
&330.00/件
商家等级:
所在地区:
福建省 厦门市
认证信息:
&1500.00元
产品认证:
同参数产品
工作电压:
同参数产品
计量单位:
同参数产品
输出频率:
同参数产品
同参数产品
同参数产品
同参数产品
加工定制:
同参数产品
外壳颜色:
同参数产品
正在加载中........
慧聪网厂家厦门天络纬贸易有限公司为您提供MODICON QUANTUM 140CPUCPU11302PLC厦门天络纬的详细产品价格、产品图片等产品介绍信息,您可以直接联系厂家获取MODICON QUANTUM 140CPUCPU11302PLC厦门天络纬的具体资料,联系时请说明是在慧聪网看到的。
热门商品推荐
我的浏览记录
PLC相关资源
PLC热门产品搜索
PLC相关热门专题
您在慧聪网上采购商品属于商业贸易行为。以上所展示的信息由卖家自行提供,内容的真实性、准确性和合法性由发布卖家负责,请意识到互联网交易中的风险是客观存在的。推荐使用慧付宝资金保障服务,保障您的交易安全!
按字母分类 :
让慧聪网撮合专家为您解决采购难题
您采购的产品:
请输入采购产品
您的手机号码:
请输入手机号码
*采购产品:
请输入采购产品
*采购数量/单位:
请输入采购数量
请选择单位
*采购截止日期:
请输入正确的手机号码
请输入验证码
*短信验证码:
<input id="valid_Code1" maxlength="6" placeholder="请输入验证码" name="VALIDCODE" class="codeInput" onkeyup="this.value=this.value.replace(/\D/g,'')" onkeypress="if(event.keyCode
57) event.returnValue =" type="text">
免费获取验证码
为了安全,请输入验证码,我们将优先处理您的需求!
请输入验证码
发送成功!
慧聪已收到您的需求,我们会尽快通知卖家联系您,同时会派出采购专员1对1为您提供服务,请您耐心等待!
电话:4-750 &&
联系人:何安童&销售经理
公司名称:厦门天络纬贸易有限公司
请输入正确的手机号码
请输入验证码
*短信验证码:
免费获取验证码
为了安全,请输入验证码,我们将优先处理您的需求!
请输入验证码
每一份需求都会在24小时内得到行业多家优质供应商报价。
每一份需求的报价供应商工商信用资质都会经过专业人员检验,交易安全有保障。
免费咨询行业专家
免费咨询行业专家
服务主题:
筛选发货地
验证供应商真伪
提供其他优质供应商
采购数量:
用途描述:
成功加入采购单!
当前采购单共3种货品
成功加入采购单!
当前采购单共3种货品
不能购买自己发布的产品!
选中货品中含失效货品,无法完成下单,可能是:
1.货品库存不足
2.货品已过期,或被卖家删除
3.货品不支持在线交易
卖家暂时不在线,留下联系方式,卖家会主动联系您
*我要采购:
我的姓名:
留言内容:[原创]VMP分析插件应用实例:一个简单的CrackMe
上次更新添加了算法分析功能,可以反出类似高级语言的表达式,现在分析简单的程序应该没什么问题了,很多人说不太会用,这里给一个实例分析,讲解一下用法。这篇文章只讲插件的应用和一般的分析方法,还简单介绍了一下VMP加壳的原理和脱壳方法,其他内容比如插件分析原理和虚拟机代码还原方法等请看VMP分析插件的帖子。
先随便输入一些内容,用户名zdhysd,注册码qwertyuiop,点确定弹出注册码错误的提示。在MessageBox设个断点看看哪里来的,再点确定直接出错,看来有断点检查,在最后的retn设断点试试。这回断下来了,返回到004CBAE8
C70424 AFF5A7F9
MOV DWORD PTR SS:[ESP],F9A7F5AF
E8 1F41F7FF
CALL VMPCrack.0043FC14
...不像正常代码,应该是虚拟机中调用的,在这里点右键,菜单选择分析虚拟机,分析成功,找到5个虚拟机。先不用分析虚拟程序,因为函数返回时在虚拟程序的中间,可以先选中进入虚拟机时中断,运行,切换到虚拟机调试模式了,Ctrl+F9运行到返回,返回到
MOV EAX,DWORD PTR SS:[ESP+8]
SUB EAX,110
JE SHORT VMPCrack.
JNZ SHORT VMPCrack.
MOV EAX,DWORD PTR SS:[ESP+C]
66:3D 0100
JNZ SHORT VMPCrack.004029BD
MOV EAX,DWORD PTR SS:[ESP+4]
E8 5EFEFFFF
CALL VMPCrack.
***这个函数被加密了
***返回到这里
RETN 10这里已经是正常代码了,跟进CALL VMPCrack.,上来就是一个JMP,应该是进入虚拟机的,在这里分析虚拟程序。先选上反汇编后自动分析和分析选项里的分析虚拟机内调用,选中反汇编后自动分析会在每个虚拟程序反汇编后自动做数据和算法分析,选中分析虚拟机内调用会自动分析虚拟机内调用的函数。分析选项中除了化简无效数据以外也都选中,这样生成的代码比较简单,一般情况下无效数据都是没有用的,不化简可以节省很多时间。分析用了70多秒,一共分析出了19个虚拟程序,16万多行代码,看看记录,有几个未知的vESP改变和多个来源vESP不同,可能是调用引起的,先不管他。还有一个未知指令,一般没有什么影响,插件会自动分析指令相关的信息继续反汇编,遇到时再说吧。因为有虚拟机内调用,虚拟机内调用要在当前程序分析完成后才会分析,这是有些信息无法得到,最好在被调用函数分析完成后再分析一遍,可以在反汇编窗口右键菜单点分析-分析虚拟程序(全部)重新分析一次,这时会自动添加一些相关的分析提示,但不一定准确,调试时最好自己检查一下。
看看代码,大段大段的灰色指令,估计超过80%,而且有很多连接转移,应该是加变形了。点击反汇编栏标题把显示模式切换到有效指令,这样就不会显示垃圾指令了。当然也可以直接看最终操作,这样更简单,但是算法分析后的结果已经和虚拟机没有什么关系了,这里为了了解一下虚拟机的原理先不使用这个功能,看看VMP是什么样的。由于不使用算法分析的结果,指令没有化简,代码量很大,最好从一些关键的部分比如转移等看起。
从头跟踪一下,先把进入虚拟机时中断去掉,否则调用时退出虚拟机后再返回也会中断,在第一条指令设个断点,输入注册码点确定,中断下来了。第一个转移是JT,检查进入虚拟机时是否设了单步标志,最大保护下也用来检查是否被脱壳,不用管它。往下是一堆连接转移,每个指令块中的代码很少,垃圾指令都被清除掉了,大部分是计算转移目标用的,这是加变形后代码乱序的结果,没有什么用。走到
004C3918&&|. /83&&vJmp_00425E0A&&; 虚拟机内调用 VMPCrack.004B77D0; 重新进入虚拟机 VMPCrack.004D6C58; VMPCrack.004B77D0
有一个虚拟机内调用,跟进去看看,代码很长,而且有很多vCall,一般程序中不会出现这个指令,只有壳本身使用,有可能是SDK代码。实际上这个函数就是SDK中的检测调试器,看看其中的几个联机指令就知道了(POP SS、INT3等),因为和壳的入口使用的方法几乎相同,这里不多作讲解了,留到后面脱壳时再讲,给他加个标签。再往下看,有一个条件转移
00465EBE&&|. /05&&vJmp_&&; JNZ VMPCrack.
看看判断的条件是什么,先点击反汇编窗口注释栏标题切换到引用数据,信息栏切换到数据。看看信息栏,vJmp引用了两个数据,一个是重定位(0),另一个是转移目标。转移目标是根据要检查的标志选择的,先生成条件转移的两个目标,在堆栈中连续保存,再根据标志计算出0或4,加上保存目标的地址,就可以选择出一个目标,所以要找判断条件,应该先找到标志是哪个计算产生的。在信息窗口的转移目标上选菜单中的转到数据来源或直接按Enter键,一直向上查找数据来源,由于转移目标是计算产生的,类型为变量,如果有多个引用数据的话跟随类型为变量的数据。一直跟随到地址为变量的vReadMemSs4,这个vReadMemSs4是读取转移目标用的,地址由标志计算得到,用来选择一个目标。下面是跟踪过程,标*的是经过的指令,有多个引用数据的话只跟踪第一个。00465EF0
vReadMemSs4
(变量); 堆栈-0B4:(变量)
***读取解密前的目标
vPopReg4 vR8
堆栈-0B4:(变量)
(常量)8; (vESP)-0B0
vPushReg4 vR8
vR8:(变量)
vPopReg4 vR13
堆栈-0AC:(变量)
***下面开始解密,异或AA2B8C58
vPushReg4 vR13
vR13:(变量)
(vESP)-0AC
vReadMemSs4
堆栈-0AC:(变量); 堆栈-0B0:(vESP)-0AC
堆栈-0B0:(变量); 堆栈-0AC:(变量)
(常量)4; (vESP)-0B0
65 A773D455
vPushImm4 55D473A7
(常量)55D473A7
堆栈-0B0:(常量)55D473A7; 堆栈-0AC:(变量)
(常量)4; (vESP)-0B0
DE 588C2BAA
vPushImm4 0AA2B8C58
(常量)0AA2B8C58
vPushReg4 vR13
vR13:(变量)
堆栈-0B4:(变量); 堆栈-0B0:(常量)0AA2B8C58 ***
(常量)4; (vESP)-0B4
堆栈-0B0:(变量); 堆栈-0AC:(变量)
(常量)4; (vESP)-0B0
vPopReg4 vR2
堆栈-0AC:(变量)
***这是解密后的目标
AddVEsp 0FFFFFFF4
(常量)0FFFFFFF4; (vESP)-0A8
vPushReg4 vR14
vR14:(寄存器)EBP
vPushReg4 vR7
vR7:(寄存器)EBX
vPushReg4 vR10
vR10:(寄存器)ECX
AddVEsp 0FFFFFFFC
(常量)0FFFFFFFC; (vESP)-0C0
vPushReg4 vR1
vR1:(寄存器)EAX
vPushReg4 vR11
vR11:(寄存器)EDX
vPushReg4 vR12
vR12:(寄存器)EDI
vPushReg4 vR4
vR4:(寄存器)ESI
vPushImm4 0EF8B1AAC
(常量)0EF8B1AAC
vPushReg4 vR5
vR5:(常量)0
vPushReg4 vR2
vR2:(变量)
堆栈-0E0:(变量); 堆栈-0DC:(常量)0
***然后开始跟踪vReadMemSs4的地址,就是第二个引用数据,地址为堆栈-0B4的那个。跟踪一次到
00465EF2&&|.&&50&&vAdd4&&堆栈-0B8:(标志); 堆栈-0B4:(vESP)-0B0
这就是把标志计算出来的结果(0或4)加上保存目标的地址,现在开始跟踪类型为标志的数据。跟踪过程就不写了,想了解详细的处理流程的话可以使用数据流图功能,在vJmp选择菜单里的图表-数据流图(指令相关),可以显示所有相关数据的来源,只能在显示全部指令时用,可能有些计算常量的指令也包括进来了。一直跟随到
&&|.&&C0&&vNand4&&堆栈-0C0:(变量); 堆栈-0BC:(变量)
两个引用数据都是变量,说明标志是这里产生的,看看做的什么计算0045E7AD
vPushReg4 vR0
vR0:(寄存器)EAX
(vESP)-0BC
vReadMemSs4
堆栈-0BC:(寄存器)EAX; 堆栈-0C0:(vESP)-0BC
堆栈-0C0:(寄存器)EAX; 堆栈-0BC:(寄存器)EAX //EAX ~& EAX = ~EAX
(常量)4; (vESP)-0C0
vPushReg4 vR0
vR0:(寄存器)EAX
vPushReg4 vR0
vR0:(寄存器)EAX
堆栈-0C4:(寄存器)EAX; 堆栈-0C0:(寄存器)EAX //EAX ~& EAX = ~EAX
(常量)4; (vESP)-0C4
堆栈-0C0:(变量); 堆栈-0BC:(变量)
//~EAX ~& ~EAX = EAX & EAX计算的是(EAX ~& EAX) ~& (EAX ~& EAX),也就是EAX & EAX,所以条件转移对应的汇编代码应该是
TEST EAX,EAX
JNZ VMPCrack.
由于前面有一个虚拟机内调用,很可能改变了EAX,这个转移应该是检查调用的返回值是否为0。可以看出即使给出了数据相关的信息和清除垃圾指令后,分析虚拟机代码还是很麻烦的,一个条件转移就要看这么多内容。下面还是看算法分析的结果吧,把反汇编栏切换到最终操作,注释栏切换到表达式。现在代码量就很少了,这里大部分都是转移和调用相关的指令。由于加了变形,代码乱序用的垃圾转移很多,插件为了调试方便会保留每个指令块第一条和最后一条指令,可能有不少没用的内容,但是垃圾转移的指令块中没有其他代码,一下就能看出来,不管他就行了。调用相关的指令也都保留了,比如调用退出虚拟机时真实寄存器的值,退出后仍然可以引用的数据(标记为EXIT)等,还有最大保护下的加密寄存器、检查虚拟机完整性,如果调用很多的话这些内容也会占用一部分代码,都可以不用管,后面给出的代码中为了节省空间把这些去掉了。
既然说到最大保护下的调用,就讲一下为什么刚才在MessageBox设断点会出错,详细的说明请看VMP分析插件帖子中的内容,这里只简单说一下检测INT3断点。随便找个调用看看0048FA81
WORD v0 = GetBytes(9 * (0 : (5A86 ^ GetBytes(Rdtsc(), 0, 2)) % 1B5), 0, 2)
DWORD v1 = 428F55 + v0
vReadMemDs1
BYTE m0 = BYTE DS:[4 + v1]
vReadMemDs4
DWORD m1 = DWORD DS:[v1]
vReadMemDs4
DWORD m2 = DWORD DS:[5 + v1]
DWORD v2 = m2 + Check(0 + (400000 + (862FE2A3 + ((708F098F ^ m1) + 1 - 1))), 0 : (0 : ByteToWord(m0))) + (((40 & AddFlag(34, 8B)) && 1) + 77D307EA)
return v2; user32.MessageBoxA可以看出返回地址是计算出来的,其中有一部分是随机检查一块内存是否被修改,这里就不讲了。看看其中的((40 & AddFlag(34, 8B)) && 1),8B是MessageBoxA的第一个字节内容,插件认为是常量直接显示了。VMP会读出被调用函数的第一个字节,加上34,取加法的ZF标志随机移位加到返回地址上。INT3代码是CC,CC+34=100,ZF为1,如果设了断点加到返回地址的数就不是0了,会返回到一个随机的位置,所以出错。
现在再来看刚才的内容就容易多了
vPopReg4 vR6
vReadMemDs4
DWORD m0 = DWORD DS:[164B38]
DWORD v0 = 0 - ((m0 &&& 1B) + 1)
vReadMemSs4
DWORD m1 = DWORD SS:[Je(SubFlag((RolFlag(v0, 17) & 100) + (0 - (((164AD8 ^ (CpuidEax(1) & 0FFFFFFF0 ^ 588B4548) + (CpuidEbx(1) & 0FFFFFF ^ 3F598CC3)) &&& 1B) + 1) &&& 17), v0 &&& 17)) + 18]
if (unpacked) goto VMPCrack.00476AA5 //检查是否被脱壳
EFL = 0FFFFF700 & EFL
vWriteMemSs4
EXIT DWORD v1 = EBP
EXIT DWORD v2 = 118D7BDF
vWriteMemSs4
EXIT DWORD v3 = EBX
vWriteMemSs4
EXIT DWORD v4 = ESI
vWriteMemSs4
EXIT DWORD v5 = EDI
vWriteMemSs4
ARG1 EXIT DWORD v6 = 1 //调用的参数,TRUE,检测用户+内核调试器
vJmp_00425E0A
callVM &VMPCrack.检测调试器& //调用SDK,检测调试器
vReadMemSs4
DWORD m2 = DWORD SS:[Je(AndFlag("发现调试器", "发现调试器")) + 0FFFFFF50]
vPushReg4 vR1
DWORD v15 = "发现调试器"
if (entryVMEax_4D6C58 != 0) goto VMPCrack.一开始是检查脱壳,壳的入口在初始化时会根据CPUID计算出一个数,每个虚拟程序开始时取CPUID计算后和这个数比较,如果被脱壳,入口的初始化代码不会被执行,直接DUMP出来的话这个值是固定的,到其他电脑上就会被检查到,返回到一个错误的地址,脱壳时要考虑这个问题。然后是保存一些寄存器,对应汇编中函数开始时的PUSH REG,由于加了变形,很多操作都被垃圾转移分隔了。再往下就是刚才那个条件转移,现在就很明显了,判断调用SDK检测调试器是否返回0。entryVMEax_4D6C58表示在4D6C58进入虚拟机时的EAX,由于插件不会自动分析被调用函数,这样有些需要的信息得不到,所以遇到调用时最好添加分析提示。一般调用需要的信息有ESP改变、引用和修改的数据等,虚拟机内调用还需要真实寄存器在初始堆栈中的位置,插件会自动填写一些,但是不一定准确,最好自己检查一下。由于这个虚拟机内调用没有填写初始寄存器提示,分析时不能把调用前后的真实寄存器联系起来,所以后面用entryVMxxx来表示被调用函数执行后重新进入虚拟机时的真实寄存器值。看看被调用函数第一条指令的注释/*esp(8); arg(1); regModify(eax)*/,插件已经自动填写了一些:ESP改变为8,有一个参数,改变寄存器EAX,这里可以设置一下参数名和返回值,改成arg(1,"检测内核调试器"); regModify(eax, "发现调试器"),显示时会自动标出这些内容。现在来检查一下是否正确,计算ESP改变最简单的方法是调试观察,先把注释栏切换到堆栈,看看分析结果
004C3918&&|. /83&&vJmp_00425E0A&&vESP=-0E8; ESP=-0E4; 堆栈长度=0038
004D6C58&&|& \68&&vPopReg4 vR13&&vESP=-0DC; ESP=-0DC; 堆栈长度=0034
执行vJmp时vESP是-0E8,调用返回后是-0DC,加了0C。调试一下,再记录调用前后的vESP,执行vJmp时是13F960,调用返回后是0013F96C,也是加了0C,说明是对的。现在填写初始寄存器提示,有两种方法判断寄存器的位置,一种是根据调用前压栈的值计算,另一种是根据被调用函数返回时恢复的寄存器计算。刚才看记录发现这个函数中有未知vESP改变和来源堆栈信息不同,分析结果有可能不正确,跟进函数看看确实是这样,退出时所有寄存器都被关联到了同一个变量,所以现在选择根据调用前压栈的值计算。其实这是由于其中有联机指令INT3,正常情况下会走到异常处理,但是插件不能自动分析异常处理,添加分析提示设置一下联机指令的返回地址就能解决,这里先不用管,等后面讲脱壳时再看这些反调试。先把注释栏切换到产生数据,显示模式切换到全部指令,因为分析时无法关联调用前后的寄存器,这些内容都作为无效数据处理了,看看调用前压栈的值004C392D
vPushReg4 vR0
堆栈-0B4:(标志)
vPushReg4 vR13
堆栈-0B8:(寄存器)ESI
vPushReg4 vR15
堆栈-0BC:(寄存器)ECX
vPushReg4 vR10
堆栈-0C0:(标志)
vPushReg4 vR3
堆栈-0C4:(寄存器)EAX
vPushReg4 vR9
堆栈-0C8:(寄存器)EBX
vPushReg4 vR13
堆栈-0CC:(寄存器)ESI
vPushReg4 vR2
堆栈-0D0:(寄存器)EDI
vPushReg4 vR8
堆栈-0D4:(vESP)30
vPushReg4 vR4
堆栈-0D8:(常量)4B77D0
vPushReg4 vR6
堆栈-0DC:(寄存器)EDX
vPushReg4 vR5
堆栈-0E0:(常量)164AD8
DE D4CF74EF
vPushImm4 0EF74CFD4
堆栈-0E4:(常量)0EF74CFD4
堆栈-0E0:(常量)0EF8B1AAC; 堆栈-0E4:(标志)
vPopReg4 vR12
vR12:(标志)
vPushReg4 vR7
堆栈-0E4:(常量)0
vPushReg4 vR4
堆栈-0E8:(常量)4B77D0
vJmp_00425E0A大部分寄存器都和虚拟程序开始时相同,没有被改变,先拿EAX计算一下,EAX被写入堆栈的地址是-0C4,vJmp执行前vESP是-0E8,执行后会把栈顶的转移目标弹出,变成-0E4,这就是被调用函数执行时的vESP,-0C4 - -0E4 = 20,说明EAX在初始堆栈中的位置是20,添加提示initReg(eax,20),其他寄存器按相同方法计算。寄存器中缺少EBP,说明EBP的值在前面被改变了,但是有一个(vESP)30,EBP一般用来做堆栈指针,很可能就是它。ESI有两个分别计算一下,看看后面怎么使用,其中位置为2C的后面没有使用,是无效数据,应该是另一个。全部提示为/*initReg(eax,20);initReg(ecx,28);initReg(edx,8);initReg(ebx,1C);initReg(ebp,10);initReg(edi,14);initReg(esi,18)*/,注意一定要包含在/**/中才有效,可以添加到插件自动生成的提示里,用;分隔。添加提示后需要重新分析才会生效,按Ctrl+A重新分析当前函数(不是被调用函数),结果条件转移前的那一堆entryVMxxx都没有了,调用前标出了寄存器值。以后遇到调用最好都填写一下,分析会更准确,这个程序虚拟机内调用比较多,填写提示可能有些麻烦。以后给出的代码都是添加提示后的结果,如果没什么特殊情况就不多说明了。
再往下又是一个调用,里面也有vCall,应该是SDK代码,其中包括联机指令STR WORD PTR SS:[ESP]等,可能是检测虚拟机,留到脱壳时再讲。先填上分析提示,这回换一种找寄存器位置的方法,从被调用函数中找一个vRet,这里用第一个联机指令,因为退出虚拟机时会恢复真实寄存器,查看执行vRet前压栈的值就可以找到对应的寄存器。0041EC9D
vPushReg4 vR6
ECX DWORD v8 = unknownInit8
vPushReg4 vR0
EFL DWORD v9 = SubFlag(0C, 800)
vPushReg4 vR8
EDX DWORD v10 = unknownInit7
vPushReg4 vR14
ESI DWORD v11 = unknownInit4
vPushReg4 vR9
EAX DWORD v12 = unknownInit2
vPushReg4 vR7
EDI DWORD v13 = unknownInit10
vPushReg4 vR5
EBP DWORD v14 = 0C
vPushReg4 vR10
EBX DWORD v15 = unknownInit3
online 4CF851; VMPCrack.004CF851; STR WORD PTR SS:[ESP]未知的初始数据显示为unknownInit,先随便找一个寄存器,比如ECX对应unknownInit8,把显示模式切换到全部指令,往前到虚拟程序开始的位置,有
0041EC54&&|.&&62&&vPopReg4 vR6&&DWORD _t31 = unknownInit8
这个指令引用的堆栈是20,这就是ECX的位置。EBP在退出前已经被改变了,还是用以前的方法,在调用的vJmp前压栈的数据中找。上面介绍的两种方法都是在寄存器没有被改变的情况下才能使用,如果被改变就要自己分析了,一般情况下即使不填这个提示也没有太大影响。填写完后分析代码如下0046783B
vJmp_00425E0A
callVM &VMPCrack.检测虚拟机&
vReadMemSs4
DWORD m3 = DWORD SS:[Je(AndFlag("发现虚拟机", "发现虚拟机")) + 0FFFFFF50]
vPushReg4 vR9
DWORD v17 = 30
vPushReg4 vR6
DWORD v18 = "发现虚拟机"
if ("发现虚拟机" != 0) goto VMPCrack.下面还是调用,有两个循环,里面都用到了vCheck,应该是SDK的检查文件改变,因为一般情况下vCheck都是在计算返回地址时用的。0049FC7D
vJmp_00425E0A
callVM &VMPCrack.检查文件改变&
vReadMemSs4
DWORD m4 = DWORD SS:[Je(AndFlag("文件没有改变", "文件没有改变")) + 0FFFFFF50]
vPushReg4 vR12
DWORD v15 = "文件没有改变"
if ("文件没有改变" == 0) goto VMPCrack.SDK中只有这3种检查,到这里应该检查完了,但是后面还有很多调用,不知道是干什么的。因为插件不能自动分析被调用函数,遇到调用最好先处理一下再看代码,这样分析结果会更准确。先跟进最近的一个调用看看
vPopReg4 vR0
EFL = 0FFFFF700 & unknownInit5
WORD v0 = GetBytes(9 * (0 : (30B4 ^ GetBytes(Rdtsc(), 0, 2)) % 1B5), 0, 2)
DWORD v1 = 428F55 + v0
vReadMemDs1
BYTE m0 = BYTE DS:[4 + v1]
vReadMemDs4
DWORD m1 = DWORD DS:[v1]
vReadMemDs4
DWORD m2 = DWORD DS:[5 + v1]
DWORD v2 = m2 + Check(0 + (400000 + (862FE2A3 + ((708F098F ^ m1) + 1 - 1))), 0 : (0 : ByteToWord(m0))) + (((40 & AddFlag(34, 8B)) && 2) + 77D0436E)
vPushReg4 vR2
EBP DWORD v3 = unknownInit2
vPushReg4 vR15
EDX DWORD v4 = unknownInit4
vPushReg4 vR14
EAX DWORD v5 = unknownInit10
vPushReg4 vR1
EBX DWORD v6 = unknownInit9
vPushReg4 vR13
ESI DWORD v7 = unknownInit6
vPushReg4 vR8
EFL DWORD v8 = unknownInit5
vPushReg4 vR12
ECX DWORD v9 = unknownInit8
vPushReg4 vR3
EDI DWORD v10 = unknownInit3
return v2; user32.GetDlgItem这很明显是调用API,调用目标已经分析出来了,是user32.GetDlgItem。VMP在选择加密输入表后所有输入函数地址都是加密的,一般的函数是加上一个常量,但是虚拟机中调用的函数会用更复杂的计算,这里因为结果是常量所以分析时自动计算了。虚拟机中每次调用输入函数都会单独生成一个虚拟程序来解密地址和调用,插件只能自动分析普通的解密输入表代码,所以这段代码没有被标记为解密输入表。看看自动添加的分析提示/*esp(0); regModify(eax)*/,插件认为这个函数的ESP改变为0,它本身的改变确实为0,但是退出时是返回到API,相当于一个API调用,插件不知道这一点,所以要修正一下,其他API调用也要这样处理。计算ESP改变还用前面说过的方法,看看分析得到的堆栈信息
0046540A&&|. /D9&&vJmp_00411C30&&vESP=-0EC; ESP=-0E8; 堆栈长度=0038
0049C3CE&&|& \58&&vPopReg4 vR14&&vESP=-0E8; ESP=-0E8; 堆栈长度=0034
在调试记录一下调用前后的vESP,分别为0013F95C和0013F96C,分析结果是4,调试得到的结果是10,所以应该填写10-4=0C,就是/*esp(0C)*/。实际上确实是这样,GetDlgItem有两个参数,返回时弹出两个参数和一个返回地址。由于插件不知道ESP改变,所以没有自动添加参数提示,这里也加上arg(1,hWnd);arg(2,ControlID),再加个标签。GetDlgItem也有对其他寄存器的改变,比如ECX等,应该填regModify(ecx)的,但是普通的函数返回后一般不会使用除EAX(返回值)以外的寄存器,所以这些不填也没关系。后面所有API都这样处理,就不多做说明了,加密输入表后虚拟机中调用的API比较多的话还是有些麻烦,其实分析提示只填ESP改变就可以,加其他内容只是为了代码更好看。参数提示最好也填上,因为这个提示会告诉插件对应的数据时有效的,不应该被作为垃圾指令,否则函数返回后把参数弹出而且这之前没有对参数的引用的话,插件可能会认为参数对应的数据是没有用的。
这里开始准备取输入的用户名,调用前那一堆带EXIT的都是真实寄存器,只有退出虚拟机的调用才会标出寄存器名,插件为了调试方便把调用相关的代码比如恢复寄存器等都保留了,如果不需要的话不管他就行了,这里贴出的代码为了节省空间直接去掉了。0049DD33
vReadMemSs4
DWORD s0 = Stack(vESP + 38, ESP + 4, 4)
vWriteMemSs4
ControlID EXIT DWORD v32 = 3E8 //用户名输入框ID
vWriteMemSs4
hWnd EXIT DWORD v33 = s0
//主窗口句柄,ESP + 4说明是第一个参数传进来的
vJmp_00411C30
callVM &VMPCrack.GetDlgItem&
//取用户名输入框句柄
vWriteMemSs4
hWnd EXIT DWORD v42 = "用户名hwnd"
vJmp_00411C30
callVM &VMPCrack.GetWindowTextLengthA&//取输入的用户名长度
vReadMemSs4
DWORD m5 = DWORD SS:[Je(AndFlag("用户名长度", "用户名长度")) + 0FFFFFF44]
vPushReg4 vR4
DWORD v51 = "用户名长度"
vPushReg4 vR10
DWORD v52 = "用户名长度"
if ("用户名长度" != 0) goto VMPCrack.//检查输入的用户名长度是否为0
vPushReg4 vR6
Style EXIT DWORD v53 = v52
vWriteMemSs4
Title EXIT DWORD v54 = 4074EC
//标题"VMPCrackMe"
vWriteMemSs4
Text EXIT DWORD v55 = 4074DC
//内容"请输入用户名"
vWriteMemSs4
hOwner EXIT DWORD v56 = v38
vJmp_00411C30
callVM &VMPCrack.MessageBoxA&
//弹出消息框
vReadMemSs4
DWORD s1 = Stack(vESP + 34, ESP + 0, 4)
return v67
//函数结束上面是检查用户名是否为空,为空的话弹出消息"请输入用户名"并结束。004482FA
DWORD v76 = SubFlag(v51, 20)
vReadMemSs4
DWORD m10 = DWORD SS:[Ja(v76) + 0FFFFFF44]
if (v51 &= 20) goto VMPCrack.004D0510
//v51 = "用户名长度"
vReadMemSs4
DWORD m11 = DWORD SS:[Jpo(v76) + 0FFFFFF44]
if (Jpe(v76)) goto VMPCrack.004D4ABA这里有一个比较if (v51 &= 20) goto VMPCrack.004D0510,在信息窗口看看,v51的值是"用户名长度",说明用户名长度不能超过0x20(32)个字符。后面的那个if (Jpe(v76))不知道是什么意思,v76等于SubFlag(v51, 20),正常代码中不太可能有这种东西,除非是转移类型分析错了。如果加了变形的话可能是垃圾分支,这种情况下两个分支的代码是相同的,等于把代码复制了一份。往下看看确实是这样,执行的代码都是弹出消息框然后退出,肯定是垃圾分支了,加个分析提示清除掉。添加提示/*jmp(4D4ABA)*/,告诉插件作为只有一个目标的转移处理,因为转移在反汇编阶段处理,必须重新反汇编才能生效,可以在分析点窗口中选择重新分析或直接按空格键。重新反汇编前一定要注意删除或禁用所有断点,因为普通断点会在代码中插入vRet,这样反出来的代码是错误的,到插入的vRet时就会停止。
重新反汇编后代码变成了0043ED7A
goto m11 ^ 6F67D83E
Style EXIT DWORD v83 = 0
vWriteMemSs4
Title EXIT DWORD v84 = 4074EC
//标题"VMPCrackMe"
vWriteMemSs4
Text EXIT DWORD v85 = 4074C4
//内容"用户名不能超过32个字符"
vWriteMemSs4
hOwner EXIT DWORD v86 = v38
vJmp_00411C30
callVM &VMPCrack.MessageBoxA&
//弹出消息框
vReadMemSs4
DWORD s2 = Stack(vESP + 34, ESP + 0, 4)
return v96
//函数结束再往下看有一个调用,也是只有一个指令块,像是调用API,但最后不是调用,而是返回了
vPopReg4 vR2
EFL = 0FFFFF700 & unknownInit6
vReadMemSs4
DWORD s0 = Stack(vESP + 34, ESP + 0, 4)
vReadMemDs1
BYTE m0 = BYTE DS:[s0]
WORD v0 = GetBytes(9 * (0 : (2595 ^ GetBytes(Rdtsc(), 0, 2)) % 1B5), 0, 2)
DWORD v1 = 428F55 + v0
vReadMemDs1
BYTE m1 = BYTE DS:[4 + v1]
vReadMemDs4
DWORD m2 = DWORD DS:[v1]
vReadMemDs4
DWORD m3 = DWORD DS:[5 + v1]
DWORD v2 = m3 + Check(0 + (400000 + (862FE2A3 + ((708F098F ^ m2) + 1 - 1))), 0 : (0 : ByteToWord(m1))) + (((40 & AddFlag(34, m0)) && 3) + Stack(34, 4))
vPushReg4 vR10
EBP DWORD v3 = unknownInit8
vPushReg4 vR5
EDX DWORD v4 = unknownInit9
vPushReg4 vR9
EAX DWORD v5 = unknownInit5
vPushReg4 vR1
EBX DWORD v6 = 77D1216B
vPushReg4 vR0
ESI DWORD v7 = unknownInit4
vPushReg4 vR7
EFL DWORD v8 = unknownInit6
vPushReg4 vR11
ECX DWORD v9 = unknownInit7
vPushReg4 vR13
EDI DWORD v10 = unknownInit10
return v2这里除EBX以外的寄存器都没有改变,EBX变成了77D1216B,看看地址,是GetWindowTextA,应该也是解密输入表,对应的汇编代码类似MOV EBX,[&GetWindowTextA&],再往后可能会有像CALL EBX这样的代码,往下看看果然是这样,有一个
0048EA90&&|.&&B5&&vRet& && && && &call v120
就是调用GetWindowTextA,但是插件不知道,可以加一个提示/*call(77D6)*/,表示调用目标为77D1216B(GetWindowTextA),返回到4D7656,返回到的地址是被调用函数返回时的地址,不是虚拟机中的地址,注意不同的系统API地址可能不同。
vWriteMemSs4
EXIT DWORD v105 = 21
//参数Count
vWriteMemSs4
EXIT DWORD v106 = 0FFFFFFE8 //参数Buffer
vWriteMemSs4
EXIT DWORD v107 = v51
//参数hWnd,v51 = "用户名hwnd"
vJmp_00411C30
callVM VMPCrack. //解密GetWindowTextA的地址,保存到EBX
call v120; user32.GetWindowTextA由于加了最大保护,调用前的那些vNand是加密寄存器,先返回到解密寄存器的代码,解密后再调用,还有检查虚拟机完整性,会用vCheck随机检查一段内存和返回地址计算,所以内容有点长,这里没贴出来,不用管这些。由于参数压栈后有一个虚拟机内调用,然后才是API,所以没有自动标出,参数Buffer因为是堆栈地址,在分析时以vESP偏移的形式保存,FFFFFFE8表示vESP+18。
往下又是3个调用0043365C
vWriteMemSs4
"MD5对象" EXIT DWORD v129 = 0CC75CF87
call v135; &VMPCrack.MD5初始化&
vWriteMemSs4
"长度" EXIT DWORD v144 = v141
vWriteMemSs4
"内容" EXIT DWORD v145 = 235A22B8
vWriteMemSs4
"MD5对象" EXIT DWORD v146 = 235A2234
call v152; &VMPCrack.MD5计算&
vWriteMemSs4
"MD5对象" EXIT DWORD v161 = 15CC7877
vWriteMemSs4
"保存MD5值" EXIT DWORD v162 = 15CC7933
call v168; &VMPCrack.MD5取结果&这些代码中都把检查虚拟机完整性和加密寄存器的部分去掉了,太占地方,而且一眼就能看出来,不用管它就行了。先跟进第一个调用看看,那些常量很熟悉吧,就是MD5初始化。由于这几个函数都是调用方弹出参数,被调用函数只弹出返回地址,所以没有自动加参数提示,自己添加一下。但是分析出来的参数好像不对,比如要初始化的MD5对象是0CC75CF87,这显然不像是一个有效的地址,往前跟踪数据来源看看怎么回事。在信息窗口中一直跟随引用数据,最后跟到了调用GetWindowTextA时的EBP,由于EBP这时是常量,而且最大保护下调用函数前要加密寄存器,就是异或一个常量,结果还是常量,分析时自动计算了。但是调用函数时要先解密,插件没有处理,认为调用后还是加密的值,所以错了。找到原因就好,可以在返回后添加一个常量提示,返回后弹出EBP是在
&&|.&&08&&vPopReg4 vR11&&DWORD _t22128 = 0CC75D053
添加提示/*const(1,20)*/,表示第一个操作数的值是常量20,也就是加密前的EBP,后面的调用也做相同处理,其实不填写也可以,只要自己能看明白就行。后面的两个函数猜都能猜出来是什么了,初始化后肯定是计算、取MD5值,跟进去看看确实是这样,顺便加上标签和参数提示。
ControlID EXIT DWORD v175 = 3E9 //注册码输入框ID
vWriteMemSs4
hWnd EXIT DWORD v176 = v173
//主窗口句柄
vJmp_00411C30
callVM &VMPCrack.GetDlgItem&
//取注册码输入框句柄
vWriteMemSs4
hWnd EXIT DWORD v185 = "注册码hwnd"
vJmp_00411C30
callVM &VMPCrack.GetWindowTextLengthA&//取输入的注册码长度
vReadMemSs4
DWORD m41 = DWORD SS:[Jnz(AndFlag("注册码长度", "注册码长度")) + 0FFFFFF50]
vPushReg4 vR7
DWORD v193 = "注册码长度"
if ("注册码长度" == 0) goto VMPCrack.00466E05//检查是否输入了注册码,转到弹出提示"请输入注册码"
DWORD v194 = SubFlag(v193, 28)
vReadMemSs4
DWORD m42 = DWORD SS:[Je(v194) + 0FFFFFF50]
if (v193 != 28) goto VMPCrack.00441B20//v193 = "注册码长度",检查注册码是否为28位,转到弹出提示"注册码错误。"
vWriteMemSs4
Count EXIT DWORD v195 = 29
vWriteMemSs4
Buffer EXIT DWORD v196 = 0FFFFFFAC //保存注册码
vWriteMemSs4
hWnd EXIT DWORD v197 = v190
//v190 = "注册码hwnd"
call v203; user32.GetWindowTextA这回是取输入的注册码,调用GetWindowTextA的地址还是上面得到的那个,也加个分析提示/*call(77DFC)*/,这里可以看出来注册码必须是0x28(40)位。00496DC7
vWriteMemSs4
"数组长度" EXIT DWORD v212 = 14
vWriteMemSs4
"数组" EXIT DWORD v213 = 0FFFFFFFC
vWriteMemSs4
"字符串长度" EXIT DWORD v214 = 28
vWriteMemSs4
"字符串" EXIT DWORD v215 = 0FFFFFFAC//输入的注册码
call v221; &VMPCrack.16进制字符串转数组&//把输入的注册码字符串转成数组
vReadMemSs4
DWORD m56 = DWORD SS:[Je(AndFlag("结果长度", "结果长度")) + 0FFFFFF50]
if ("结果长度" == 0) goto VMPCrack.00441B20//结果长度为0表示转换失败,注册码不是16进制字符串,转到弹出提示"注册码错误。"这里调用了一个函数4024F0,是把16进制字符串转成数组,说明注册码必须是16进制字符串,注册码输入5678再往下跟。
callVM &VMPCrack.验证注册码&
//调用验证函数
vReadMemSs4
DWORD m57 = DWORD SS:[Jnz(AndFlag(GetBytes("验证成功", 0, 1), GetBytes("验证成功", 0, 1))) + 0FFFFFF50]
if (GetBytes("验证成功", 0, 1) == 0) goto VMPCrack.00441B20//返回0为验证失败,转到弹出提示"注册码错误。"
vWriteMemSs4
Style EXIT DWORD v231 = 0
vWriteMemSs4
Title EXIT DWORD v232 = 4074EC //标题"VMPCrackMe"
vWriteMemSs4
Text EXIT DWORD v233 = 1
//内容,地址为1?
vWriteMemSs4
hOwner EXIT DWORD v234 = v182
vJmp_00411C30
callVM &VMPCrack.MessageBoxA&
//弹出消息框
return v24642E566的调用像是关键的验证函数,因为他返回0就转到验证失败了,试试能不能在if (GetBytes("验证成功", 0, 1) == 0)爆破。可以执行到vJmp然后修改堆栈里的目标,或者先执行到验证失败的转移目标,再用菜单里的 此处为新vEIP 功能设置到正确的目标。执行后出错了,77d08944引用内存1,好像是调用MessageBoxA时出的,回去看看参数,内容的地址果然是1。那怎么显示消息?先不管他,跟进验证函数里看看。
先试试最简单的方法,看能不能在验证函数里爆破。因为知道了返回0是失败,在函数里找出所有返回,可以查找vRet(不包括用作调用的),一共找到6个,看看返回值EAX。
vPushReg4 vR0
EAX DWORD v84 = GetBytes(v80, 1, 3) : 0
return v83
vPushReg4 vR2
EAX DWORD v95 = GetBytes(s43, 1, 3) : 0
return v94
vPushReg4 vR0
EAX DWORD v125 = GetBytes(s46, 1, 3) : 0
return v124
vPushReg4 vR6
EAX DWORD v140 = GetBytes(entryVMEax_4689BB, 1, 3) : 1
return v139
vPushReg4 vR9
EAX DWORD v151 = GetBytes(entryVMEax_45CED7, 1, 3) : 0
return v150
vPushReg4 vR4
EAX DWORD v162 = GetBytes(v120, 1, 3) : 0
return v161其中5个把EAX的低BYTE(AL)设为0,另一个设为1,应该就是验证成功的地方。找一找转移到这些返回的分支,可以在包含返回的指令块开始用右键菜单 转到-转移来自,看看是哪里跳过来的,最后找到下面这些。004425EA
if (v79 == v80) goto VMPCrack.//跳
if (v40 == s43) goto VMPCrack.004318DE//跳
if (Cross(nonentity, v111) == v121) goto VMPCrack.004A927A//跳
if (entryVMEax_45CED7 != entryVMEsi_45CED7) goto VMPCrack.00467F03//不跳
if (v119 != v120) goto VMPCrack.00437E53//不跳跳到返回0的必须不跳才能能验证成功,跳过返回0的必须跳。先调试一下,跟踪时用设置vEIP功能设置到正确的目标,这回弹出了"注册码正确,验证完成。",看来是可以爆破的。但是为什么刚才内容地址是1呢,再回去看看那个1是哪里来的,向上跟踪数据来源,最后找到了004DFC32
vPushImm4 407470
(常量)407470
vReadMemDs4
内存:(常量)1; 堆栈-0B4:(常量)407470是从地址407470里读出来的,插件认为内存是常量,所以直接显示了,再看看407470的值,已经变成了3420000,应该是注册成功后写入的。到返回1的那个vRet往前看看,前面有00446EE6
vJmp_00411C30
callVM VMPCrack.0046BB01
vWriteMemDs4
DWORD DS:[407470] = v136; EXIT DWORD v136 = entryVMEax_4689BB字符串地址是一个调用返回的,跟进那个调用0046BB01
vPopReg4 vR5
vReadMemSs4
DWORD m0 = DWORD SS:[Je(AndFlag(0, 0)) + 28]
vPushReg4 vR12
DWORD v0 = 0
vJmp_00411C30
if (0 != 0) goto VMPCrack.004B8F91
vPopReg4 vR15
ARG4 DWORD v1 = 4
ARG3 DWORD v2 = 3000
ARG2 DWORD v3 = 18
ARG1 DWORD v4 = 0
v5 = Call(495AD3); VMPCrack.00495AD3 //VirtualAlloc
vWriteMemDs4
DWORD DS:[v5] = v6; DWORD v6 = 0E1B2A2D7 //产生字符串
vWriteMemDs4
DWORD DS:[v5 + 4] = v7; DWORD v7 = 0FDD5EBC2
vWriteMemDs4
DWORD DS:[v5 + 8] = v8; DWORD v8 = 0ACA3B7C8
vWriteMemDs4
DWORD DS:[v5 + 0C] = v9; DWORD v9 = 0A4D6E9D1
vWriteMemDs4
DWORD DS:[v5 + 10] = v10; DWORD v10 = 0C9B3EACD
vWriteMemDs4
DWORD DS:[v5 + 14] = v11; DWORD v11 = 0A3A1
vWriteMemDs4
DWORD DS:[491D42] = v12; EXIT DWORD v12 = v5
vPushReg4 vR8
DWORD v0 = v5
return v15有vCall,还是SDK,应该是解密字符串。上面有一个if (0 != 0) goto VMPCrack.004B8F91,0和0比较,一般不会这么做,最有可能的是其中一个是内存中读出来的,被插件当作常量了,往上看看,有0046BA92
vPushImm4 491D42
(常量)491D42
vReadMemDs4
内存00491D42:(常量)0; 堆栈24:(常量)491D42确实是内存里读出来的,加个分析提示告诉插件这里不是常量,/*memConst(0,491D42,4)*/表示491D42开始的4字节不是常量,一定要加到内存块开始,就是40A000。再分析一下,上面的比较变成了0046BA90
vReadMemDs4
DWORD m0 = DWORD DS:[491D42]
vReadMemSs4
DWORD m1 = DWORD SS:[Je(AndFlag(m0, m0)) + 28]
vPushReg4 vR12
DWORD v0 = m0
vJmp_00411C30
if (m0 != 0) goto VMPCrack.004B8F91检查是否已经解密过字符串,如果解密过直接使用,否则调用VirtualAlloc分配内存,产生字符串保存起来。
现在可以爆破了,把那5个条件转移改掉就行,因为加了壳,这部分代码是压缩的,只能在解压后修改。你如果觉得脱壳更简单那就脱吧,我不会脱壳,连最简单的压缩壳都没有脱过,一直都是带壳分析带壳修改,这次为了测试插件才研究了一下VMP脱壳,后面会讲一下。先把补丁做好,方法很多,能改转移目标就行,比如把两个解密前的目标改成相同的值、在靠近vJmp的位置插入代码修改目标、在验证失败的指令块开始添加vJmp转到正确的目标等。因为加了最大保护,解密前的目标不好修改,这里先讲一下使用插入指令的方法。随便找一个分支,比如
0047DD76&&|. /33&&vJmp_&&if (v40 == s43) goto VMPCrack.004318DE
这个分支必须跳,由于插入指令至少需要8字节的空间来添加一些附加的代码,所以不能直接在vJmp插入,应该向前找几个指令,最近的是
0047DD80&&|.&&63 15CDF8FA&&vPushImm4 0EF74CFD4&&DWORD _t51643 = 0EF74CFD4
在这里选择插入指令,会弹出一个窗口,建好了插入指令的框架,内容是vPopReg4 vR14 ;弹出重定位
;在这里添加指令
vPushImm4 0EF74CFD4 ;被覆盖的指令
vPushReg4 vR3 ;被覆盖的指令
vAdd4 ;被覆盖的指令
vPopReg4 vR5 ;被覆盖的指令
vPushReg4 vR14 ;重定位
vPushImm4 0047DD79 ;目标地址
vJmp_ ;转到原来的位置在上面标出的位置添加指令,由于是想修改转移目标,看看保存在哪个寄存器中。执行vJmp时栈顶的值是返回地址,一般是离vJmp最近的那个vPushReg4压入的,这个寄存器是vR9,所以添加代码
vPushImm4 4318DE
vPopReg4 vR9
这样就可以改变转移目标了,由于VMP中没有MOV指令,只能通过这种方法来赋值。然后找个地方来保存插入的代码,因为要带壳修改,最好放到不用解压的段里,一般找包含模块入口点的段就可以了,这个段在解压前就会执行。这个程序里是.vmp1,在后面找一块空的地方。选择保存位置时要注意是否为反向指令块,这时代码是从后向前保存的,这次要修改的代码就在反向指令块中,所以直接放在最后就行了,地址设置为5C0FFF。
插入指令会把原来的指令块拆分成两个,添加一个vJmp到插入的指令,执行插入代码后再执行被覆盖的指令,最后跳回原来的位置,还会自动处理重定位等。这是比较通用的方法,可以用于任何修改,而且有些修改由于会覆盖原来的指令,只能用这种方法,但缺点是生成的代码比较多。如果只是想改转移目标的话还有更简单的方法,就是在验证失败的指令块开始添加vJmp转到正确的目标,还是刚才那个分支,验证失败时转到004C9665,可以在这里修改代码,转到正确的目标4318DE
vPushImm4 4318DE
只修改这两条指令就可以了,但是因为VMP使用类似流加密的方法加密指令,即使只修改一个指令,也要重新编译从修改位置到指令块结束的所有代码,这些插件都自动做了。由于是在指令块开始修改,整个指令块都重新编译了,修改范围太大,最大保护下很容易碰到验证完整性的代码,还要处理验证。但是这里修改后的指令是vJmp,后面的指令不会执行了,打补丁时只写入这两条指令就可以。把其他几个转移也修改一下,下面是修改后的代码和对应的内存内容
8A 1914727C
vPushImm4 47E2E8
64 19 14 72 7C 8A
47 C8CD0790
vPushImm4 4318DE
23 C8 CD 07 90 47
22 95AD0CE9
vPushImm4 4A927A
A2 95 AD 0C E9 22
21 14AEF0B4
vPushImm4 43C25B
82 14 AE F0 B4 21
71 E3CBEF76
vPushImm4 461C3F
B6 E3 CB EF 76 71注意修改的这些指令块都是反向的,内存里的代码指令顺序相反。还有压栈的转移目标是重定位前的,如果修改DLL的话要注意这一点。现在在运行一下结果出错了,因为修改范围太大被查到了。重启程序,不用插件的汇编功能,只写入修改过的代码,随便输入40位16进制数就注册成功了,长度限制就不改了,否则输入超过40位可能会溢出。现在的问题是要修改的部分是解压出来的,不能直接修改文件,只能在解压后再写入。这也有很多方法,关键是解压后转到我们的代码,最简单的方法就是拦截程序真正的入口,走到这里肯定是解压完成的。但是这可能有一个问题,VMP在解压后会把段的内存属性设置成和加壳前相同,一般程序的代码段都是不可写的,这时可以自己修改内存属性或在VMP修改属性前写入补丁。因为这次只修改虚拟机代码,这些代码在VMP自己的段中,这个段始终是可写的,可以不考虑这个问题,等讲完脱壳后你会发现更好的拦截点。下面是找入口,VMP加壳后程序的入口很好找,先重启程序停在模块入口点然后分析,完成后看看记录,会发现这样的提示
&&开始反汇编虚拟程序...
& & 指令块44DB51没有初始化,可能已加壳还没有解压完成,请解压完成后重新分析
&&反汇编完成,75个指令块,7892个指令
因为真正的入口代码是压缩的,分析的时候还没有解压,插件不能继续分析,所以会给出这个提示,这里的44DB51就是真正的入口。只有入口代码也被虚拟化后才这样,如果没有被虚拟化的话就是一个vRet退出虚拟机返回到入口。到44DB51看看004E68CD
vJmp_005BED37 连接 VMPCrack.0044DB51; VMPCrack.0044DB51
DB 00 指令块没有被初始化等壳的入口执行完后就会通过这个转移转到真正的入口,可以在这里插入一段代码写入补丁。如果不想出虚拟机的话可以使用vWriteMemDs来写入,但是直接写虚拟指令的话有点麻烦,如果补丁较长要写大量代码,可以用vCall调用一段汇编。vCall指令好像不是所有版本都有的,如果遇到没有的版本就只能通过vRet来调用了,但是要自己处理真实寄存器的恢复、返回时的虚拟程序初始化等,比较麻烦,还不如直接写虚拟指令了。先在.vmp1段里找一块空位,这个段是不需要解压的,写入补丁代码005C0900
C705 A91472
MOV DWORD PTR DS:[141964
66:C705 AC8A
MOV WORD PTR DS:[A7C
C705 5F964C00 23C8CD07
MOV DWORD PTR DS:[4C965F],7CDC823
MOV WORD PTR DS:[4C
C705 FA9A4C00 A295AD0C
MOV DWORD PTR DS:[4C9AFA],0CAD95A2
66:C705 FE9A4C00 E922
MOV WORD PTR DS:[4C9AFE],22E9
C705 FD7EAEF0
MOV DWORD PTR DS:[467EFD],F0AE1482
66:C705 017F
MOV WORD PTR DS:[467F01],21B4
C705 4D7ECBEF
MOV DWORD PTR DS:[437E4D],EFCBE3B6
66:C705 517E
MOV WORD PTR DS:[437E51],7176
RETN注意保护寄存器,vCall指令不会保护任何寄存器,要自己来做,这里没有用到寄存器所以不用处理。然后在那个转到入口的vJmp前插入指令005C0FFF
vPopReg4 vR12 弹出重定位
C6 49EC81D9
vPushImm4 5C0900 //补丁代码地址
//调用补丁代码,0个参数
vPopReg4 vR10 //弹出返回值
BE 802FF1B7
vPushImm4 0EF74CFD4 被覆盖的指令
vAdd4 被覆盖的指令
vPopReg4 vR10 被覆盖的指令
vPushReg4 vR12 被覆盖的指令
vPushReg4 vR12 重定位
80 5893A8AA
vPushImm4 4E68CF 目标地址
vJmp_005BED37 转到原来的位置先不保存,直接运行试试,提示文件损坏,看来有内存检查。重启一下,先把那段汇编代码填进去,没有问题,可能VMP不检查空位,只检查有效代码。再把虚拟指令的修改加上,这回查到了,设个硬件访问断点试试,断不到,可能被清了,换内存访问断点试试,又提示发现调试器,没办法了,只能看代码了。这些都是壳的入口里检查,还是留到脱壳时再讲吧,先直接给一个地址
005B15C3&&|. /06&&||vJmp_005BED37&&if (GetBytes(v331, 0, 1) != 0) goto &VMPCrack.检测到文件改变&
让他不跳就可以了,这回用插入指令的方法修改吧,因为有很多转移转到这个目标。注意现在直接反汇编入口的话看不到这个指令,因为中间有异常处理,插件不能自动分析,以后再讲。把所有添加的部分(包括插入指令时修改的虚拟机代码)选择复制到可执行文件然后保存,启动保存的文件又提示文件损坏,内存检查不是已经去掉了吗?保存文件后才被查到,那就可能是文件检查,还是看代码吧,改下面的地址
005A9B1F&&|. /6A&&||vJmp_005BED37&&if (GetBytes(v313, 0, 1) != 0) goto &VMPCrack.检测到文件改变&
让他不跳,其他的以后再讲。这回再保存文件,可以正常运行了,但是输入注册码后点确定进程结束,退出代码是DEADC0DE,肯定又被查到了。检查注册码时不是调了SDK吗,把它也改掉,由于这也是压缩的代码,放到上面的补丁里吧。
爆破完成,下面看看能不能找出算法,跟进验证函数里看看,这里贴出的代码还是去掉调用相关代码和垃圾转移后的。
vPopReg4 vR14
vESP = 0FFFFFFFC
vReadMemSs4
DWORD s0 = Stack(vESP + 38, ESP + 4, 4)
vWriteMemSs4
EXIT DWORD v0 = unknownInit2
vWriteMemSs4
EXIT DWORD v1 = unknownInit6
vWriteMemSs4
EXIT DWORD v2 = unknownInit5
vWriteMemSs4
"长度" EXIT DWORD v3 = 10
vWriteMemSs4
"结果" EXIT DWORD v4 = 0FFFFFFFC
vWriteMemSs4
"内容" EXIT DWORD v5 = s0
//用户名MD5
vWriteMemSs4
"密码" EXIT DWORD v6 = 407498 //QWERTYUI
vWriteMemSs4
"加解密" EXIT DWORD v7 = 1
call v13; &VMPCrack.DES&开始就是一个调用,跟进去看看,好像内容很多,没准是通用的算法。用peid打开进程,Krypto ANALyzer插件显示有base64、crc32、des、md5,md5刚才看到了,crc32和base64又不像,那是des?我对算法不是很熟悉,看看能不能找到什么特征,des应该有好几个表。跟进第一个调用里就有
0040122C&&|& /8A86 D0604000 /MOV AL,BYTE PTR DS:[ESI+4060D0]
4060D0的内容是
&&01 01 02 02 02 02 02 02 01 02 02 02 02 02 02 01
找个des代码看看,确实有这个,就是其中的bitDisplace。肯定是des了,加个标签,再把参数提示填上,因为这是调用方弹出参数,插件不会自动添加。遇到这种情况最好添加提示,否则插件不知道函数引用这些参数,可能把他们当成没用的而不显示。也可以在调用的vRet添加/*exitValid(1)*/,这样会认为退出虚拟机后可以引用的数据都是有效的,但不会在代码中标出参数。函数还会把加解密的结果保存到堆栈里,看看参数,地址是0FFFFFFFC就是-4,在vRet添加一个提示/*vStackModify(-4,10,MD5)*/,表示函数把堆栈-4开始10字节内容变成了MD5,以后用到时会自动标出来。参数加解密为1是解密,要解密的内容是从ESP+4读出来的,应该是第一个参数,就是用户名的MD5,密码是QWERTYUI。0042AAF1
vReadMemSs4
DWORD m6 = DWORD SS:[Jnz(~v17 | AddFlag(LoWord(v21), 0FFFF)) + 0FFFFFFD4]
if (Jnz(~v17 | AddFlag(LoWord(v21), 0FFFF))) goto VMPCrack.004DD64A
vReadMemSs4
DWORD s1 = Stack(vESP + 3C, ESP + 8, 4)
vWriteMemSs4
EXIT DWORD v22 = 10
vESP = 0FFFFFFD8
vReadMemSs4
DWORD m7 = DWORD SS:[Jle(v17) + 0FFFFFFD0]
vPushReg4 vR0
DWORD v23 = s1
if (Jg(SubFlag(30, 34) ^ 8C4)) goto VMPCrack.004DBBDF又有两个奇怪的比较,看看分支的代码都很像,可能还是垃圾分支,加提示清除掉。由于第二个垃圾分支在第一个后面,可以直接让第一个跳过去,添加/*jmp(4DD64A)*/后重新反汇编。这里再次提醒重新反汇编前要禁用或删除所有断点,否则断点插入到程序中的vRet会对反汇编造成影响,我就经常忘记,结果反出来很奇怪的代码。现在变成了0042AABF
goto 0ACA0F192 ^ m6
vReadMemSs4
DWORD s1 = Stack(vESP + 3C, ESP + 8, 4)
vWriteMemSs4
EXIT DWORD v22 = 10
//参数"长度"
vWriteMemSs4
EXIT DWORD v23 = 20
//参数"结果"
vWriteMemSs4
EXIT DWORD v24 = 20
//参数"内容",输入的注册码
vWriteMemSs4
EXIT DWORD v25 = 40748C //参数"密码",ASDFGHJK
vWriteMemSs4
EXIT DWORD v26 = 1
//参数"加解密",解密
vReadMemSs4
DWORD m7 = DWORD SS:[Je(AddFlag(5, 0)) + 0FFFFFF78]
vPushReg4 vR2
DWORD v27 = 20
vPushReg4 vR9
DWORD v28 = 5
vPushReg4 vR15
DWORD v29 = s1
if (5 + 0 == 0) goto VMPCrack.0046964E
/vPopReg4 vR13
|vReadMemDs4
DWORD m8 = DWORD DS:[v29]
|vWriteMemEs4
DWORD ES:[v27] = v30; DWORD v30 = m8
DWORD v31 = 0FFFFFFFC + ((0FFFFFBFF ~& v17) && 7)
|vReadMemSs4
DWORD m9 = DWORD SS:[Jnz(AddFlag(v28, 0FFFFFFFF)) + 0FFFFFF78]
|vPushReg4 vR7
DWORD v27 = v27 + v31
|vPushReg4 vR0
DWORD v28 = v28 + 0FFFFFFFF
|vPushReg4 vR11
DWORD v29 = v29 + v31
if (v28 != 0) goto VMPCrack.0043807C
vPopReg4 vR12
call v37; &VMPCrack.DES&有一个循环,前面还有一个判断if (5 + 0 == 0),常量和常量比较?看看是干什么的。循环里有vWriteMemEs4,一般很少用到ES段,可能是MOVS之类的指令。地址每次循环增加0FFFFFFFC + ((0FFFFFBFF ~& v17) && 7),很奇怪的计算,往前看看v17的值是SubFlag(30, 34) ^ 8C4,用标志来计算地址?既然是MOVS之类的指令前面很可能有REP,0FFFFFBFF二进制是,第10位为0,对应DF(方向标志),和标志与非再右移7等于取出DF取反并设置到第3位,结果是0或8,再加上0FFFFFFFC(-4)就是-4或4。这就明白了,这个循环就是REP MOVS,根据方向标志每次对地址加4或减4,因为长度已知(5个DWORD),所以前面的判断显示为常量。
因为循环中写入的地址不是常量,插件不会把对应的数据添加到已知数据中,应该在写入的位置添加一个提示/*vStackModify(20,14,"注册码")*/,表示这个指令把堆栈20开始的14字节设为注册码。如果有写入地址不是常量的内存而你又知道地址的话,最好填上vStackModify或vMemModify提示,否则后面分析到这个地址的时候可能不知道数据已经改变。如果改变前数据没有初始化还好,直接生成一个读堆栈或读内存操作,否则分析时会认为数据还是初始化的内容,得到错误的结果,尤其是有循环的时候。比如int a[4] = {0};//a初始化为0
for (int i = 0; i & 4; i++)
a[i] = 1;//把a中所有的值设为1,由于这里i不是常量,不会把这个赋值添加到已知数据中
b = a[0];//这里会认为a中的值还是0遇到这种情况只要在对a赋值的地方填上vStackModify提示就可以了,因为循环4次,每次写4字节,应该填写vStackModify(a的地址,10,写入内容(可不填))。
后面又是des,因为参数压栈和调用隔着一个循环,没有自动标出,插件只能标出和调用在同一个指令块中的参数。地址是20,就是刚才那个循环复制的内容,ESP+8读出来的,应该是第二个参数,看看内容是5678,是我们输入的注册码,这里是要把输入的注册码解密。0048DF12
vWriteMemSs1
BYTE v46 = GetBytes(MD5, 8, 1) + GetBytes(MD5, 0C, 1) + GetBytes(MD5, 4, 1) + GetBytes(MD5, 0, 1) ^ 12
vReadMemSs4
DWORD v47 = GetBytes(MD5, 4, 4)
vWriteMemSs1
BYTE v48 = GetBytes(MD5, 1, 1) - GetBytes(MD5, 0D, 1) - GetBytes(MD5, 9, 1) - GetBytes(MD5, 5, 1) ^ 34
vWriteMemSs1
BYTE v49 = LoByte(ERROR("")) + 56
vWriteMemSs1
BYTE v50 = (GetBytes(MD5, 0F, 1) ^ GetBytes(MD5, 0B, 1) ^ GetBytes(MD5, 7, 1) ^ GetBytes(MD5, 3, 1)) - 78
vWriteMemSs4
EXIT DWORD v51 = (GetBytes(MD5, 0, 4) ^ v50 : (v49 : (v48 : v46))) + (GetBytes(MD5, 8, 4) ^ v47) + (GetBytes(MD5, 0C, 4) ^ )
vWriteMemSs4
"结果" EXIT DWORD v52 = 0C
vWriteMemSs4
"密码" EXIT DWORD v53 = 0FFFFFFFC
//解密后的MD5
vWriteMemSs4
"内容" EXIT DWORD v54 = 0C
//上面计算的内容
vJmp_00425E0A
callVM &VMPCrack.TEA加密&
vReadMemSs4
DWORD v57 = v50 : (v49 : (v48 : v46))
vReadMemSs4
DWORD v58 = Cross(GetBytes("注册码", 0, 4), nonentity)
vReadMemSs4
DWORD m16 = DWORD SS:[Jnz(SubFlag(v57, v58)) + 0FFFFFFE8]
if (v57 == v58) goto VMPCrack. //检查注册码第一部分
vReadMemSs4
DWORD s2 = Stack(vESP + 34, ESP + 0, 4)
vPushReg4 vR0
EAX DWORD v62 = GetBytes(v58, 1, 3) : 0
return v61
vPopReg4 vR14
vReadMemSs4
DWORD v70 = Cross(GetBytes("注册码", 4, 4), nonentity)
vReadMemSs4
DWORD m21 = DWORD SS:[Jnz(SubFlag(v51, v70)) + 0FFFFFFE8]
if (v51 == v70) goto VMPCrack.004318DE //检查注册码第二部分
vReadMemSs4
DWORD s3 = Stack(vESP + 34, ESP + 0, 4)
vPushReg4 vR2
EAX DWORD v74 = GetBytes(v70, 1, 3) : 0
return v73上面那几个计算应该不用讲了吧,因为前面添加了提示,连内容都标出来了。有一个LoByte(ERROR("")) + 56,这可能是未知指令造成的,未知指令自动分析得到的信息会把操作表达式设为错误。往前看看那个错误表达式是哪里来的,找到
0047FE58&&|.&&A7&&未知指令&&WORD _t13010 = ERROR(""); DWORD _t13011 = ERROR("")
确实是未知指令,从主菜单里打开虚拟指令窗口,找到这个指令0040CF8A
/MOV DL,BYTE PTR SS:[EBP] 未知指令
|MOV AL,BYTE PTR SS:[EBP+2]
|SUB EBP,2
|MOV WORD PTR SS:[EBP+4],AX
\POP DWORD PTR SS:[EBP]有IMUL DL,应该是1字节有符号乘法,指令信息里没有这个指令,添加一下。特征用
MOV WORD PTR [EBP+4],AX
把指令信息中的分析虚拟机时自动获取选上,这样会自动分析指令信息。填写完后保存指令信息文件然后重启OD,因为分析时使用指令索引,如果在中间添加指令的话后面的索引都会改变,导致分析结果中的指令错位,已经开始分析后只能在最后添加指令,否则需要重启OD。重启完后先分析虚拟机,这回没有未知指令了,看看指令信息,已经分析出来了。有两个写操作数,填上操作表达式,分别为op1 ** op2和ImulFlag(op1, op2),记得点保存。然后再分析虚拟程序,变成了
00483EB5&&|.&&38&&vWriteMemSs1&&BYTE v49 = LoByte(LoByte(LoByte(GetBytes(MD5, 0E, 1) ** GetBytes(MD5, 0A, 1)) ** GetBytes(MD5, 6, 1)) ** GetBytes(MD5, 2, 1)) + 56
有一个调用,跟进去看看0045E54B
vPopReg4 vR13
vReadMemSs4
DWORD s0 = Stack(vESP + 3C, ESP + 8, 4) //参数2
vReadMemDs4
DWORD m0 = DWORD DS:[s0]
vReadMemDs4
DWORD m1 = DWORD DS:[s0 + 4]
vWriteMemSs4
EXIT DWORD v0 = m1
vReadMemDs4
DWORD m2 = DWORD DS:[s0 + 8]
vReadMemDs4
DWORD m3 = DWORD DS:[s0 + 0C]
vReadMemSs4
DWORD s1 = Stack(vESP + 38, ESP + 4, 4) //参数1
vReadMemDs4
DWORD m4 = DWORD DS:[s1]
vReadMemDs4
DWORD m5 = DWORD DS:[s1 + 4]
vPushReg4 vR4
DWORD v1 = m5 ^ 2B3C4D5E
vPushReg4 vR11
DWORD v2 = 10
//循环次数,0x10(16)
vPushReg4 vR9
DWORD v3 = 0
vPushReg4 vR0
DWORD v4 = m4 ^ 1A2B3C4D
/vPopReg4 vR6
DWORD v5 = v3 - 61C88647
DWORD v6 = (v1 && 5) + v0
DWORD v7 = v1 && 4
DWORD v8 = m0 + v7 ^ v6
DWORD v9 = v1 + v5
DWORD v10 = v8 ^ v9
DWORD v11 = v4 + v10
|vReadMemSs4
DWORD m6 = DWORD SS:[Jnz(DecFlag(v2)) + 10]
|vPushReg4 vR6
DWORD v1 = v1 + ((v11 && 5) + m3 ^ (v11 && 4) + m2 ^ v11 + v5)
|vPushReg4 vR9
DWORD v2 = v2 + 0FFFFFFFF
|vPushReg4 vR15
DWORD v3 = v5
|vPushReg4 vR11
DWORD v4 = v11
\vJmp_00425E0A
if (Jnz(DecFlag(v2))) goto VMPCrack.004D8832
DWORD v12 = v4 ^ 4D3C2B1A
vReadMemSs4
DWORD s2 = Stack(vESP + 40, ESP + 0C, 4) //参数3
DWORD v13 = v1 ^ 5E4D3C2B
vWriteMemDs4
DWORD DS:[s2] = v14; DWORD v14 = v12
//保存结果
vWriteMemDs4
DWORD DS:[s2 + 4] = v15; DWORD v15 = v13 //保存结果
vReadMemSs4
DWORD s3 = Stack(vESP + 34, ESP + 0, 4)
return v18有3个参数,都是指针,参数1两个DWORD,参数2四个DWORD,经过一个0x10次的循环计算出两个DWORD后保存到参数3中。由于加了变形,垃圾转移分隔了一部分操作,循环中的计算连起来是v5 = v5 - 61C88647
v11 = v11 + ((v1
&& 5) + m1 ^ (v1
&& 4) + m0 ^ v1
+ ((v11 && 5) + m3 ^ (v11 && 4) + m2 ^ v11 + v5)看起来很熟悉,尤其是那个常量,对,就是tea加密的代码,参数1是内容,参数2是密码,参数3是保存结果。但是循环前后好像多了一些东西,加密前和加密后都异或了一个常量,可能是修改过的tea,填上标签和参数提示再回去看刚才的代码。现在参数已经标出来了,密码地址是0FFFFFFFC,就是第一次des的结果,用户名md5。要加密的内容地址是0C,好像没看到过这个地址,往前看看,前面那几个计算,就是变量v46、v48、v49、v50、v51,都保存在0C开始的8字节以内,这就是要加密的内容。后面是两个比较,第一个是if (v57 == v58),看看变量内容,是检查 v50 : (v49 : (v48 : v46)) == Cross(GetBytes("注册码", 0, 4), nonentity),就是注册码的第一个DWORD。因为注册码是在最开始的那个循环里复制过来的,但是前面有一个分支跳过循环,也就是说这个循环可能不会执行(其实不是,因为比较是常量,但插件不知道),这个路径中注册码没有定义,所以产生了一个交汇,就是说这里可能是"注册码"或nonentity(不存在的变量)。第二个比较是if (v51 == v70),检查注册码的第二个DWORD。
下面有一个垃圾分支
0043505F&&|. /06&&vJmp_&&if (0FFFFFF9C + 50 &= 0) goto VMPCrack.00482F6A
添加提示/*jmp(482F6A)*/清除掉,重新反汇编后再看0043183F
vReadMemSs4
DWORD v82 = GetBytes(MD5, 0, 4)
vReadMemSs4
DWORD v83 = GetBytes(MD5, 0C, 4)
vReadMemSs4
DWORD v84 = GetBytes(MD5, 8, 4)
vWriteMemSs1
EXIT BYTE v85 = (GetBytes(v51, 0, 1) ^ v46) + (GetBytes(MD5, 0, 1) ^ 0AA)
vReadMemSs4
DWORD v86 = GetBytes(MD5, 4, 4)
vWriteMemSs1
EXIT BYTE v87 = (GetBytes(v51, 1, 1) ^ v48) + (GetBytes(MD5, 5, 1) ^ 0BB)
vWriteMemSs1
EXIT BYTE v88 = (v49 ^ GetBytes(v51, 2, 1)) + (GetBytes(MD5, 0A, 1) ^ 0CC)
vWriteMemSs1
EXIT BYTE v89 = (GetBytes(MD5, 0F, 1) ^ 0DD) + (GetBytes(v51, 3, 1) ^ v50)
DWORD v90 = 0 : (v82 ^ (v83 ^ v84 ^ v86)) % 5
vReadMemSs4
DWORD m27 = DWORD SS:[Ja(SubFlag(v90, 4)) + 0FFFFFFE8]
vPushReg4 vR11
DWORD v91 = v84
if (v90 & 4) goto VMPCrack.
vReadMemDs4
DWORD m28 = DWORD DS:[(v90 && 2) + 40609C]
switch (v90)
vPopReg4 vR2
//switch 0
vWriteMemSs4
EXIT DWORD v92 = (v82 ^ ) + (v86 ^ )
vPopReg4 vR6
//switch 1
DWORD v93 = v91 ^
vWriteMemSs4
EXIT DWORD v92 = v93 + (v86 ^ )
vPushReg4 vR12
DWORD v91 = v93
vPopReg4 vR4
//switch 2
DWORD v94 = v91 ^
vWriteMemSs4
EXIT DWORD v92 = v94 + (v83 ^ )
vPushReg4 vR1
DWORD v91 = v94
vPopReg4 vR1
//switch 3
vWriteMemSs4
EXIT DWORD v92 = (v83 ^ 778899AA) + (v82 ^ 8899AABB)
vPopReg4 vR8
//switch 4
DWORD v95 = v91 ^ 0AABBCCDD
DWORD v96 = v82 ^ 99AABBCC
DWORD v97 = v96 + v95
vWriteMemSs4
EXIT DWORD v92 = v97
vPushReg4 vR4
DWORD v91 = v97
vPopReg4 vR12
vWriteMemSs4
"结果" EXIT DWORD v98 = 14
vWriteMemSs4
"密码" EXIT DWORD v99 = 0FFFFFFFC
//解密后的MD5
vWriteMemSs4
"内容" EXIT DWORD v100 = 14
//上面计算的内容
vPushReg4 vR9
EXIT DWORD v101 = v91
EXIT DWORD v102 = 0EF8B1AAC
callVM &VMPCrack.TEA解密&
vReadMemSs4
DWORD v103 = Cross(GetBytes("注册码", 8, 4), nonentity)
vReadMemSs4
DWORD v104 = v89 : (v88 : (v87 : v85))
DWORD v105 = v103 ^ 13579BDF
vReadMemSs4
DWORD m29 = DWORD SS:[Je(SubFlag(v104, v105)) + 0FFFFFFE8]
if (v104 != v105) goto VMPCrack.00437E53
//检查注册码第三部分
vReadMemSs4
DWORD v106 = Cross(GetBytes("注册码", 0C, 4), nonentity)
DWORD v107 = v106 ^ 0FDB97531
vReadMemSs4
DWORD m30 = DWORD SS:[Jnz(SubFlag(Cross(nonentity, v92), v107)) + 0FFFFFFE8]
if (Cross(nonentity, v92) == v107) goto VMPCrack.004A927A//检查注册码第四部分
vReadMemSs4
DWORD s4 = Stack(vESP + 34, ESP + 0, 4)
return v110这部分因为有一个switch,可能乱一点。一开始是把MD5的4个DWORD做异或,然后除以5取余数作为switch索引选择一个分支,每个分支有不同的算法。索引对应的分支可以看看switch的注释,第一个目标是0,第二个是1,依此类推。后面又是一个调用,是tea解密,代码和加密很像,也是前后加了异或,就不贴出来了。下面是两个比较,分别检查注册码的第三和第四个DWORD,算法应该很容易看出来了。这次不是直接和解密后的注册码比较,而是先把注册码异或了一个常量。第二个比较有一个Cross(nonentity, v92),也是因为switch前有一个分支跳了过去,这个路径没有变量定义。004C04FE
vWriteMemSs4
"长度" EXIT DWORD v119 = 14
vWriteMemSs4
"内容" EXIT DWORD v120 = 20 //输入的注册码
vPushReg4 vR10
EXIT DWORD v121 = XorFlag(Cross(GetBytes("注册码", 4, 4), nonentity) + Cross(GetBytes("注册码", 0, 4), nonentity), v103 + v106)
EXIT DWORD v122 = 0EF8B1AAC
vJmp_00411C30
callVM &VMPCrack.CRC&
vReadMemSs4
DWORD m35 = DWORD SS:[Je(SubFlag(entryVMEax_45CED7, entryVMEsi_45CED7)) + 0FFFFFFE8]
if (entryVMEax_45CED7 != entryVMEsi_45CED7) goto VMPCrack.00467F03
EXIT DWORD v123 = 0EF8B1AAC
vJmp_00411C30
callVM VMPCrack.0046BB01 //调用SDK,解密字符串
vWriteMemDs4
DWORD DS:[407470] = v124; EXIT DWORD v124 = entryVMEax_4689BB
vReadMemSs4
DWORD s5 = Stack(vESP + 34, ESP + 0, 4)
return v127开始是一个调用,跟进去看看,第一个转移是垃圾分支
&&|. /2B&&vJmp_00411C30&&if (Ja(1 ^ unknownInit2)) goto VMPCrack.004C9F2B
添加提示/*jmp(4C9F2B)*/清除掉,重新反汇编0042C5FB
vPopReg4 vR6
vReadMemSs4
DWORD s0 = Stack(vESP + 3C, ESP + 8, 4) //参数2
vReadMemSs4
DWORD m1 = DWORD SS:[Jle(AndFlag(s0, s0)) + 28]
vPushReg4 vR14
DWORD v0 = s0
vPushReg4 vR12
DWORD v1 = unknownInit10
vPushReg4 vR11
DWORD v2 = unknownInit4
vPushReg4 vR4
DWORD v3 = unknownInit9 | 0FFFFFFFF
vPushReg4 vR9
DWORD v4 = 0
vJmp_00411C30
if (s0 &= 0) goto VMPCrack.
vReadMemSs4
DWORD s1 = Stack(vESP + 38, ESP + 4, 4) //参数1
vPushReg4 vR13
DWORD v5 = s1
/vPopReg4 vR9
|vPushReg4 vR2
DWORD v6 = 0 : ((GetBytes(v3, 1, 1) ^ 0) + 1)
|vReadMemDs1
BYTE m2 = BYTE DS:[v5 + v4]
|vReadMemDs4
DWORD m3 = DWORD DS:[((v6 : m2 ^ v3 & 0FF) && 2) + 407030]
|vReadMemSs4
DWORD m4 = DWORD SS:[Jge(SubFlag(v4 + 1, v0)) + 20]
|vPushReg4 vR7
DWORD v3 = m3 ^ v3 && 8
|vPushReg4 vR6
DWORD v4 = v4 + 1
|vPushReg4 vR10
DWORD v1 = m3
\vJmp_00411C30
if (v4 & v0) goto VMPCrack.004AC8BD
vPopReg4 vR1
vReadMemSs4
DWORD s2 = Stack(vESP + 34, ESP + 0, 4)
vPushReg4 vR13
EAX DWORD v12 = v3 ^ 6789ABCD
return v9先检查第二个参数是否大于0,然后是一个循环,次数由第二个参数控制,第一个参数是指针,这里可以看出来第二个参数就是内容长度。大部分计算都很明显,但是有一个
DWORD DS:[((v6 : m2 ^ v3 & 0FF) && 2) + 407030],v6长度是4字节,又连接上一个1字节的m2是5字节?v6的高3字节是0,我觉得可能是0:m2,这里的分析有点问题。循环里的代码连起来是
DWORD DS:[((BYTE DS:[v5 + v4] ^ v3 & 0FF) && 2) + 407030] ^ v3 && 8
好像是crc,看看407030的内容
00 00 00 96 30 07 77 2C 61 0E EE BA 51 09 99
很明显是crc使用的表了,刚才peid的Krypto ANALyzer插件显示的crc就是这个地址,但还是有点不一样,crc结果最后应该取反,这里是异或6789ABCD,可能又是修改过的算法,异或6789ABCD相当于取反后再异或。填上标签和参数提示再回去看刚才的代码。
然后是一个比较if (entryVMEax_45CED7 != entryVMEsi_45CED7),eax是调用crc的返回值,esi是什么?给crc函数加上初始寄存器提示吧,/*initReg(esi,0C)*/。调用前还有一个
v121 = XorFlag(Cross(GetBytes("注册码", 4, 4), nonentity) + Cross(GetBytes("注册码", 0, 4), nonentity), v103 + v106)
这是异或的标志,但是前面没见过这个计算,调用前计算的,很可能是调用需要的内容,没准是要计算crc的部分,但是插件不能自动分析被调用函数,不知道他引用了这个数据,所以认为是无效的了。给调用crc的vJmp加上一个/*exitValid(1)*/提示,表示调用退出虚拟机时可访问的数据都是有效的,再重新分析一下试试,这回变成了004A8FBE
vReadMemSs4
DWORD v120 = Cross(GetBytes("注册码", 0, 4), nonentity)
DWORD v121 = v104 + v107 //v104 = GetBytes("注册码", 8, 4),v107 = GetBytes("注册码", 0C, 4)
DWORD v122 = Cross(GetBytes("注册码", 4, 4), nonentity) + v120
vWriteMemSs4
"长度" EXIT DWORD v123 = 14
vWriteMemSs4
EXIT DWORD v124 = v122 ^ v121
vWriteMemSs4
"内容" EXIT DWORD v125 = 20 //输入的注册码,第5个DWORD替换成了v124
vPushReg4 vR15
EXIT DWORD v136 = Cross(GetBytes("注册码", 10, 4), v0)
vPushReg4 vR10
EXIT DWORD v137 = XorFlag(v122, v121)
EXIT DWORD v138 = 0EF8B1AAC
vJmp_00411C30
callVM &VMPCrack.CRC&
vReadMemSs4
DWORD m35 = DWORD SS:[Je(SubFlag(entryVMEax_45CED7, v136)) + 0FFFFFFE8]
if (entryVMEax_45CED7 != v136) goto VMPCrack.00467F03//检查注册码第五部分
EXIT DWORD v139 = 0EF8B1AAC
vJmp_00411C30
callVM VMPCrack.0046BB01 //调用SDK,解密字符串
vWriteMemDs4
DWORD DS:[407470] = v140; EXIT DWORD v140 = entryVMEax_4689BB
vReadMemSs4
DWORD s5 = Stack(vESP + 34, ESP + 0, 4)
return v143现在出来了,原来是把输入的注册码第5个DWORD替换成了GetBytes("注册码", 0, 4)+GetBytes("注册码", 4, 4) ^ GetBytes("注册码", 8, 4)+GetBytes("注册码", 0C, 4)。由于后面没有引用这个数据,插件认为是无效的,所以没有显示。如果被调用函数引用了堆栈或内存的话,最好填上stackRef或memRef提示,也可以更简单直接在调用的地方填exitValid(1),否则函数引用的数据可能被作为无效的而不显示。这是最后一个比较了,先取出输入的注册码的第5个DWORD,替换成上面的内容,再计算crc,和刚才取出的值比较,如果相等的话就解密要显示的字符串并返回成功。
现在验证过程已经知道了,反过来就是注册码的计算方法,注册码一共5个DWORD,计算方法为
md5=DES解密(MD5(用户名), QWERTYUI)
注册码1.1=(md51.1+md52.1+md53.1+md54.1)^12
注册码1.2=(md51.2-md52.2-md53.2-md54.2)^34
注册码1.3=(md51.3*md52.3*md53.3*md54.3)+56
注册码1.4=(md51.4^md52.4^md53.4^md54.4)-78
注册码2=(注册码1^md51) + (md52^md53) + (md54^)
注册码1:2=TEA加密(注册码1:2 ^ 1A2B3C4D:2B3C4D5E, md5) ^ 4D3C2B1A:5E4D3C2B
注册码3.1=(注册码1.1^注册码2.1) + (md51.1^AA)
注册码3.2=(注册码1.2^注册码2.2) + (md52.2^BB)
注册码3.3=(注册码1.3^注册码2.3) + (md53.3^CC)
注册码3.4=(注册码1.4^注册码2.4) + (md54.4^DD)
注册码4=switch((md51^md52^md53^md54) % 5)
& & & & (md51^) + (md52^)
& & & & (md52^) + (md53^)
& & & & (md53^) + (md54^)
& & & & (md54^778899AA) + (md51^8899AABB)
& & & & (md51^99AABBCC) + (md53^AABBCCDD)
注册码3:4=TEA解密(注册码3:4 ^ A1B2C3D4:B2C3D4E5, md5) ^ D4C3B2A1:E5D4C3B2
注册码3=注册码3^13579BDF
注册码4=注册码4^FDB97531
注册码5=(注册码1+注册码2) ^ (注册码3+注册码4)
注册码5=CRC(注册码1:24:5) ^
注册码1:24=DES加密(注册码1:24, ASDFGHJK)下面讲一下VMP的脱壳吧,这是我脱的第一个壳,原来一直感觉脱壳没什么用,带壳分析带壳修改对大部分程序都可以,这次想测试插件才研究了一下VMP脱壳。我不会脱壳,方法都是自己想的,可能不是很好。在插件的帮助下很快就把VMP加壳的原理和各种检测看懂了,感觉还不是太难,只是一些修复比较麻烦。先从入口跟踪一下,看看VMP都做了什么。005BB9EE
vPopReg4 vR10
vReadMemSs4
DWORD m0 = DWORD SS:[Je(SubFlag(0, 0)) + 28]
vJmp_005BED37
if (0 != 0) goto VMPCrack.005B820C第一个比较是if (0 != 0),常量和常量比较一般是其中的变量值在编译时已知,或者是在内存中而且插件认为内存是常量。由于VMP中经常读取虚拟机的一部分来计算常量,现在认为内存段中有虚拟机代码或属性为只读、可执行,或包含常量内容比如代码、输入表等,这个段中的内容都是常量,这可能把一些变量当作常量,出现上面的情况。向上跟踪一下数据来源,找到
005BB9CF&&|.&&33&&vReadMemDs1&&内存005ABAAF:(常量)0; 堆栈2A:(常量)5ABAAF
确实是内存里的,而且在包含虚拟机的段里,可以在4E6000也就是5ABAAF所在的段的开始添加/*memConst(0,5ABAAF,1)*/来告诉插件这不是常量,或者不理他,反正已经看懂了。添加提示后变成了005BB9EE
vPopReg4 vR10
vReadMemDs1
BYTE m0 = BYTE DS:[5ABAAF]
vReadMemSs4
DWORD m1 = DWORD SS:[Je(SubFlag(m0, 0)) + 28]
vJmp_005BED37
if (m0 != 0) goto VMPCrack.005B820C如果5ABAAF不等于0就直接跳到真正的入口执行,插件在初始化完成后会设为1。005B1EB0
vPopReg4 vR10
vPushReg4 vR11
EXIT DWORD v0 = EAX
vPushReg4 vR12
EXIT DWORD v1 = EBX
vPushReg4 vR15
EXIT DWORD v2 = ECX
vPushReg4 vR0
EXIT DWORD v3 = EDX
vPushReg4 vR5
EXIT DWORD v4 = EBP
vPushReg4 vR2
EXIT DWORD v5 = ESI
vPushReg4 vR14
EXIT DWORD v6 = EDI
vPushImmSx1 0
EXIT DWORD v7 = 0
vPushImmSx1 0
EXIT DWORD v8 = 0
vPushImmSx1 0
EXIT DWORD v9 = 0
vESP = 0FFFFF808
EFL = 0FFFFF700 & (0FFFFFBFF & SubFlag(8, 800))
vWriteMemDs4
DWORD DS:[5BFE27] = v10; EXIT DWORD v10 = 0EF74C7DC
vWriteMemDs4
EXIT DWORD v11 = 6E72656B
vWriteMemDs4
EXIT DWORD v12 = 32336C65
7B 1DC65B00
vPushImm4 5BC61D
DWORD v13 = 5BC61D
vWriteMemDs4
EXIT DWORD v14 = 6C6C642E
vWriteMemDs4
EXIT DWORD v15 = 0
vPushReg4 vR15
DWORD v16 = ECX
vPushReg4 vR0
DWORD v17 = EDX
vPushReg4 vR14
DWORD v18 = EDI
vPushReg4 vR6
DWORD v19 = 0FFFFFFF8
vPushReg4 vR8
DWORD v20 = 8
DWORD v21 = 0EF74C7DC
vJmp_005BED37
goto VMPCrack.005B13EC
/vPopReg4 vR0
|vPushReg4 vR4
ARG1 DWORD v22 = v19
v23 = Call(5B15BC); &JMP.&KERNEL32.LoadLibraryA&
|vPushReg4 vR11
DWORD v24 = v23
|vPushReg4 vR4
DWORD v25 = v19
|vPushReg4 vR13
DWORD v26 = v18
DWORD v27 = 0EF74CFD4 + (108B302C + v21)
|vJmp_005BED37
//动态转移前面是保存一些寄存器,然后生成了kernel32.dll的文件名,调用LoadLibraryA加载dll,准备取壳自己用的api地址。005BC61D
|vPopReg4 vR14
51 F9A45A00
|vPushImm4 5AA4F9
DWORD v28 = 5AA4F9
|vWriteMemDs4
DWORD DS:[v25 + 0FFFFFFF0] = v29; DWORD v29 = 0FC7CBF83//加密的函数名
|vWriteMemDs4
DWORD DS:[v25 + 0FFFFFFF0 + 4] = v30; DWORD v30 = 5FEC1BC
|vWriteMemDs4
DWORD DS:[v25 + 0FFFFFFF0 + 8] = v31; DWORD v31 = 0C0FC3D7C
|vWriteMemDs4
DWORD DS:[v25 + 0FFFFFFF0 + 0C] = v32; DWORD v32 = 19FC40
|vPushReg4 vR0
DWORD v33 = v25 + 0FFFFFFF0
DWORD v34 = 0EF74CFD4 + (108B302C + v27)
|vJmp_005BED37
goto VMPCrack.005AD32F
|/vPopReg4 vR12
||vPushReg4 vR4
ARG2 DWORD v35 = v33
//加密的函数名
||vPushReg4 vR15
ARG1 DWORD v36 = v24
v37 = Call(5AB3FD); &VMPCrack.输入函数地址&
||vReadMemSs4
DWORD m2 = DWORD SS:[Je(AndFlag(v37, v37)) + 0FFFFF7FC]
||vPushReg4 vR4
DWORD v18 = v33
DWORD v38 = 108B302C + v34 + 0EF74CFD4
||vJmp_005BED37
if (v37 != 0) goto VMPCrack.005AD670 //检查是否成功
||vPopReg4 vR6
||vPushReg4 vR9
EXIT DWORD v39 =
EXIT DWORD v40 = 108B302C + v38 + 0EF74CFD4
||vJmp_005BED37
callVM VMPCrack.005B383A
||vPopReg4 vR5
||vPopVEsp
vESP = 0FFFFF804
||vPushReg4 vR1
DWORD v41 = entryVMEcx_5BC6C0
||vPushReg4 vR15
DWORD v42 = entryVMEax_5BC6C0
||vPushReg4 vR3
DWORD v43 = entryVMEbp_5BC6C0
||vPushReg4 vR9
DWORD v44 = entryVMEdx_5BC6C0
DWORD v45 = 0
||vJmp_005BED37
goto VMPCrack.005BB560
||vPopReg4 vR13
||vReadMemDs1
BYTE m3 = BYTE DS:[v37]
//读取第一个字节
||vReadMemSs4
DWORD m4 = DWORD SS:[Jnz(SubFlag(m3, 0CC)) + 0FFFFF7FC]
DWORD v46 = 108B302C + v38 + 0EF74CFD4
||vJmp_005BED37
if (m3 == 0CC) goto &VMPCrack.发现调试器& //检查是否设了INT3断点
||vPopReg4 vR14
DWORD v47 = 108B302C + v46 + 0EF74CFD4
||vJmp_005BED37
//动态转移,取下一个函数5AD2F9有一个vCall调用,加了变形,有两个参数,其中一个是刚才加载的dll,调试发现执行后返回了一个api地址,应该是取输入函数用的,另一个参数是加密过的函数名。然后检查是否成功,不成功的话执行一个调用callVM VMPCrack.005B383A,生成显示信息
The procedure entry point "函数名" could not be located in the dynamic link library "dll名"
还会读取第一个字节检查是否等于CC,就是INT3断点,如果检测到断点就转到发现调试器然后退出。005B9D4D是发现调试器时的处理,有很多vWriteMemDs4,实际是写入了字符串
A debugger has been found running in your system.
Please, unload it from memory and restart your program.
然后显示信息并结束程序。
取完api地址就开始检测了,一共有三类检测,检测虚拟机、检测调试器、检测文件和内存改变。我对反调试了解的不多,一般都是用插件过反调试,现在只是观察VMP的行为,所以有些解释不一定准确。这里有很多异常处理,选上进入虚拟机时中断,免得产生异常时跑丢了。
开始是一个联机指令005AA297&&|. /2E&&|vRet&&online 5BC287; VMPCrack.005BC287; STR WORD PTR SS:[ESP]
没见过这个指令,查了一下是存储任务寄存器,因为写入了堆栈,在5BC287加一个分析提示/*stackModify(0,2,STR)*/,然后重新分析。005AA297
online 5BC287; VMPCrack.005BC287; STR WORD PTR SS:[ESP]
|vPopReg4 vR3
DWORD v171 = SubFlag(LoWord(STR), 4000)
|vReadMemSs4
DWORD m6 = DWORD SS:[Je(v171) + 0FFFFF800]
|vJmp_005BED37
if (LoWord(STR) == 4000) goto &VMPCrack.发现虚拟机&检查保存的值是否为4000,是的话转到发现虚拟机,也是一堆vWriteMemDs4写入字符串
Sorry, this application cannot run under a Virtual Machine.
然后结束程序。摘一段&&虚拟机检测技术剖析&&里的解释这里STR(Store task register)指令是用于将任务寄存器 (TR) 中的段选择器存储到目标操作数,目标操作数可以是通用寄存器或内存位置,使用此指令存储的段选择器指向当前正在运行的任务的任务状态段 (TSS)。在虚拟机和真实主机之中,通过STR读取的地址是不同的,当地址等于0x0040xxxx时,说明处于虚拟机中,否则为真实主机。又是一个联机指令
005B7AE0&&|. /55&&|vRet&&online 5B3314; VMPCrack.005B3314; IN EAX,DX
再引用一段&&虚拟机检测技术剖析&&里的解释Vmware为真主机与虚拟机之间提供了相互沟通的通讯机制,它使用“IN”指令来读取特定端口的数据以进行两机通讯,但由于IN指令属于特权指令,在处于保护模式下的真机上执行此指令时,除非权限允许,否则将会触发类型为“EXCEPTION_PRIV_INSTRUCTION”的异常,而在虚拟机中并不会发生异常,在指定功能号0A(获取VMware版本)的情况下,它会在EBX中返回其版本号“VMXH”;而当功能号为0x14时,可用于获取VMware内存大小,当大于0时则说明处于虚拟机中。这个联机指令可能会改变ebx,在5B3314添加一个提示/*regModify(ebx)*/重新分析。005B7B04
|vReadMemFs4
v172 = Cross(nonentity, v183)
|vWriteMemFs4
DWORD FS:[0] = v173; v173 = 0FFFFF7FC
|vPushReg4 vR6
EAX DWORD v174 = 564D5868
|vPushReg4 vR15
EBP DWORD v175 = v20
|vPushReg4 vR7
ESI DWORD v176 = v165
|vPushReg4 vR14
EFL DWORD v177 = v171
|vPushReg4 vR9
EDX DWORD v178 = 5658
|vPushReg4 vR8
EDI DWORD v179 = v168
|vPushReg4 vR13
EBX DWORD v180 = 0
|vPushReg4 vR12
ECX DWORD v181 = 0A
online 5B3314; VMPCrack.005B3314; IN EAX,DX
|vPopReg4 vR11
|vJmp_005BED37
goto VMPCrack.005B4219
|vPopReg4 vR1
|vReadMemFs4
v182 = 0FFFFF7FC
vESP = 0FFFFF7FC
|vWriteMemFs4
DWORD FS:[0] = v183; v183 = v172
DWORD v184 = SubFlag(entryVMEbx_5B8B50, 564D5868)
|vReadMemSs4
DWORD m7 = DWORD SS:[Je(v184) + 0FFFFF800]
|vPushReg4 vR0
DWORD v170 = 0A
|vPushReg4 vR10
DWORD v167 = 5658
|vJmp_005BED37
if (entryVMEbx_5B8B50 == 564D5868) goto &VMPCrack.发现虚拟机&先设置异常处理,里面什么都没做,只是把context里的eip设成了5B98BA,是虚拟代码5B41D7的入口,异常处理后返回到这里。然后调用联机指令IN EAX,DX。根据上面的解释,这里是检查ebx是否等于564D5868,就是VMXh。
又是联机指令
005B0E27&&|. /2E&&|vRet&&online 5B8B58; VMPCrack.005B8B58; POPFD
插件只能显示第一个指令,跟进5B8B58看看
005B8B58& && &9D& && && && &&&POPFD
005B8B59& && &0F31& && && && &RDTSC
005B8B5B& && &90& && && && &&&NOP
一共三个指令,POPFD弹出了4字节,还引用了栈顶数据,加上提示/*esp(4);stackRef(0,4)*/。这个联机指令也设置了异常处理,步过的时候会中断到异常处理中,可以在数据窗口中跟随vEIP,选择分析虚拟程序,或者在异常处理的虚拟程序入口分析。切换到显示全部指令,往前看一下在vReadMemFs4和vWriteMemFs4前有
005B0E5D&&|.&&51 759F5A00&&|vPushImm4 5A9F75&&DWORD _t4364 = 5A9F75
这就是异常处理的虚拟程序入口,在这里分析虚拟程序,分析结果是004E6C8D
vPopReg4 vR4
vReadMemSs4
DWORD s0 = Stack(vESP + 40, ESP + 0C, 4) //第三个参数,context
vReadMemDs4
DWORD m0 = DWORD DS:[0B8 + s0]
vReadMemDs1
BYTE m1 = BYTE DS:[m0]
DWORD v0 = SubFlag(m1, 90)
vWriteMemDs1
BYTE DS:[0A4 + s0] = v1; BYTE v1 = Setnz(v0) //检查eip指向的代码是否为90(nop),设置到ebx
return Stack(34, 4)异常处理修改了context中的ebx,在5B8B58再加一个提示,全部内容是/*esp(4);stackRef(0,4);regModify(ebx)*/,重新分析后回去看代码005B0E90
|vPopReg4 vR12
|vReadMemFs4
v185 = v183
|vWriteMemFs4
DWORD FS:[0] = v186; v186 = 0FFFFF7FC
|vWriteMemSs4
EXIT DWORD v187 = v184 | 100
//要弹出的标志,或上了100(单步标志TF)
online 5B8B58; VMPCrack.005B8B58; POPFD
|vPopReg4 vR1
|vJmp_005BED37
goto VMPCrack.005BADDF
|vPopReg4 vR7
|vReadMemFs4
v196 = 0FFFFF7FC
vESP = 0FFFFF7FC
|vWriteMemFs4
DWORD FS:[0] = v183; v183 = v185
DWORD v197 = SubFlag(GetBytes(entryVMEbx_5A906A, 0, 1), 1)
|vReadMemSs4
DWORD m8 = DWORD SS:[Jnz(v197) + 0FFFFF800]
|vPushReg4 vR9
DWORD v170 = 0A
|vPushReg4 vR10
DWORD v167 = 5658
|vJmp_005BED37
if (GetBytes(entryVMEbx_5A906A, 0, 1) == 1) goto &VMPCrack.发现虚拟机&先弹出包含TF的标志,再执行RDTSC,触发异常时检查eip是否在nop指令,不在的话就转到发现虚拟机。查了一下资料,据说是vmware的漏洞,把eip多加了1。
后面还是联机指令
005BC3A6&&|. /2E&&|vRet&&online 5BC652; VMPCrack.005BC652; POPFD
又是POPFD,这回是
005BC652& && &9D& && && && &&&POPFD
005BC653& && &0FA2& && && && &CPUID
005BC655& && &90& && && && &&&NOP
变成了CPUID,看一下异常处理,和上面的检查一样,也设上提示,回去看代码005BC410
|vPopReg4 vR7
|vReadMemFs4
v198 = v183
|vWriteMemFs4
DWORD FS:[0] = v199; v199 = 0FFFFF7FC
|vWriteMemSs4
EXIT DWORD v200 = v197 | 100
online 5BC652; VMPCrack.005BC652; POPFD
|vPopReg4 vR7
|vJmp_005B}

我要回帖

更多关于 主板序列号 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信