逆向再还原技术.docx
《逆向再还原技术.docx》由会员分享,可在线阅读,更多相关《逆向再还原技术.docx(19页珍藏版)》请在冰豆网上搜索。
逆向再还原技术
逆向还原代码技术
(SOHU畅游公司顾晓波)
IDA和C++BUILDER是同构汇编代码
这里先看个简单的例子
先看C/C++原始程序:
#include
intt1(ints)
{
ints0=10;
ints2;
s2=s0+s-2;
returns2;
}
intmain(void)
{
intnTest1=1;
intnTest2=2;
intnTest3;
inti;
nTest3=nTest1+nTest2+t1(nTest2);
printf("nTest3=%d\r\n",nTest3);
for(i=0;i<5;i++)
{
printf("%d\r\n",nTest3+i);
}
printf(__TIME__""__DATE__"\r\nhelloworld\n");
return0;
}
下面是sample1.exe的IDA反汇编的结果:
IDA里的逆向main代码:
.text:
00401000;int__cdeclmain(intargc,constchar**argv,constchar*envp)
.text:
00401000_mainprocnear;CODEXREF:
mainCRTStartup+16E_p
.text:
00401000
.text:
00401000argc=dwordptr4
.text:
00401000argv=dwordptr8
.text:
00401000envp=dwordptr0Ch
.text:
00401000
.text:
00401000pushesi
.text:
00401001push0Dh
.text:
00401003pushoffsetaNtest3D;"nTest3=%d\r\n"
.text:
00401008callprintf
.text:
0040100Daddesp,8
.text:
00401010xoresi,esi
.text:
00401012
.text:
00401012loc_401012:
;CODEXREF:
_main+27_j
.text:
00401012leaeax,[esi+0Dh]
.text:
00401015pusheax
.text:
00401016pushoffsetaD;"%d\r\n"
.text:
0040101Bcallprintf
.text:
00401020addesp,8
.text:
00401023incesi
.text:
00401024cmpesi,5
.text:
00401027jlshortloc_401012
.text:
00401029pushoffseta102134Mar32010;"10:
21:
34Mar32010\r\nhelloworld\n"
.text:
0040102Ecallprintf
.text:
00401033moveax,dword_409044
.text:
00401038addesp,4
.text:
0040103Bdeceax
.text:
0040103Cpopesi
.text:
0040103Dmovdword_409044,eax
.text:
00401042jsshortloc_40104D
.text:
00401044inc_iob
.text:
0040104Axoreax,eax
.text:
0040104Cretn
.text:
0040104D;---------------------------------------------------------------------------
.text:
0040104D
.text:
0040104Dloc_40104D:
;CODEXREF:
_main+42_j
.text:
0040104Dpushoffset_iob
.text:
00401052call_filbuf
.text:
00401057addesp,4
.text:
0040105Axoreax,eax
.text:
0040105Cretn
.text:
0040105C_mainendp
.text:
0040105C
C++BUILDER6.0恢复后的代码:
#include
#include
#include
#include
chars_Ntest3D[16]="nTest3=%d\r\n";
chars_D[7]="%d\r\n";
chars_104950May1320[21]="10:
49:
50May132008";
void*null_1=printf;
intsub_401000(intt)//procnear
{
#definevar_8(dwordptr-8)
#definevar_4(dwordptr-4)
#definearg_0(dwordptr8)
asm
{
pushebp
movebp,esp
subesp,8
mov[ebp+var_4],0Ah
mov[ebp+var_8],0h
movecx,[ebp+var_4]
leaedx,[ecx+eax-2]
mov[ebp+var_8],edx
moveax,[ebp+var_8]
movesp,ebp
popebp
//retn
}
//sub_401000endp
}
int__cdeclmain(intargc,constchar**argv,constchar*envp)
{
//_mainprocnear
alloca(0x10);
#definevar_10(dwordptr-10h)
#definevar_C(dwordptr-0Ch)
#definevar_8(dwordptr-8)
#definevar_4(dwordptr-4)
#definearg_0(dwordptr8)
#definearg_4(dwordptr0Ch)
#definearg_8(dwordptr10h)
asm
{
pushebp
movebp,esp
subesp,10h
pushesi
mov[ebp+var_C],1
mov[ebp+var_10],2
movesi,[ebp+var_C]
addesi,[ebp+var_10]
moveax,[ebp+var_10]
pusheax
callsub_401000
addesp,4
addesi,eax
mov[ebp+var_8],esi
movecx,[ebp+var_8]
//pushs_104950May1320
//callprintf
pushecx
pushoffsets_Ntest3D
//leaebx,[s_Ntest3D]
//pushebx
callprintf
addesp,8
mov[ebp+var_4],0
jmpshortloc_40107F
loc_401076:
movedx,[ebp+var_4]
addedx,1
mov[ebp+var_4],edx
loc_40107F:
cmp[ebp+var_4],5
jgeshortloc_40109B
moveax,[ebp+var_8]
addeax,[ebp+var_4]
pusheax
pushoffsets_D
callprintf
addesp,8
jmpshortloc_401076
loc_40109B:
pushoffsets_104950May1320
callprintf
addesp,4
xoreax,eax
popesi
movesp,ebp
popebp
//retn
}
//_mainendp
}
下面是sample1逆向还原代码的调试结果:
挖掘PE代码技术研究
需要具备的知识点:
汇编语言(熟练)
Win32系统环境下的SDK编程经验(掌握)
Win32PE执行体结构(熟练)
逆向工程操作原理和实现(包括静态动态调试器的熟练掌握,以及这些工具实现的基本原理)(掌握)
各种常用类型的编译器的基本编译结构(掌握)
技术建议:
最好是具备壳或者感染型病毒开发的一些技术经验,对挖掘代码工作也很有帮助,挖掘PE代码操作实际上相当于加壳或者开发Win32PE感染型病毒的反操作.如果成功破解过商业软件,并且制作出注册机这方面的经验,在挖掘PE代码时,也相对较容易,因为有很多商业软件中含有算法加密和解密的部分,开发注册机时就可以直接使用该技术挖掘软件加密部分的算法还原 代码,不用进行算法逆向还原的操作.
1.在PE代码挖掘之前,首要做的操作是侦壳,如果软件加壳,无法还原出正确代码,不能进行代码的挖掘操作。
侦壳使用常用的一些侦壳软件就可以,比如PEID、Die等.当确定壳的类型以后,需要使用脱壳软件或者手工脱壳,在实现手动脱壳时,需要确定壳的大小和还原程度,不同种类的壳手脱方法不一样,主要有以下几大类型:
(1).压缩壳:
压缩壳一般只要找到正确的PE入口点即可,跳过压缩算法,在内存中执行解压缩的操作,找到正确的入口点,进行内存Dump,就基本可以还原出正确的代码,还原后的代码可以直接执行,PE文件的导入导出表由于已经被解压缩出来,所以不用进行修复.,这类脱壳后的代码可以直接进行PE解压缩的操作.
(2).加密壳:
这类壳由于一般除了压缩之外,还有加密功能,加密的程序除了需要找到PE正确的入口点之外,在内存中Dump出解密后的数据,还需要修复导入函数,或者重建导入表,如果导入函数没有被正确修复,程序无法正常运行,同时由于Dump出的代码不具备完整性,不能使用PE代码挖掘,否则导致程序没有正确的导入导出函数地址,运行时崩溃.
(3).虚拟机壳:
这类壳在手工脱壳时,不能按照传统方法查找PE入口点,因为程序的原始代码是在虚拟机部分边还原边执行的情况下在内存中产生的,所以需要在程序运行到适当的位置进行分块Dump内存的操作,对Dump出的代码再进行重构PE,修复导入导出表,这类操作需要视具体壳的类型而定,基本没有统一的标准规范,重构成功的PE程序可以直接进行代码挖掘工作.不过这类代码冗余的代码相对较多,可以做适当的裁剪,提高程序运行效率.
2.软件代码中的花指令:
花指令给PE代码挖掘时造成的难度也很大,如果代码中存在花指令,那么这类代码不能直接进行PE代码挖掘操作,需要借助调试器插件或者手工清除花指令,目前安全界还没有开发出相对比较成熟的花指令检测软件,基本上花指令都是逆向人员通过阅读汇编代码的经验来判断的,花指令的范围也根据情况而定,它可以是整个PE代码插入花指令,或者只插入一部分花指令,如果在没有花指令检测机制和去除花指令插件以及针对未知的花指令操作时,用动态调试器比如OD可以使用Ctrl+↓和Ctrl+↑来调整汇编代码字节序列,从而在花指令中还原出正确的汇编代码,这类操作需要对加花指令的程序区域逐条进行汇编指令还原后再进行PE代码的挖掘工作.具体操作视不同的编译器而定.
举例说明:
Olldbg调试:
00418640>A100000000moveax,dwordptr[0]←被花
00418646.55pushebp
00418647.8BECmovebp,esp
0x418640地址处的指令被花,原因是该指令明显是无法正确执行,访问一个非法的内存单元ds:
[0],但是后面的两条指令是函数的正常开头部分,所以没有被花的可能性,那么根据经验可以判断花指令就在0x418640之前的一条代码:
具体的代码长度目前无法确定,所以使用Ctrl+↑操做向上先调整一个字节查看效果:
00418640>64:
A100000000moveax,dwordptrfs:
[0]
00418646.55pushebp
00418647.8BECmovebp,esp
0x418640的代码可以正常执行,是一条有效的汇编指令,将上一个字节0x64纳入到了0x418640代码中,指令补充完整,代码已经成功还原,可以进行PE代码挖掘操作了.
注意:
在手工去除花指令的操作时,需要对花指令的范围做出正确的判断,不能对正常的代码执行这类操作,否则造成代码混淆错位,PE挖掘代码失败.严重时会导致程序崩溃.
3.软件未加壳情况:
未加壳的软件可以直接进行PE程序的代码挖掘操作,静态或者动态调试器均可以,在对挖掘好的代码进行编译时也不需要选择具体编译器,如果是VC、Delphi、BCB等代码,使用内联汇编的格式进行书写和编译,具体操作查看文档按照语法规范写就可以了,如果是Masm之类的汇编器,直接挖掘使用,调整量相对较小,在使用前,需要首先分析目标程序,确定需要挖掘哪些功能代码,使用调试器动态跟踪或者使用IDA等工具静态分析,定位挖掘代码的长度范围,在挖掘时需要根据具体需求和具体软件视情况而定,代码中如果使用了全局变量或者堆上的数据,就必须找到全局变量的具体位置,定位文件代码节中的实现代码和数据节上的全局变量,代码节的名称是根据具体编译器而有所不同的,比如VC编译的程序是.text节,Delphi/BCB则是.code节,数据和全局变量是在.data节等,在调试时可能不同的软件会有一些细节上的变化,视具体情况而定,具体软件节的大小名称和取值范围可以使用PEID工具查看,如图:
Delphi编译器编译后的PE节:
具体各节的大小范围如何计算属于PE知识范畴,需要查阅相关PE结构文档.
在进行数据挖掘操作时,根据以往经验总结尽量挖掘代码完整的函数,这样在将挖掘后的数据放入自身的源代码后容易做上下文,尽量不要从函数中间截取代码,否则调整源代码的上下文相对困难,需要重新阅读完善被挖掘部分代码,了解被挖掘部分代码的基本结构原理实现才可以,在挖掘代码时,由于挖掘操作不等同于逆向还原代码,尽量保证函数名称不做修改,除非绝对必要,这样的代码在进行调试时可以比较方便的定位到被挖掘的代码位置,提高调试开发效率.
如果代码中调用了系统API函数,那么不能直接使用挖掘出的代码,在源代码中需要引入相应的函数库,以VC举例
比如:
需要挖掘的代码:
.text:
004143BDpushedi;lpName
.text:
004143BEpushebx;dwMaximumSizeLow
.text:
004143BFpush[ebp+dwFileOffsetHigh];dwMaximumSizeHigh
.text:
004143C2push2;flProtect
.text:
004143C4pushedi;lpFileMappingAttributes
.text:
004143C5pushdwordptr[esi+34h];hFile
.text:
004143C8callds:
CreateFileMappingA//API函数
.text:
004143CEmov[esi+38h],eax
.text:
004143D1cmp[esi+24h],edi
.text:
004143D4leaeax,[esi+20h]
.text:
004143D7movedx,[eax]
由于调用了API函数CreateFileMappingA在源文件中需要引入Windows.h头文件,当在挖掘代码的过程中,需要特别密切地注意代码中所调用的函数,正确区分出MFC、VCL等不同编译器所使用的库函数以及WindowsAPI函数,需要确定这些函数在代码中实现的功能,如果有必要,需要在源代码中重写部分库函数,关于操作时具体函数,要及时查阅MSDN.
挖掘好的代码需要调试,特别关注被挖掘出的代码所使用的函数地址是否正确,全局变量是否被正确定义和引用等,这些需要操作调试器熟练和软件调试时的经验相配合。
具体实现:
需要挖掘的目标代码:
举例说明,使用IDA反汇编效果如下:
.text:
00417B40moveax,[401240]//取得参数
.text:
00417B44testeax,eax
.text:
00417B46jzshortlocret_417B57//判断参数
.text:
00417B48pusheax;lpMem
.text:
00417B49push0;dwFlags
.text:
00417B4Bmoveax,hHeap//拿到堆句柄
.text:
00417B50pusheax;hHeap
.text:
00417B51callds:
HeapFree//API释放堆
.text:
00417B57
.text:
00417B57locret_417B57:
;CODEXREF:
_free+6j
.text:
00417B57retn
.text:
00417B57_freeendp
挖掘后的代码如下:
#include
#include
HANDLEhHeap=NULL;//和逆向出的变量名称保持一致,以便调试
BYTE*pVar401240=NULL;//和目标中的变量地址名称一致
intSub_00417B40();//和IDA中的函数名称接近一致
intmain(char**argc,intargv)
{
intiRet=0;
hHeap=HeapCreate(1,0x1000,0x3000);
if(hHeap==NULL)
{
exit
(1);
}
pVar401240=HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x1000);
if(pVar401240==NULL)
{
exit
(1);
}
iRet=Sub_00417B40();
}
intSub_00417B40()
{
_asm//内联汇编开始部分,这部分是直接挖掘的原始代码
{
moveax,dwordptr[pVar401240]
testeax,eax
jzshortlocret_417B57
pusheax;lpMem
push0;dwFlags
moveax,dwordptr[hHeap]
pusheax;hHeap
calldwordptr[HeapFree]
locret_417B57:
;CODEXREF:
_free+6_j
}
return0;
}
技术总结:
优点:
使用PE代码挖掘技术可以在没有源代码的情况下使用自己架构的代码实现开发目标,节省开发时间,最大可能地做到不破坏降低原始目标程序代码的执行效率,是一种在较短时间内完成没有源代码和技术资料但是要实现目标程序所完成功能的一种优选方案,降低开发逆向程序难度,节省了开发成本和开发时间
缺点:
对实现该技术的要求较多,涉及到的知识点比较繁杂,对大型公司的商业程序不适用,原因是随着代码量地增长调试难度也随之增大,需要具备软件开发和调试方面经验的积累,需要更多的时间和耐心
关于PE代码挖掘技术在壳方面的一些技术设想:
当遇到软件加未知壳时,没有壳方面的技术资料支持,也没有现有的脱壳工具使用,手工脱壳也无法达到目标时,可以使用PE挖掘技术将加壳文件中的壳代码直接取出,众所周之,加壳后的程序里壳代码实际上就是在内存中解密还原程序的代码,只要完整地挖掘出目标中所包含的壳代码,就能开发出针对某种加壳程序的脱壳机,在不用逆向分析壳代码的情况下直接利用软件中的壳实现脱壳操作.
具体在使用这类技术时根据具体目标,任务进度,技术难度,个人能力等因素有的放矢的加以使用和优化。
逆向恢复实例
逆向恢复Sandboxie
众所周知,对于编译型的语言,其编译并链接好的应用程序其实就是二进制的机器语言序列,而机器语言和汇编语言是一一对应的,因此我们可以在没有源代码的情况下,通过对可执行程序进行反汇编来得到汇编源代码,并通过进一步整理获得可以编译运行的汇编源代码。
本文将要论述并实践的就是这样一种技术。
俗话说:
“工要善其事,必先利其器”,进行代码还原更是如此,好的工具可以使你的工作事半功倍。
在开展我们的工作前,需要以下几个工具:
1)IDAPro
强大的静态逆向工具(也可以动态),被逆向工作者称为“屠龙刀”
2)一款适合你的编辑器(依据个人情况随意,如果不怕难看,记事本都可以)
下面就详细说明一下代码还原的步骤:
A)第一步:
找到程序的入口点start(快捷键ctrl+E)
B)第二步:
去掉IDA多余的显示内容(如图)
C)第三步:
从程序入口点开始,逐步还原代码
1.将入口点函数拷贝到文本文件中
2.定义入口点函数所用到的全局数据
3.如果程序中用到了自定义dll中的函数,还须在入口代码前添加获取函数地址的代码
4.用汇编器编译,排除除了“未定义函数”所引起的错误
5.从start开始,将“未定义函数”逐步实现
需要注意的问题:
1.关于函数的参数名称
为了避免函数的参数名和全局变量名冲突,建议在函数的参数名前加上一个下划线“_”
2.关于局部变量的定义顺序
汇编语言局部变量的定义顺序和IDA中显示的局部变量栈顺序是相反的,如下图:
IDA局部变量的栈布局
汇编源代码(MASM)中的局部变量定义
3.去除栈框架(见上图)
4.数据格式修正,如下图:
IDA中的代码
修正后的代码
通过以上这些方法,相信你已经成功了99%了,但是计算机程序千变万化,存在无限的未知性,这就需要拥有扎实功底的你不断的探索,细心的Debug,这就是那剩下的那1%,也是最精华的1%,只要努力,你一定行!