软件逆向-吴斌_第1页
软件逆向-吴斌_第2页
软件逆向-吴斌_第3页
软件逆向-吴斌_第4页
软件逆向-吴斌_第5页
已阅读5页,还剩41页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

2024/1/31软件逆向分析整理科锐课程内容说明:主要针对VC下的逆向分析使用工具VC、OD、IDAV0.12024/1/32识别main函数2010_年4月_8日Debug方式下Relese方式下

在函数开始的第一个3个push下面的call就是main函数,WinMain是4个push后面跟的call就是WinMain。因为在main函数之前的函数没有3个和4个参数的,main函数后面就是exit退出函数。这里主要是针对VC编译平台下,其他平台需要自行找到特殊的标志2024/1/33识别编译方式2010_年4月_8日

编译方式的不同主要体现在是否有初始化内存CC的操作,debug方式内存初始化CC,而Release不会。另外debu方式会chkesp检查栈平衡。Relese方式下ODIDADebug方式下重定栈底坐标杆申请变量空间保存环境初始化变量空间为全CCOD参数压栈、掉函数、平衡栈恢复环境传出值检查堆栈平衡释放局部变量空间恢复栈底2024/1/34栈平衡(释放参数占用空间)2010_年4月_8日

堆栈平衡主要是释放申请的局部变量空间,而调用约定也是在栈平衡上有不同,stdcall是被调方平衡、cdecl是调用方平衡、fastcall也是被调方平衡,与stdcall区别是使用寄存器传参。返回值

函数返回值主要通过寄存器EAX返回,在被函数中EAX赋值,并且调用方有使用,说明是有返回值的(当然是是EAX传递的)ESP可以用来做参数访问的标杆,永远都是栈底又做栈平衡的标志2024/1/35识别调用约定2010_年4月_8日

调用约定主要是约束栈平衡的所属,也就是参数空间谁负责释放,如果没有参数,则无法判定到底是什么调用约定,当然也没有知道的必要了。调用方被调用方debugdebugreleaserelease2024/1/36识别参数个数

2010_年4月_8日

参数的识别要结合调用方式栈平衡的方式信息,平衡多少就会有多少空间参数,识别的方式:1)栈平衡多少空间2)函数内是否有[ebp+x]的访问方式,其他也有使用[esp+1]3)特殊的Fastcall:要看是否有push寄存器,在被函数内,寄存器没有赋值就直接使用,则寄存器传参

debugrelease归并归并在优化时才有且前提是函数要使用的参数没有被分割开Fun2参数不连续不能归并2024/1/37Copy代码2010_年4月_8日

在IDA中找到感兴趣的函数后,进入函数体内,将对应的汇编代码COPY出来,放到汇编建的dll工程下,要注意分析清楚其调用方式以便做正确声明IWillCopyCodeprocCValue1:DWORD

将代码COPY这里,修改crt_(c库函数)retIWillCopyCode

endp

函数申明然后将函数名放.def文件中作导出申明EXPORTS IWillCopyCode在VC工程中增加编译后的lib文件,声明函数类型extern“C”调用方式函数名称…2024/1/38识别运算操作机制2010_年4月_9日常量优化

编译器扫描相关变量,①其值是可确定的②其值后面没有依赖性,则编译器会做常量归并。申请、释放局部变量内存空间常量优化①申请空间: ②释放空间:Subesp,xx(申请较多) addesp,xx/mov

esp,xxpush寄存器(少量空间) popecx

说明:对于需要少量的内存空间则使用push申请,push后没有对应的pop做恢复,则是申请空间;同样有pop的,前面没有push与之配对,则是释放栈空间.值编译可确定,无依赖性归并Scanf中a值不确定,且后面printf中有依赖性2024/1/39识别运算操作机制2010_年4月_9日Ebp与esp使用时机在寄存器紧张时,使用esp寻址,可以节省下ebp他用(前提是esp可确定).IDA下k命令[esp+4+var_x]标示x那个局部变量②在esp栈顶被破坏,esp不确定,则只能使用ebp访问栈内容。如:VC下有嵌入汇编__asm{push

eaxpopeax},则使用ebp识别参数类型有格式化类型信息时如:pushxx“%d”,说明是整形②指令类型 imul

说明是有符号数③函数接收参数具体类型pushxxcallputs,则参数为指针2024/1/310识别运算操作机制2010_年4月_9日Lea作运算操作Lea主要作用是做地址运算,使用[]可以对其中的信息作运算,也仅限在已有的寻址方式范围内。加法:Printf(“%d”,a+b+6);

Leaeax,[ebx+ecx+6]乘法:a+b*4+6 leaeax,[ebx+ecx*4+6]相对基变(2/4/8)

a+b*3+6leaeax,[ebx+2],leaeax,[ebx,eax*3]a*2 leaeax,[ebx+ebx]/直接相乘或移位

a*3 leaeax,[ebx+ebx*2]a*4 leaeax,[ebx*4]a*11 (a*5)*2+a leaeax,[ebx+ebx*4]==a*5 leaeax,[ebx+eax*2]说明:无符号是2的倍数可以做右移位,[ebx+ecx*M]M只能是2、4、82024/1/311识别运算操作机制2010_年4月_9日Lea作运算操作--除法X/2^nC编译器优化1)若X为正偶数,X/2等价于X>>2

若X为正奇数,按C语言规定,除法是向下取整,3/2=1,X/2=(X-1)/2,等价于X>>1,或者(X-1)>>1,所以正整数情况下,不同其他处理直接移位2)按补码格式规定若X为负偶数,X/2等价于X>>1

若X为负奇数,这时(-3)/2=-1,除法变成向上取整,X/2等价于(X+1)/2,XXX负数情况下,不能直接移位,需要+1。

对于偶数正常移位,而奇数,正的向上取整(-1),负数向下取整(+1)X/2^nC编译器优化1)若X为正偶数,X/2等价于X>>2

若X为正奇数,按C语言规定,除法是向下取整,3/2=1,X/2=(X-1)/2,等价于X>>1,或者(X-1)>>1,所以正整数情况下,不同其他处理直接移位2)按补码格式规定若X为负偶数,X/2等价于X>>1

若X为负奇数,这时(-3)/2=-1,除法变成向上取整,X/2等价于(X+1)/2,XXX负数情况下,不能直接移位,需要+1。If([index_2]%[var_c]==0)执行减法取余数

减法一般都会优化成加上一个负数。Addesp

,-8(当然应该是补码)2024/1/3122010_年4月_9日分析strlen

Strlen形式还是挺多的,以上为2中常见方式,最终都是用计数找0结尾长度,减去结束符。分析strcpy4字节拷贝有2种,a)将字符串放到4字节寄存器中,不足的放2字节或1字节,作中间桥梁拷贝

b)对于字符串较长,计算4倍数个数作4字节串操作,再取余作2字节或1字节操作SPEED方式,单字节初始化,也是采用4字节小体积Eax与es:[edi]不相等及不为\0则继续(ecx继续减法)ecx有个字符就-1,最后ecx–len=-(len+1)Not-1=1Not–(len+1)=len+1=>len-1,最后减去一个\02024/1/3132010_年4月_10日制作IDA的sig文件

sig文件是IDA用来识别函数名称的特征文件库,sig文件保存的是关键操作数指令,不保存偏移地址等不确定信息。有与特征码匹配则显示对应的函数信息

obj

sig

使用pcf

xx.obj

xx.sigLibsig

使用lib2sigxx不需要.lib后缀名使用:将做好的sig文件copy到IDA的sig文件下,在IDA中点击一个是红花的按钮(Shift+F5)右击选择选择要添加的sig文件,支持输入查找确定即可,有些老版本的IDA需要重新开启才能有效IDA命令

C解释为代码,D解释为数据,N改名字2024/1/3142010_年4月_10日--识别分支结构单分支if语句多分支ifelse语句

在代码中绝对不会使用的语句或运算,在优化后会自动被清除掉对应的代码。If语句产生代码为a)比较语句;b)不成立就走(判断相反的);c)执行if语句内的代码。

在某些条件在编译时就能确定执行的就会直接优化成顺序语句执行,后面的就直接优化掉了!绝对不执行的、值确定的直接优化掉。优化掉判断相反条件abc直接优化掉一定成立因为分支一一定成功执行,后面的就不会再执行,被优化掉2024/1/3152010_年4月_10日--识别分支结构多分支ifelse语句SPEED优先SIZE优先BABPPEBABPE共用printf共用相同

多分支不同于单分支在于,在分支内执行后就直接JMP出来判断相反条件优化代码少时直接考份,减少跳转2024/1/3162010_年4月_10日--识别分支结构多分支switch语句switch语句主要有4中方式:1)很少分支则使用类似ifelse方式;2)有规律多个使用跳转表,部分间隔用default填充;3)间隔较大数量少于256时,使用稀疏矩阵,来二次查表获取case块地址;4)复杂无规律情况使用二分法或二叉树;5)其他情况下页会有多种情况并存。switch语句(情况一)分支较少转换成类似ifelse分支选择部分有break则直接跳出类语句,没有break就顺序向下执行修正为0下标定位到case块2024/1/3172010_年4月_10日--识别分支结构switch语句(情况二)分支多且多数连续,使用跳转表FFFDh==-3(减法优化)Addeax,-1==subeax,3超过最大标号则def6=(9-3)通过计算下标来定位CASE语句块的位置,并执行CASE语句块地址,当中有缺少的用Default语句地址来替换。2024/1/3182010_年4月_10日--识别分支结构switch语句(情况三)分支多,空的间隔大,数量<256,使用稀疏矩阵减去基值,使其从0下标开始查索引表取函数地址对应的下标

0下标开始,其他减去基值,将函数地址做成索引表,缺少的项用Default地址填充,通过标号-基值找到索引表,取出索引号,然后到地址表中找到对应下标的函数地址,调用之。跳转表索引表2024/1/3192010_年4月_10日--识别分支结构switch语句(情况四)分支多,不连续,间隔大,使用二分法或二叉树Switch与Ifelse语句区别及逆向方法

区别:ifelse是反条件跳走,代码块在跳转指令和跳转位置之间,而switch是成功就执行,不是反条件,在没有break时,可以顺序向下执行。

逆向要点:1)识别default分支,是否有default看break跳转地址是否与疑似default相同,对于switch语句后面代码较多的情况一般很难分辨,没有default则是视为空”default:;”语句。 2)识别case语句要能识别使用的优化方式,主要是跳表、二叉树,标识出对应的case号。J1AB反条件成立跳走不成立向下执行J1J2J3判断正条件CaseN2CaseN1CaseN3OKNG

在复杂情况中又会根据各子树的情况是否满足上述3种情况,有则又会包含其他的处理,对于这种复杂的结构主要还是分析使用的优化方式来明确标注出名称,优先标注的default,根据树的分支状况分析每个case所代表的case编号对应多少,注意代码结尾。2024/1/3202010_年4月_10日--识别分支结构逆向练习TestSrc1,分析编译方式和运行环境;

程序中有Debug初始化变量为CC2,分析程序功能;

循环10次响铃,通过hostname获取主机名及IP3,还原等价C源码,还原相关结构体和数组,且尽量和源码一致(包括定义顺序); 主要是两个循环和if分支语句,关键是变量的识别.4,本程序存在一个潜在的Bug,请找到并修正; 主要是保存主机名和响铃用的数组定义的太小,主机名最大256实际只有128,有益处风险5,修正Bug以后,提取关键功能代码,用masm制作OBJ文件,并且在VC里调用之。

建需要工程(控制台obj或Dll),将修改后的代码IDA载入,直接copy出来,放到工程的函数体内,修改一些标示(变量类型)或寻址方式(+dword

ptr),需要到处的在def文件中作导出说明EXPORTS,将需要的文件.obj或.lib和.dll文件拷到要使用的工程下,添加到工程,对引用函数做申明,extern“C”调用方式函数名,调用之2024/1/3212010_年4月_12日--识别循环Do{}while();

BEGIN_DO:[语句体]

判断成立(满足条件的条件判断)JXXBEGIN_DOwhile(){}for(){}BEGIN_WHILE:

反条件判断JXX

OUT_WHILE[语句体]JMPBEGIN_DOBEGIN_FOR:

初始化部分JMP反条件判断

步长部分

反条件判断JXXEND_FOR[语句体]JXX步长部分If反条件成立goto

EndWhile(一次把关)BEGIN_DO[语句块]

正条件比较JXXBENGIN_DO优化优化If反条件成立

不成立则直接优化成dowhileBEGIN_DO[语句块]

正条件比较JXXBENGIN_DOWhiel和for循环优化后无法分辨,但是两者合do还有明显的区别,当一次把关的判读是常量,且成立,则直接优化成do–while了2024/1/322循环常量优化--代码外提2010_年4月_12日--识别循环循环体内{m=6;}Printf(“%d”,m);循环判断把关语句

m=6;循环体内{}Printf(“%d”,m);m=6;(用户外提会在循环外)循环判断把关语句

循环体内{}Printf(“%d”,m);外提编译器人为编译器

对于调函数返回相同信息的重复操作,应该外提循环外,编译器外提会在循环语句判断语句和语句块之间,而用户外提代码则会在整个循序块的外面局部变量优化

在A语句块中使用X局部变量,在B中使用Y局部变量,在A使用完X后就不在使用,且与后面代码无相关相关性,则B会使用X局部变量来作为自己的局部变量使用,节省申请和释放空间的步骤.2024/1/323识别数组2010_年4月_12日--识别数组

一维数组寻址公式:ary[N]

TYPEARY[A]=ARYAddr+sizeof(TYPE)*A

二维数组寻址公式:ARY[M][N]

TYPEARY[A][B]=ARYAddr+sizeof(TYPE[N])*A+sizeof(TYPE)*B数组:

访问方式为[ebx+ecx*4+var_xx]

首先要确定数组的首地址,可以根据一些引用方式来估计。

如:format%s传递的是字符串地址,有可能是数组首地址,还有具体访问的情况,发现数组首地址后,在数组区将某段数据区域解释成数组方式,可以大大方便对数据的分析.2024/1/324函数被优化掉2010_年4月_13日编译是否保留某些符号的信息,就看程序中使用有依赖关系,有使用就会保留,否则的话就会被优化掉。没有符号引用就被直接优化掉了,程序跳到错误地址去解决办法:1)在主函数调用时对函数名称作引用;2)被掉函数做成裸函数主函数调用时对函数名称作引用裸函数实际还是被优化掉2024/1/325变量作用域2010_年4月_13日局部变量:局部变量主要使用栈方式来寻址,主要特征是使用[(esp/ebp)-/+(value)],对于某些固定常量可能会直接引用,也有使用寄存器的情况,但非主流。

全局变量:

编译期可得地址,可使用一些地址值,或取常量的操作静态变量:

静态有赋值则等价于全局变量,静态变量通过标志值来标示是否已初始化,一般在静态变量附近,使用位方式标示,每位代表一个静态变量,0未初始化。指令优化,不相关指令可以并发数据区才可以offset,还有直接使用数值[eax*4+424xxx]设置标志值当全局变量使用判断是否已初始化*((&b)+1)=0标志位清02024/1/326常量时,则使用公式优化计算2010_年4月_13日

运算符的形式为:表达式1

?表达式2:表达式3结果根据表达式1真否来决定,当表达式1为TRUE结果为表达式2,否则为3非常量则类似单分支语句非常量则成分支语句Setnz

等于0则置1,否则为0(相等)Dec0-1and22dec21(==)Dec10and20dec2-1(!=)三目运算符

复杂2024/1/3272010_年4月_13日识别指针操作数组的访问方式:1)下标法;2)指针定位;3)数组寻址公式

1)数组名作指针使用,会优化成接触器保存其地址做访问,有寄存器保留指针说明被保存的是指针;2)有内存保存地址,对其有地址类操作,则说明必是指针类型

3)对指针的强制类型转换也是在编译器下作说明而已,在内存中是什么还是什么,当什么用就是什么。4字节数组赋值,

程序优化后,会很少用公式的乘法,而是转成加法操作

指针指向的某内存地址作什么样的解释就是什么类型,说是int就按int解释,说是函数指针就是了。函数指针参数也是编译器级别作管控,汇编下,就是call对应的地址调用而已!2024/1/3282010_年4月_13日工具流脱ASPack2.12压缩壳1)PEiD查壳OD载入后走一步,记录当前栈地址2)3)OD命令行下硬件访问断点,F9跑走到返回原函数入口前4)5)设置当前地址为OEP,脱壳,保存2024/1/3292010_年4月_14日浮点运算Fldxx取存储器的浮点数整数,压入浮点寄存器栈顶ST(0)

Fildxx取存储器的整数,压入浮点寄存器ST(0),原ST(0)ST(1)

Fst[xx]保存单精度浮点数到mem

Fstpqwordptr

ss:[esp]取浮点ST(0)值要到SS栈空间(双精度),ST(0)ST(7)备份(让标示empty),并将ST(0)清0Fdiv[xx]取浮点寄存器栈顶值与[xx]作浮点数除法Fiadd[xx]ST(0)+[xx]ST(0)var_4=2.6var_8=9.52.6==>ST(0)ST(0)*9.5保存单精度到内存中(栈中)申请8字节栈,将ST(0)->[esp-8]中2024/1/3302010_年4月_15日识别结构体

结构体和数组非常类似,当结构体数据类型为相同或相似时,几乎和数组等同,与数组最大的区别就是可以保存不同的数据类型,区别方式也用这点。IDA对结构体进行标注的方式,要是有信息结构也是添加进来。在ends按D增加成员,U取消,大小看右上角托选要解释成结构信息,按K,选择合理类型OK使用了类型数组的访问方式,但数据类型不同1)这里申请结构体空间时使用未释放的4字节栈2)结构体直接赋值就相当于memcpy,获取成员的便宜量:&(((st*)NULL)->m_xx)2024/1/3312010_年4月_16日共用体枚举类型

共用体的转换操作类似于指针类型的解释操作,内存转换成什么就是什么,也就是老师说的见人说人话,见鬼说话。

枚举类型就实质还是常量,只是可以定义一组有意义标识,比纯数据可读性好,比#define定义的更多、更方便而已。123这里没有IDA标示方式的使用2024/1/3322010_年4月_16日位段段位段结构实质是方便对位做操作,上层避免作复杂的地址运算操作。无名位段,占位置用

C下很多操作,只是为了操作方便,在汇编情况下是无法分辨原来的具体状况,就像位段操作、枚举、共用体、指针、数组、结构体,只要等价,怎么解释都可以,只是是否通俗易懂。2024/1/3332010_年4月_16日识别构造及构造方式1)第一个调用,完成的是初始化工作

2)传入ecx,ecx指向结构体的首地址,并且内部有使用

3)传出ecx又被传出,eax=ecx

满足上述三点则基本则构造函数的可能性就非常大了。

全局构造:在GetStartUpInfo前或再调用main函数前的_cinit()在构造全局class之后,会掉atexit(虚构地址)New出来对象,主要特征表现在,new之后就判断是否成功,不成功跳走,ok则call,参数ecx等3个特征。Ecx直接使用(参数传),且通过eax返回,里面一般做初始化。对象保存在栈中是比较难识别的,方法仍然是上面的3条原则。This指针应该就是指向结构体(这里说类)的首地址。2024/1/3342010_年4月_16日识别析构函数全局析构:在main函数结束后exit(mainret)用户定义结束执行的函数也会添加数组中找到就执行1)最后一个调用,完成的是释放资源工作

2)仍然有ecx传参,再有析构函数时会存在析构代理的函数,处理析构函数中多继承的关系

3)this指针用来做析构时的标识,在有临时变量拷贝构造,会有传参,使用临时栈存信息,用析构在函数调用,函数外,栈会被释放,信息就没了,栈被破坏了。

????在delete申请的对象时,会先调用析构函数再delete堆空间Delete时先掉析构函数,再delte对于在栈中的对象,释放时仍然有上述的迹象存在,只是不确定的信息也非常多,识别的难度就打多了。2024/1/3352010_年4月_16日识别成员函数

想识别成员变量

想2024/1/3362010_年4月_17日虚函数构造(单继承)

虚函数的显著特点就是虚表,多态性只有new的情况下才有,其他情况都是利用名称粉碎的方式来确定具体某个函数(编译期就决定的函数地址),而new这是通过this获取vTable,调用其中的某项。

构造有虚表给值则是构造函数行为(除非手工做),在构造函数时,先祖先、再成员对象,最后自己。如果有继承关系每个类中都维护一个虚表的首地址,自己改写的就修改虚表的内容,虚表的排列顺序是第一次定义的顺序。不是最顶层的都要保存虚表,防止下层还有继承,析构时没有保存就析构自己的,自己没有就析构其他人了.构造时,call构造函数,然后虚表赋值。在构造函数中,如有父类,先参数列表先初始化,call父类构造,最后初始化自己的构造内的初始化工作。名称粉碎直接定位

edx=this指针Eax=[this]=Vtabla(虚表)Call[eax];虚表的第一项有父类则Call父类构造,否则call自己,并保存虚表2024/1/3372010_年4月_17日虚函数析构函数(单继承)

析构也有对虚表的赋值操作(防止释放其他子类调用自购释放错资源),非new的对象直接调用析构函数,首先保存虚表地址,再调用析构函数,如果有继承则再掉上层的析构函数。在有new对象时,通过虚表调用函数,函数是个析构代理函数,同样先保存虚表,然后调用析构函数,返回是1则掉delete函数释放堆空间。非new的对象析构时直接掉析构函数,有继承关系则依次调用,并保存当前的虚表信息

[edx+8]虚表地址开始偏移8字节的函数(这里就是析构)这里多了析构代理负责调析构,最后在delete堆空间。先保存虚表,再析构自身,如果有继承,再call父类。如果是根节点则直接调析构处理释放资源,就不要在有保存虚表的操作。2024/1/3382010_年4月_19日

组合情况在在有虚函数的情况下较好区分,有虚表则继承的虚表永远在第一项(保存虚表地址),组合操作用this+offset来定位组合对象的this指针。在没有虚函数的情况下,组合或继承也没有区分的必要,当然也是很难区分。虚函数(带组合)构造:3父类构造,2个改虚表,1个有虚表未被修改,2继承,1组合,在在赋虚表赋值0偏移前是父类构造和初始化列表,后是自身构造在有this指针转换时,会作非0判断,析构时按构造相反顺序进行析构。保存虚表->析构自己->按构造相反顺序依次析构。This指针作偏移,并取值操作,有作非0检查2024/1/3392010_年4月_20日多继承结构1)在没有虚表的情况下,多继承方式和组合方式生成的代码很相似

2)在release有inline情况时,从没偏移赋虚表地方做分隔,上面的是父类构造,下面添虚表。

3)多继承先父类占地,后面再有虚表,紧跟父类成员后。?A?B?AB优化CXxx*p=newCXxx;Deletep;

在没有虚表的情况下,多继承和组合没有任何区别,再有虚表存在的情况就可以区分组合和继承的关系。区别在于:继承下,子类会修改父类的虚表为自己的,当某个对象的虚表被修改为子类时则是继承;而在组合的情况下,成员对象保存自己的所有信息,其虚表不会被包含她的类所修改!

有两个虚表被改写,则说明至少继承2个,CJiaJu有虚表确没有被改写说明是组合关系

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论