首页 > 代码库 > c++反汇编与逆向分析 小结

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 寻址