c++反汇编与逆向分析 小结

第一章 熟悉工作环境和相关工具
1.1 熟悉OllyDBG 操作技巧
1.2 反汇编静态分析工具 IDA(最专业的逆向工具)
快捷键 功能
Enter 跟进函数实现
Esc 返回跟进处
A 解释光标处的地址为一个字符串的首地址
B 十六进制数与二进制数转换
C 解释光标处的地址为一条指令
D 解释光标处的地址为数据,没按一次将会转换这个地址的数据长度
G 快速查找到对应的地址
H 十六进制数与十进制数转换
K 将数据解释为栈变量
; 添加注释
M 解释为枚举成员
N 重新命名
O 解释地址为数据段偏移量
T 解释数据为一个结构体成员
X 转换视图到交叉参考模式
Shift+F9 添加结构体

认识各视图功能
IDA View-A:分析视图窗口,用于显示分析结果。
Hex View-A:二进制视图窗口,打开文件的二进制信息。
Exports: 分析文件中的导出函数信息窗口
Imports: 分析文件中的导入函数信息窗口
Names: 名称窗口,分析文档中用到的标题号名称。
Functions: 分析文件中的函数信息窗口
Structures:添加结构体信息窗口
Enums: 添加枚举信息窗口


SIG文件的制作步骤 1、从LIB文件中提取出OBJ文件 2、将每个OBJ文件制作成PAT文件 3、多PAT文件联合编译SIG文件

SIG文件制作批处理文件的过程
md %l_objs
cd %l_objs
for /F %%i in ('link - lib /list %l.lib') do link -lib /extract:%%i %l.lib
for %%i in (*.obj) do pcf %%i
sigmake -n"%l.lib" *.pat %l.sig
if exist %l.exc for %%i in (%l.exc) do find /v ";" %%i >adc.exc
if exist %l.exc for %%i in (%l.exc) do > abc.exc more +2 "%%i"
copy abc.exc %l.exc
del abc.exc
sigmake -n"%l.lib" *.pat %l.sig
copy %l.sig ..\%l.sig
cd ..
del %l_objs /s /q
rd %l_objs

代码说明
通过 md%l_objs 创建目录,目录名称为参数1
通过 cd %l_objs 进入刚刚创建的目录
在当前目录下 循环取出 LIB 中的OBJ名称 并逐个生成对应的OBJ
循环将生成的OBJ文件通过PCF转换对应的PAT文件
将目录下的所有的PAT文件生成一个SIG文件
将生成的SIG文件拷贝到上级目录中
返回上级目录,删除创建的目录中的所有数据
将创建目录删除


1.3 反汇编引擎的工作原理
Instruction Prefixes: 指令前缀
重复指令:REP、REPE\REPZ
跨段指令:如 MOV DWORD PTR FS:[xxxx],0
32位 16位 互换 如:MOV AX,WORD PTR DS:[EAX] MOV EAX,DWORD PTR DS:[BX+SI]
Opcode: 指令操作码
Mode R/M:操作数类型
SIB:辅助 Mode R/M,计算地址偏移
Displacement:辅助 Mode R/M, 计算地址偏移
Immediate:立即数


第二章 基本数据类型的表现形式
2.1 整数类型
无符号整数 有符号整数 区别就是最大位
2.2 浮点数类型
浮点数的编码方式 在内存中占4字节 最高位符号 剩余的31位中 左取8位指数 其余尾数
浮点寄存器的使用就是压栈/出栈的过程

例如
float fFloat =(float)argc; //argc为命令行参数

fild dword ptr [ebp+8] ;将地址ebp+8处的整形数据转换浮点型,并入st(0)中,对应变量 argc

fst dword ptr [ebp-4] ;从st(0)中取出数据以浮点编码方式放入地址ebp-4中,对应变量fFloat

printf("%f",fFloat);

;这里对esp执行减8操作 是由于浮点数作为变参函数的参数时 需要转换为双精度浮点值
;这步操作时提前准备8字节的栈空间,以便于存放double 数据
sub esp,8

fstp dword ptr [esp] ;将st(0) 中的 数据传入esp中,并弹出st(0)

push offset string "%f"

call printf

add esp,0ch

argc =(int)fFloat;

fld dwrod ptr [ebp-4] ;将地址 ebp-4处的数据以浮点数类型压入st(0)中

call _ftol ;调用函数_ftol 进行浮点类型转换

mov dword ptr [ebp+8],eax ;转换后结果放入eax中,并转递到ebp+8地址处


类型转换函数 _ftol 的实现
push ebp
mov ebp,esp
add esp,0f4h

;浮点异常检查,CPU 与 FPU的同步工作
wait
fnstcw word ptr [ebp-2]
wait
mov ax,word ptr [ebp-2]
or ah,0ch
mov word ptr [ebp-4],ax
fldcw word ptr [ebp-4]

;从st(0)中取出8字节数据转换成整形并传入ebp-0ch中
;将st(0) 从栈中弹出
flstp dword ptr [ebp-och]
fldcw word ptr [ebp-2]
;使用eax 保存长整形的低4位,用于返回
mov eax,dword ptr [ebp-0ch]
;使用edx 保存长整形数据的高4字节,用于返回
mov edx,dword ptr [ebp-8]
;释放栈
leave
ret


2.3 字符和字符串
字符串是由一系列按照一定的编码顺序线性排列的字符组成的

2.4 布尔类型
2.5 地址、指针和引用
地址是一个有32位二进制数字组成的值。指针是用于保存这个编号的一种变量类型。
指针的取内容操作分为两个步骤:先取出指针中保存的地址信息,然后针对这个地址进行去内容。
两指针相加是没有意义的
引用类型就是指针类型
2.6 常量
数据在程序运行前就已经存在,他们被编译到可执行文件中。
#define定义的常量名称,编译器对其进行编译时,会将代码中的宏名称替换成对应的信息。
const 是为了增加程序的健壮性而存在的
#define 是一个真常量,而const 却是由编译器判断实现的常量,是一个假常量。



第三章 认识启动函数,找到用户入口

mian函数被调用前要先调用的函数 如下

GetVersion() 版本号
_heap_init() 初始化堆空间
GetCommandLineA() 获得命令行首地址
_crtGetEnviromentStringsA() 环境变量首地址
_setargv() 命令行参数首地址
_setenvp() 把得到的每条环境变量字符串的首地址存放在字符指针数组中
_cinit() 全局数据和浮点寄存器的初始化



第四章 观察各种表达式的求值过程

加法 add 常量传播 和 常量折叠
常量传播--将编译期间可计算出结果的变量转换成常量,这样就减少了变量的使用。
常量折叠--所有的常量计算都将被计算结果代替。
减法 sub
例如:
int nVarOne=argc;
012E13CE mov eax,dword ptr [argc]
012E13D1 mov dword ptr [nVarOne],eax
int nVarTwo=0;
012E13D4 mov dword ptr [nVarTwo],0
scanf("%d",&nVarTwo);
012E13DB mov esi,esp
012E13DD lea eax,[nVarTwo]
012E13E0 push eax
012E13E1 push offset string "%d" (12E5750h)
012E13E6 call dword ptr [__imp__scanf (12E82BCh)]
012E13EC add esp,8
012E13EF cmp esi,esp
012E13F1 call @ILT+325(__RTC_CheckEsp) (12E114Ah)
nVarOne=nVarOne-100;
012E13F6 mov eax,dword ptr [nVarOne]
012E13F9 sub eax,64h
012E13FC mov dword ptr [nVarOne],eax

nVarOne=nVarOne+5-nVarTwo;
012E13FF mov eax,dword ptr [nVarOne]
012E1402 add eax,5
012E1405 sub eax,dword ptr [nVarTwo]
012E1408 mov dword ptr [nVarOne],eax

printf("nVarOne = %d \r\n",nVarOne);
012E140B mov esi,esp
012E140D mov eax,dword ptr [nVarOne]
012E1410 push eax
012E1411 push offset string "nVarOne = %d \r\n" (12E573Ch)
012E1416 call dword ptr [__imp__printf (12E82C4h)]
012E141C add esp,8


乘法 有符号 imul 和 无符号 mul 两种 例子直接编译
除法 有符号 idiv 和 无符号 div 两种 例子直接编译


判断除法 总结 1
mov eax,MagicNumber
imul …
sar edx,…
mov reg,edx
shr reg,1fh
add edx,reg
;此后直接只用edx的值,eax弃而不用


判断除法 总结 2
mov eax,MagicNumber
;这里的reg表示通用寄存器,上例中为ecx,实际分析中还可能是其他寄存器
mul reg
sub reg,edx
shr reg,1
add reg,edx
shr reg,A ;这句或许没有
;此后直接只用edx的值,eax弃而不用


判断除法 总结 3
mov eax,MagicNumber (大于7ffffffffh)
imul reg
add edx,reg
sar edx,…
mov reg,edx
shr reg,1fh
add edx,reg
;此后直接使用edx的值


判断除法 总结 4
mov eax,MagicNumber (大于7ffffffffh)
imul reg
sar edx,…
mov reg,edx
shr reg,1fh
add edx,reg
;此后直接使用edx的值


判断除法 总结 5
mov eax,MagicNumber (大于7ffffffffh)
imul reg
sub edx,reg
sar edx,…
mov reg,edx
shr reg,1fh
add edx,reg
;此后直接使用edx的值

算数结果溢出
溢出是由于数据进位后超出数据的保存范围导致的
进位-- 无符号数超出存储范围叫做进位

自增自减实例
int nVarOne=argc;
0098138E mov eax,dword ptr [argc]
00981391 mov dword ptr [nVarOne],eax
int nVarTwo=0;
00981394 mov dword ptr [nVarTwo],0

nVarTwo=5+(nVarOne++);
0098139B mov eax,dword ptr [nVarOne]
0098139E add eax,5
009813A1 mov dword ptr [nVarTwo],eax
009813A4 mov ecx,dword ptr [nVarOne]
009813A7 add ecx,1
009813AA mov dword ptr [nVarOne],ecx
nVarTwo=5+(++nVarOne);
009813AD mov eax,dword ptr [nVarOne]
009813B0 add eax,1
009813B3 mov dword ptr [nVarOne],eax
009813B6 mov ecx,dword ptr [nVarOne]
009813B9 add ecx,5
009813BC mov dword ptr [nVarTwo],ecx

nVarOne=5+(nVarTwo++);
009813BF mov eax,dword ptr [nVarTwo]
009813C2 add eax,5
009813C5 mov dword ptr [nVarOne],eax
009813C8 mov ecx,dword ptr [nVarTwo]
009813CB add ecx,1
009813CE mov dword ptr [nVarTwo],ecx
nVarOne=5+(++nVarTwo);
009813D1 mov eax,dword ptr [nVarTwo]
009813D4 add eax,1
009813D7 mov dword ptr [nVarTwo],eax
009813DA mov ecx,dword ptr [nVarTwo]
009813DD add ecx,5
009813E0 mov dword ptr [nVarOne],ecx


表达式短路--通过逻辑与运算和逻辑或运算使语句根据条件在执行时发生中断。

位运算
int BitOperation(int argc)
{
00BE1370 push ebp
00BE1371 mov ebp,esp
00BE1373 sub esp,0C0h
00BE1379 push ebx
00BE137A push esi
00BE137B push edi
00BE137C lea edi,[ebp-0C0h]
00BE1382 mov ecx,30h
00BE1387 mov eax,0CCCCCCCCh
00BE138C rep stos dword ptr es:[edi]
argc=argc<<3;
00BE138E mov eax,dword ptr [argc]
00BE1391 shl eax,3
00BE1394 mov dword ptr [argc],eax
argc=argc>>5;
00BE1397 mov eax,dword ptr [argc]
00BE139A sar eax,5
00BE139D mov dword ptr [argc],eax
argc=argc|0xffff0000;
00BE13A0 mov eax,dword ptr [argc]
00BE13A3 or eax,0FFFF0000h
00BE13A8 mov dword ptr [argc],eax
argc=argc&0x0000ffff;
00BE13AB mov eax,dword ptr [argc]
00BE13AE and eax,0FFFFh
00BE13B3 mov dword ptr [argc],eax
argc=argc^0xffff0000;
00BE13B6 mov eax,dword ptr [argc]
00BE13B9 xor eax,0FFFF0000h
00BE13BE mov dword ptr [argc],eax
argc=~argc;
00BE13C1 mov eax,dword ptr [argc]
00BE13C4 not eax
00BE13C6 mov dword ptr [argc],eax

return argc;
00BE13C9 mov eax,dword ptr [argc]
}
00BE13CC pop edi
00BE13CD pop esi
00BE13CE pop ebx
00BE13CF mov esp,ebp
00BE13D1 pop ebp
00BE13D2 ret

reg==A?C:b+c -->等效于下面

;遇到sub/neg/sbb 就表明是等值比较了
sub reg,A
neg reg
sbb reg,reg
and reg,B
add reg,C 若等值条件成立,其结果为C 否则b+c

例如
int BitOperation(int argc)
{
00DC1370 push ebp
00DC1371 mov ebp,esp
00DC1373 sub esp,0C0h
00DC1379 push ebx
00DC137A push esi
00DC137B push edi
00DC137C lea edi,[ebp-0C0h]
00DC1382 mov ecx,30h
00DC1387 mov eax,0CCCCCCCCh
00DC138C rep stos dword ptr es:[edi]
return argc ? 8 : 20;
00DC138E mov eax,dword ptr [argc]
00DC1391 neg eax
00DC1393 sbb eax,eax
00DC1395 and eax,0FFFFFFF4h
00DC1398 add eax,14h
}
00DC139B pop edi
00DC139C pop esi
00DC139D pop ebx
00DC139E mov esp,ebp
00DC13A0 pop ebp
00DC13A1 ret


代码优化一般有四个方向:
1 执行速度优化
2 内存存储空间优化
3 磁盘存储空间优化
4 编译时间优化



第五章 流程控制语句的识别

if语句
总结:
;先执行各类影响标志位的指令
;其后是各种条件跳转指令
jxx xxxxx

if...else...语句
总结:
;先执行影响标志位的相关指令
jxx ELSE_BEGIN
IF_BEGIN:
……
IF_END:
jmp ELSE_END
ELSE_BEGIN:
……
ELSE_END;

用if构成的多分支流程
总结:
;会影响标志位的指令
jxx ELSE_IF_BEGIN
IF_BEGIN:
……
IF_END:
jmp END
ELSE_IF_BEGIN:
;可影响标志位的指令
jxx ELSE_BEGIN
……
ELSE_IF_END:
jmp END
ELSE_BEGIN:
……
END:
……

Switch 的真相
总结 1
mov reg,mem
;影响标志位的指令
jxx xxxx
;影响标志位的指令
jxx xxx
;影响标志位的指令
jxx xxx
jmp END
……
jmp END
……
jmp END
……
END:
……

总结 2
mov reg,mem
; 对变量进行运算,对齐case地址表的0下标,非必要
;上例中的eax 也可用其他寄存器替换,这里也可以是其他类型的运算
lea eax,[reg+xxxx]
;影响标志位的指令,运行范围检查
jxx DEFAULT_ADDR
jmp dword ptr [eax*4+xxxx] ;


总结 3
mov reg,mem
sub reg,1
mov mem,reg
;影响标志位的指令
jxx xxxx
mov reg , [mem]
;eax不是必要用的,但是之后的数组查询用到的寄存器一定是此处使用的寄存器
xor eax,eax
mov al,byte ptr [xxxx] [reg]
jmp dword ptr [eax*4+xxxx]


do/while/for的比较

do 总结
DO_BEGIN:
.....
;影响标志位的指令
jxx DO_BEGIN ;向上转

while 总结
WHILE_BEGIN:
;影响标志位的指令
jxx WHILE_END
.....
jmp WHILE_BEGIN
WHILE_END:

for 总结
mov mem/reg,xxx
jmp FOR_CMP
FOR_STEP:
;修改循环变量step
mov reg,step
add reg,xxxx ; 修改循环变量的计算过程
mov setp,eax
FOR_CMP:
mov ecx,dword ptr step
;判断循环变量和循环终止条件stepend的关系,满足条件则推出 循环
cmp ecx,setpend
jxx FOR_END
……
jmp FOR_STEP
FOR_END:


第六章 函数的工作原理

栈帧的形成和关闭
栈在内存中是一块特殊的存储空间,它的存储原则是“先进后出”.
esp 与 ebp 栈顶 与 栈底
当 esp 小于 ebp时 就形成了栈帧,栈帧中可以寻址的数据有局部 函数返回地址 函数参数等
vc环境下的调用约定有三种
_cdecl: c/c++默认的调用方式,调用方平衡堆栈,不定参数的函数可以使用。
_stdcall:被调用方平衡堆栈,不定参数的函数无法使用
_fastcall:寄存器方式传参,被调用方平衡堆栈,不定参数的函数无法使用。

使用ebp 或 esp 寻址 寄存器相对间接寻址方式

函数的参数 通过栈结构进行传递。

C/C++将不定长参数的函数定义为:
至少要有一个参数,
所有不定长的参数类型传入时都是dword类型
需在某一个参数中描述参数总个数或将最后一个参数赋值为结尾标记

函数调用的一般工作流程
1 参数传递
通过栈或寄存器方式传递参数
2 函数调用,将返回地址压栈
使用 call 指令调用参数,并将返回地址压入栈中
3 保存栈底
使用栈空间保存调用方的栈底寄存器 ebp
4 申请栈空间和保存寄存器环境
根据函数内部局部变量的大小抬高栈顶让出对应的栈空间
并且将要修改的寄存器保存在栈中
5 函数实现代码
6 还原环境
7 平衡栈空间
平衡局部变量使用的栈空间
8 ret 返回,结束函数调用
更新eip
9 调整 esp,平衡栈顶
此处为_cdecl特有的方式,用于 平衡参数占用的栈顶

两种编译选项下的函数识别
debug

push reg/mem/imm
……
call reg/mem/imm
add esp,xxx

jmp FUN_ADDR
FUN_ADDR:
push ebp
……
mov eax,0cccccccch
rep stos dword ptr [edi];
……
pop ebp
ret


Release

push reg/mem/imm
……
call reg/mem/imm
add esp,xxxx
;函数实现内没有将局部变量初始化为0cccccccch
;若在函数体内不存在内联汇编或异常处理等代码 则使用esp 寻址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值