首页
社区
课程
招聘
[原创]逆向实操
2023-2-5 23:23 18188

[原创]逆向实操

2023-2-5 23:23
18188

逆向实操

汇编

《汇编语言》,王爽
《天书夜读》邵坚磊等
《Intel汇编指令集手册》

基础

学习汇编的原则

  • 不推荐写纯汇编程序,一般都是通过_asm{}方式嵌入部分汇编代码
  • 学习汇编的目的是:解底层,调试,逆向分
  • .c-编译->.s-汇编→.o(linux平台)/.obj(windows平台)-链接->.elf/.exe
  • C源程序(.c/.cpp)->汇编程序(.S) ->二进制(.exe/.dll/.sys或者.elf/.so)->加壳保护

    冯.诺伊曼体系

  • 图灵在理论上证明了计算机可以被制造出来
  • 冯.诺伊曼将理论中的计算机变成现实--冯.诺伊曼体系(存储程序)
    • Cpu:ALU(算法逻辑运行单元)+FPU(浮点数计算单元)+CPU Registers(存放数据,地址,代码)
    • Cpu CacheMemory
    • 内存
    • 磁盘

      寄存器

      • 寄存器的位数:1次能存储和处理的位数,也是计算机的位数(16/32/64位计算机)
    • 16位,ax
    • 32位,eax
    • 64位,rax
      32位汇编寄存器
  • 通用寄存器(通用指的是可以存放任何数据,寄存器名字不区分大小写):
    • EAX,EBX,ECX, EDX
  • 源变址目标变址寄存器:
    • 表示ESI和EDI会自++/--
    • ESI
    • EDl(S表示source,D表示Destination)
  • 栈相关寄存器:
    • SS(只要在16位系统才有用,在32/64位系统在SS存放的是个常数),
    • ESP(栈顶),
    • EBP(栈底)
  • 代码段寄存器,程序指令寄存器:
    • CS(代码段的起始地址),
    • EIP(下一条要执行指令的地址)
  • 数据段寄存器:
    • DS(常与ESI寄存器结合使用)(data)
  • 附加段寄存器:
    • ES(常与EDI寄存器集合使用)(extra)
  • 控制寄存器:
    • CR0-CR3(control)
    • CR0包括指示处理器工作方式的控制位,包含启用和禁止分页管理机制的控制位,包含控制浮点协处理器操作的控制位
    • CR1被保留,供今后开发的处理器使用。
    • CR2及CR3由分页管理机制使用。CR2用于发生页异常时报告出错信息。当发生页异常时,处理器把引起员异常的线性地址保存在CR2中。操作系统中的页异常处理程序可以检查CR2的内容,从而查出线性地址空间中的哪一页引起本次异常.
    • CR3用于保存页目录表页面的物理地址,因此被称为PDBR
  • 系统地址寄存器:
    • GDTR
    • LDTR
    • IDTR,中断描述符表寄存器
    • TR寄存器包含了当前正在CPU运行的进程的TSSD(任务段描述符)选择符
  • Flag标志寄存器
    • ZF零标志,零标志ZF用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0;
    • AF辅助进位标志,运算过程中第三位有进位值,置AF=1,否则,AF=0;
    • PF奇偶标志,当结果操作数中偶数个"1",置PF=1,否则,PF=0;
    • SF符号标志,当结果为负时,SF=1;否则,SF=0。溢出时情形例外;
    • CF进/借位标志,最高有效位产生进位值,例如,执行加法指令的,MSB最高位)有进位,置CF=1;否,CF=0;
    • OF溢出标志,若操作数结果超出了机器能表示的范围,则产生溢出,置OF=1,否则,OF=0。
  • 在win2000以上操作系统对于ebx,esi,edi拿来就用,没有进行保护和恢复,如果你的程序中使用了这几个寄存器,请一定先压栈,用完后恢复。
    64位汇编寄存器
  • 在64位系统中,寄存器的表示方法为:
    • 寄存器作用一样,就是保存数据的位数增加了(16位ax->32位eax->64位rax),名字加一个r
    • 通用寄存器:rax, rbx,rcx, rdx
    • 栈寄存器:rsp,rbp
  • r8,r9,r10,r11,r12,r13,r14,r15(这8个是64位汇编中新增加的)
    -fastcall传递参数的寄存器:(rdx,rcx,) r8, r9(arguments)
    • Scratch寄存器:(rbx),r12,r13, r14, r15(scratch-乱写),即可以随时改写的奇存器

汇编中的整数

  • intel汇编:
    • 1aH/0ffffh(后面加,16进制)
    • 17O(8进制)
    • 12D(10进制)
    • 110B(2进制)
  • C语言
    • 0x(前面加,16进制)
    • 0(8进制)
    • (什么都没加,16进制)
    • (C语言中不存在2进制表示方式)

CPU,寄存器,和内存之间的关系

内存寻址

16位汇编-Hello world

  • demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data segment           ;这里定义一个数据段
str db 'hello world$’ ;这里用内存存放字节数据"hellow world",$用来判断字符串是否输出完毕
data ends                 ;数据段的结束标志
code 'segment'          ;这里定义了一个代码段
assume cs:code, ds:data ;程序中定义的段与对应的段寄存器关联起来
start:      ;start标号是程序的开始位置
mov ax,data
mov ds,ax   ;这里把数据段的地址放到数据段寄存器ds中
lea dx,str  ;dx中放将要显示数据的偏移地址
mov ah,9h   ;al(ax低8位) ah(ax高8位)
int 21h     ;调用21号中断的9号功能来显示字符串
mov ah,4ch
int 21h     ;程序返回
 
code ends   ;代码段的结束语
end start   ;定义程序从哪个标号处开始执行
  • 16位汇编开发编译平台:masm,dosbox
    • 安装masm包(含masm,link,debug等工)c:\masm
    • 安装并启动dosbox0.74
    • mount c: c:\masm5
    • 切换到masm安装路径:直接输入c:
    • 编辑a.asm汇编代码
    • 编译
      masm a.asm
      Link a.obj
      debug a.exe
  • VC中开发调试汇编代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//x86
int_tmain(int argc,_TCHAR* arg[])
{
    char str[]="hello world";
    char format[]="%s \n";
    //printf("%s\n",str)
    __asm
    {
        LEA eax,str
        PUSH eax
        LEA eax,format
        PUSH eax
        Mov ebx,[pintf]
        call ebx
        add esp,8 //栈平衡,入栈时是-8,因为栈是从高地址向地址增长
    }
    return 0;
}
 
//x64中: 需要把汇编独立写在一个文件中

汇编指令

  • 汇编指令分类
    • 传送指令:mov/lea/push/pop
    • 算术指令
    • 逻辑指令
    • 串操作指令
    • 控制转移指令(jmp,call,int)
    • 处理机控制指令
    • 汇编指令操作对象:立即数(常量),内存,寄存器
  • 汇编指令构成
    • 1.汇编指令:机器码助记符,有对应的机器码,比如mov指令等;
    • 2.伪指令:无对应的机器码,编译器执行比如assume语句;
    • 3.其他符号:+,-,*,/,由编译器识别,无对应机器码。
  • 常见机器码:
    • short jump eb
    • near jump e9
    • far jump ea
    • je/jz 74
    • jne/jnz 75
    • nop 90
    • int 3 cc
    • ret c3
    • call eax d0ff
    • jmp esp e4ff

      简单指令集和复杂指令集

  • 1.指令集
    • RISC指令集(reduced instruction set computer,简单的指令集)只提供很有限的操作,基本上单周期执行每条指令,其指令长度也是固定(一般4个字节)。
    • CISC指令(complex instruction set computer,复杂的指令集)复杂丰富,功耗大,长度不固定(1到6个字节)
  • 2.Load-Store 结构
    • 在RISC 中,CPU并不会对内存中的数据进行操作,所有的计算都要求在寄存器中完成。而寄存器和内存的通信则由单独的指令来完成。
    • 而在CISC中,CPU是可以直接对内存进行操作的。
  • 3.更多的寄存器
    • 和CISC相比,基于RISC的处理器有更多的通用寄存器可以使用,且每个寄存器都可以进行数据存储或者寻址。
  • 4.RISC指令集能够非常有效地适合于采用流水线、超流水线和超标量技术,从而实现指令级并行操作,提高处理器的性能。
  • 5.x86是cisc代表,arm,Macintosh是risc代表
  • 6.CISC存在的问题:指令系统庞大,指令功能复杂,指令格式、寻址方式多;执行速度慢;难以优化编译,编译程序复杂;80%的指令在20%的运行时间使用;无法并行;无法兼容;

传送指令

mov
  • MOV DST,SRC
  • MOV Reg/Mem, Reg/Mem/lmm
    • Reg-Register(寄存器)
    • Mem-Menory(存储器)
    • lmm-lmmediate(立即数)
  • eg:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
;段寄存器之间不能用MOV指令直接传送
mov eax,5
mov ds,eax ;正确
 
mov ds,es ;错误,据说是设计寄存器的时候电路不同,电路上不通
mov ds,5 ;错误
 
mov ax,word ptr value
mov eax,dword ptr value
mov al,byte ptr value
这里的word ptr/dword ptr/byte ptr实际上是指获取value的宽度(分别对应2字节,4字节和1个字节
 
MOV EAX,DWORD PTR SS:[EBP+1C] ;把
ebp-1C地址存放的值给eax,
;类似C语言中的DWORD *p = EBP-1C;eax = *p;
 
MOV DWORD PTR DS:[ESI+1C],EAX;
;类似C语言中DWORD *p = ESI+1C;eax = *p;
PUSH/POP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
push reg/mem/seg ;ss:[sp] = reg/mem/seg; sp = sp-2
pop reg/seg/mem ;sp = sp+2; reg/seg/mem = ss:[sp]
 
;PUSH进栈指令 格式为:PUSH SRC
push eax ;等价于mov [esp],eax;sub esp 4
;POP出栈指令 格式为:POP DST
pop eax ;等价于add esp 4;mov eax,[eax]
 
push AX
PUSH [2000H]
PUSH CS
POP AX
POP[2000H]
POP SS
lea: load effective address
1
2
3
4
5
6
mov eax,[00400000];DWORD *p = 00400000; eax = *p
lea eax,[00400000];eax = 00400000
 
LEA EAX,[EBX+ECX*2+1] ;eax = ebx+ecx*2+1
mov eax, ebxtecx*2+1 ;不能这样写,mov不能跟表达式
mov eax,[ebx+edx+1] ;DWORD *p = ebx+edx*2+1 ;eax = *p
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int val = 10;
char *s = "hello world";
int a = {1,2,3,4,5};
__asm
{
    lea eax,val //等同lea eax,[val],都把val的地址放到eax寄存器
    mov eax,val //等同mov eax,[val],都是把val的值放到eax寄存器
 
    mov eax,s //等同于mov eax,[s],都是把s的值放到eax  
    lea eax,s //等同于lea eax,[s],都是把s的地址放到eax中
 
    lea eax,[ebx+edx+1]
    mov eax,[ebx+edx+1]
 
    lea eax,a //把数组首地址存放eax
    mov ebx,a //把a[0]存放到ebx
    mov ecx,[ea×] //把eax地址的内存值
    lea edx,[eax] //把eax直接放入edx
    mov edx,[eax] //把eax地址的内存值
}
标志寄存器操作
  • 用来备份和恢复标志寄存器
  • LAHF(Load AH with flags)flags送AH
    • 用于将标志寄存器的低八位送入AH,即将标志寄存器FLAGS中的SF、ZF、A PF、CF五个标志位分别传送到AH的对应位(八位中有三位是光效的)
  • SAHF(store AH into flags)AH送标志寄存器
  • PUSHF(push the flags)标志进栈push eflags
  • POPF(pop the flags)标志出栈pop eflags

    算术指令

  • ADD(add)加法
1
2
3
4
add eax,1 ;eax = eax+1
add eax,ebx ;eax = eax + ebx
add Leax],ebx ;*eax= *eax + ebx
add eax,[ebx] ;eax = eax + *eb
  • ADC(add with carry)带进位加法
1
2
3
4
5
6
7
8
9
adc eax,1 ;eax = eax + 1 + CF
 
;add和adc是否有区别取决于在计算过程中cf寄存器是否为1
xor eax,eax
add eax,1
 
xor eax,eax
adx eax,1
;两者没有区别
  • INC(increment)加1
  • DEC(Decrement)减1
1
2
inc eax ; eax = eax+1
dec eax ; eax = eax-1
  • SUB(subtract)减法
  • SBB(subtract with borrow)带借位减法
    • (DST)=(DST)-(SRC)-CF,其中CF为进位的值
1
2
3
4
5
6
7
8
sub eax, ebx ;eax = eax-ebx
sbb eax,5 ;eax = eax-5-CF
 
;ADC: 同下
 
;SBB:将DX:AX中存放的32位无符号数减去BX尚的16位无符号数
SUB AX,BX    ;结果的低16位,如果AX小于BX将产生借位,导致CF=1
SBB DX,0    ;高16-CF,若前一步出现借位,则据此调整高16位的内容
  • NEG(Negate)求补(把正变负,把负变正)
1
neg eax    ;eax = 0-eax
  • CMP(Compare)比较,相减
1
2
3
4
5
6
7
8
9
cmp eax,5    ;eax-5,只影响标志寄存器,不会修改eax的值
 
jz L1         ;为0
jnz L2        ;不为0
 
L1:
    ...
L2:
    ...
  • MUL(Unsigned Multiple)无符号数乘法
  • IMUL(Signed Multiple)带符号数乘法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
;imul 1个操作数
imul al ;ax = al*al
imul bx ;dx:ax = ax*bx
imul ebx ;edx:eax = eax*ebx
imul dword ptr [ebx] ;edx:eax = eax*[ebx]
 
;5*6
mov eax,5;
mov ebx,6;
imul ebx    ;edx:eax = eax*ebx
 
;imul 2个操作数
imul al r/m8 ;ax = al*r/m8
 
;imul 3个操作数
imul r32,r/m32,imm32 ;r32 = r/m32*imm32

-DIV(Unsigned divide)无符号数除法

1
2
3
4
5
6
7
div ebx ; eax = (edx:eax)/ebx edx = (edx:eax)%ebx
 
;100/2
xor edx, edx ;高32位置0
mov eax,100
mov ebx,2
div ebx
  • IDIV(Signed divide)带符号数除法
    • 与 DIV一样的
  • CBW(Convert byte to word)字节转换为字
  • CWD(Contert word to double word)字转换为双字
1
2
3
4
5
6
;用符号位去填充
mov al 10001101B;
CBW al ; ax = 11111111 10001101B
 
mov ax 12H;
CBW ax ; eax = FF12H
用汇编实现+-*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int add_asm(int x,int y)
{
    __asm
    {
        mov eax,x
        mov ebx,y
        add eax,ebx //函数返回值是放在eax中的
    }
}
 
int sub_asm(int x,int y)
{
    __asm
    {
        mov eax,x
        sub eax,y //函数返回值是放在eax中的
    }
}
 
int imul_asm(int x,int y)
{
    __asm
    {
        mov eax,x
        mov ebx,y
        imul ebx //函数返回值是放在eax中的,edx的值怎么返回?假设不会结果不会超过4Byte
        //如果超过4Byte,在函数中定义多一个,传入参数的指针,edx部分可以存放在指针指向的变量来返回
    }
}
 
int idiv_asm(int x,int y,int *r)
{
    __asm
    {
        xor edx,0
        mov eax,x
        idiv y        //(edx:eax)/y-->q-->eax,r-->edx
        mov ebx,r
        mov [ebx],edx //r
    }
}

逻辑指令

逻辑(位)运算
  • AND(and) 与
  • OR(or) 或
  • NOT(not) 取反
  • XOR(exclusive or) 异或
1
2
3
4
5
and eax,1 ;eax = eax&1
or eax,1  ;eax = eax|1
not eax   ;eax = ~eax
xor eax,eax ;eax= eax^eax
xor eax,ebx ;eax = eax^ebx
  • TEST(test) 测试:(DST)&(SRC)
    test两个操作数相与的结果不保存,只根据其特征置条件码(影响EFLAGS寄存器ZF位,配合jz/jnz等条件跳转指令)
1
2
3
4
5
6
7
8
Mov eax,2
Test eax,1 ;10&01=0
jz L1
 
L1:
    xor eax,eax
L2:
    mov eax,1
移位运算
  • SHL(shift logical left) 逻辑左移
  • SAL(shift arithmetic left) 算术左移,与shl一样
  • SHR(shift logical right) 逻辑右移
  • SAR(shift arithmetic right) 算术右移
  • ROL(Rotate left) 循环左移
  • ROR(Rotate right) 循环右移
  • RCL(Rotate left through carry) 带进位循环左移
  • RCR(Rotate right through carry) 带进位循环右移
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
;格式: SHL OPR,CNT(其余的类似)
;其中OPR可以是除立即数以外的任何寻址方式。移位次数由CNT决定,CNT需要放在CL(CX寄存器的低8位)寄存器中
mov al 00110011B
mov cl,3
 
shl al cl ; al = 11001100B 逻辑左移
sal al cl ; al = 11001100B 算术左移(C语言中对应的左移)
shr al cl; al = 00001100B 逻辑右移(左边用0填充)
sar al cl ; al = 11001100B 算术右移(左边用符号位填充)(C语言中对应的右移)
shr al cl ; al = 10011001B 循环左移
shr al cl ; al = 01100110B 循环右移
 
mov cl,1
clc        ; CF = 0
mov al,88h ; CF,AL = 0 10001000b
rcl al,cl  ; CF,AL = 1 00010000b 
rcl a1,cl  ; CF,AL = 0 00100001b
 
;rcr 同理
用汇编实现算术左/右移
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// x<<y
int sal_asm(int x,int y)
{
    __asm
    {
        mov eax,x
        mov cl,byte ptr y //不会超过127的,目前128位系统并不常见
        sal eax,cl
    }
}
 
/// x>>y
int sar_asm(int x,int y)
{
    __asm
    {
        mov eax,x
        mov cl,byte ptr y //不会超过127的,目前还没有128位系统比不通用
        sar eax,cl
    }
}

串操作指令

movs
  • MOVS BYTE PTR[DI],BYTE PTR[SI](双字)
    • MOVSB(字节)
    • MOVSW(字)
  • 执行的操作:
    • 1.((DI))<-((SI))
    • 2.当方向标志DF=0时用+,当方向标志DF会1时用-,默认DF=0
    • a.字节操作
      (SI)++或者(SI)--
      (DI)++或者(DI)--
    • b.字操作:
      (SI)+=2或者(SI)-=2
      (DI)+=2或者(DI)-=2
    • c.双字操作
      (SI)+=4或者(SI)-=4
      (DI)+=4或者(DI)-=4
  • REP MOVS BYTE PTR[EDI],BYTE PTR[ESI]
  • REP执行的操作
    • 1.如(ECX)=0则退出REP,否则往下执行。
    • 2.(ECX)--
    • 3.执行其中的串操作
    • 4.重复1~3
  • CLD(Clear direction flag)该指令使DF=0,在执行串操作指令时可使地址自动++;
  • STD(Clear direction flag)该指令使DF=1,执行串操作指令时可使地址自动--。
  • CLDSTD指令不影响条件码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/// 实现串拷贝
void strncpy_asm(char*dst,char *src,size_t len)
{
    __asm
    {
        //mov edi,dst;
        //mov esi,src;
        //mov ecx,len;
 
        mov edi,[ebp+8] //目标地址
        mov esi,[ebp+0xc] //源地址
        mov ecx,[ebp+0x10] //ecx里rep的次数
        cld //设置DF=O,(ESI),(EDI)就会自动++
        rep movs byte ptr[edi],byte ptr[esi] //rep表示每执行这条指令后ecx--,直到ecx的值变为0,才停止重复执行
        //rep movsb [edi],[esi]
    }
}
/**
*  低   函数参数右往左次入栈      /\
*  |  |局部变量|                |
*  -------  <---ebp         |
*  |  |老ebp|                  |
*  |   -----                   |
*  |  |老eip|                  |
*  内  ----- <---ebp+8         栈 
*  存  |dst|                   增
*  增  ----- <---ebp+ch        长
*  长  |src|                   方
*  方  ----- <---ebp+10h      
*  向  |len|                   |     
*  |   ----- <---              |
*  \/
* 
*/
stos
  • STOS存入串指令(store string):把eax(ax,al)存放在[edi]
    • STOS DST(双字)
    • STOSB(字节)
    • STOSW(字)
  • 执行的操作:
    • 1.((DI))=(AL)
    • 2.当方向标志DF=0时用+,当方向标志DF会1时用-,默认DF=0
    • a.字节操作:(DI)+=1或者(DI)-=1
    • b.字操作: (DI)+=1或者(DI)+=2
    • c.双字: (DI)+=4或者(DI)+=4
  • 该指令把AL或AX的内容存入由(DI指定的附加段的某单元中,并根据DF的值及数据类型修改DI的内容,当它与REP联用时,可把AL或AX的内容存入1个长度为CX)的缓冲区中。
1
2
3
4
5
6
7
//例子:函数开头汇编部分理解
//局部变量初始化成CCCCCCCCh,即int 3指令(断点),起保护作用
//在debug版本,如果程序出问题(eip指向了局部变量空间),执行int3指令,程序就会断下来,防止程序继续执行破坏系统。
lea edi,[ebp-0C0h] ;12*16=192Byte
mov ecx,30h        ;30h*4Btye=c0h
mov eax,0CCCCCCCCh ;int 3
rep stos dword ptr es:[edi] ;ecx-1,edi+4
lods
  • LODS 从串取指令(load string):把中的值放到eax(ax,al)中
    • LODS SRC(双字)
    • LODSB(字节)
    • LODSW(字)
  • 执行的操作:
    • 1.(AX)<-((SI))
    • 2.当方向标志DF=0时用+,当方向标志DF会1时用-,默认DF=0
    • a.字节操作:(SI)+=1或者(SI)-=1
    • b.字操作: (SI)+=1或者(SI)+=2
    • c.双字: (SI)+=4或者(SI)+=4
  • 该指令把由(SI)指定的数据段中某单元的内容送到AL或AX中,并根据方向标志及数据类型修改SI的内容。指令允许使用段跨越前缀来指定非数据段的存储区。该指令也不影响条件码。一般说来,该指令不和REP联用。有时缓冲区中的一串字符需要逐次取出来测试时,可使用本指令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// lods实现strlen函数
unsigned int strlen_asm(char *s)
{
    __asm
    {
        xor eax,eax
        xor ebx,ebx
        mov esi,s
    L0:
        lodsb      //把(esi)中一个字节存放到al中,esi++
        test al,al //al&al 判断(esi)是否已经指向字符串s的'\0'
        jz L1;
        INC ebx    //ebx寄存器存放着非零字符个数
 
        jmp L0
    L1:
        mov eax,ebx //返回值存放在eax
        }
}
cmps和scas
  1. CMPS和SCAS指令与REPE/REPZ和REPNZ/REPNE联合工作
  • REPE/REPZ当相等/为零时重复串操作
    格式:REPE(或REPZ) cmps/scas
  • 执行的操作:
    • 1.如(CX)=0或ZF=0(即某次比较的结果两个操作数不等)时退出,否则(cx)!=0&&zf==1往下执行
    • 2.(CX)-=1
    • 3.执行其后的串指令
    • 4.重复1)~3)
  • REPNE/REPNZ当不相等/不为零时重复串操作
  • 格式:REPNE(或RERNZ) cmps/scas
  • 执行的操作:
    • 1.(cx)!=0&&zf==0继续执行,否则(CX=0)或ZF=1退出
    • 其他操作与REPE完全相同。
  1. CMPS串比较指令:把[esi]和[edi]作差比较
  • 格式: CMPS SRC, DST(双字)
  • CMPSB(字节)
  • CMPSW(单字)
  • 执行的操作:
    • 1.((SI))-((DI))
    • 2.当方向标志DF=0时用+,当方向标志DF会1时用-,默认DF=0
    • a.字节操作:(SI)+=1,(DI)+=1或者(SI)-=1,(DI)-=1
    • b.字操作:(SI)+=2,(DI)+=2或者(SI)-=2,(DI)-=2
    • c.双字: (SI)+=4,(DI)+=4或者(SI)-=4,(DI)-=4
  • 指令把由(Sl)指向的数据段中的一个字(或字节)与由(DI)指向的附加段中的一个字(字节)相减,但不保存结果只根据结果设置条件码,指令的其他特性和MOVS指令的规定相同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/// strncmp
int strncmp_asm(const char* s1, const char* s2, size_t len)
{
    __asm
    {
        mov ecx, len
        mov esi, s1
        mov edi, s2
        repz cmpsb //当ecx不为零并且zf=1 ([esi]-[edi]==0),重复执行
        jecxz L1 //len个字节比较完毕,len-1字符相等
        js L2    // ecx不为0就退出,说明前len个字符就已经发现不相等的字符了
        jns L2
    L1:
        js L2 //负数,最后一个字符不等
        jns L2 //非负,最后一个字符相等
        xor eax, eax //最后一个字符相等
        jmp L4
    L2:
        xor eax, eax
        xor ebx, ebx
        mov al,byte ptr [esi - 1] //最后不相等的字符
        mov bl,byte ptr [edi - 1]
        sub eax, ebx
        jmp L4
    L4:
    }
}
  1. SCAS串扫描指令:把[eax]和[edi]做差比较
  • 格式:SCAS DST(双字)
    • SCASB(字节)
    • SCASW(字)
  • 执行的操作:
    • 1.(AL)-((DI))
    • 2.当方向标志DF=0时用+,当方向标志DF会1时用-,默认DF=0
    • a.字节操作:(DI)+=1或者(DI)-=1
    • b.字操作:(DI)+=2或者(DI)-=2
    • c.双字: (DI)+=4或者(DI)-=4
  • 该指令把AL(或AX)的内容与由(DI)指定的在附加段中的一个字节(或字)进行比较,并不保存结果,只根据结果置条件码。指令的其他特性和MOVS的规定相同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// strcmp
int strcmp_asm(const char* s1, const char* s2)
{
    __asm
    {
        xor eax,eax
        mov esi,s1
        mov edi,s2
    L2:
        lodsb        //(al) < --byte ptr[esi]
        scasb        //(al)-byte ptr[edi]
        jne L1;
        test al,al  //判断是否为'\0'
        jne L2      //不是'\0',到L2继续比较
        xor eax,eax
        jmp L3;
    L1:
        xor ebx,ebx
        mov bl,byte ptr[edi - 1]
        sub eax,ebx
    L3:
    }
}

控制转移指令

跳转指令分三类:

  • 无条件转移指令
  • 根据CX、ECX寄存器的值跳转:
    • JCXZ(CX为0则跳转)
    • JECXZ(ECX也为0则跳转);
  • 根据EFLAGS寄存器的标志位跳转
    1.JMP
1
2
3
4
5
6
7
short jump:eb
near jump:e9
far jump:ea
 
jmp offset(relative = addr - current)
 
jmp address
  • 1.短转移(8位)
    • 格式:JMP SHORT QPR;
    • -128--127
  • 2.近转移(16位)
    • 格式:JMP NEAR PTR OPR
    • 同一个段内-32768至32761
  • 3.段间跳转(32位)
    • 格式:JMP FAR PTR OPR
      2.JCXZ、JECXZ、LOOP
  • 根据CX、ECX寄存器的值跳转:
    • JCXZ(CX为0则跳转)
    • JECXZ(ECX也为0则跳转);
  • LOOP循环指令
    • 格式:LOOP OPR
    • 测试条件:(CX)!=0
    • rep指令类似
  • LOOPZ/LOOPE当为零或相等时循环指令
    • 格式:LOOPZ(或LOOPE)OPR
    • 测试条件:(CX)!=0&&ZF==1
    • repz指令类似
  • LOOPNZ/LOOPNE当不为零或不相等时循环指令
    • 格式:LOOPNZ(或LOOPNE)OPR
    • 测试条件:(CX)!=0&&ZF==0
    • repnz指令类似
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//计算2^n需要n - 1条重复的指令add ax,ax
//2^index,类似报纸对折
unsigned int power_2(unsigned int index)
{
    __asm
    {
        mov eax, 2
        mov edx, index
        sub edx, 1
        mov ecx, edx;
    s:
        add eax, eax
        loop s
    }
}
3.根据EFLAGS寄存器的标志位跳转
  • EFLAGS寄存器的变化依赖算术指令、 逻辑指令(带进位循环左/右移)、串操作指令(cmps和scas)
    • JE:等于则跳转
    • JNE:不等于则跳转
    • JZ:为0则跳转
    • JNZ:不为0则跳转
    • JS:为负则跳转
    • JNS :不为负则跳转
    • JC:进位则跳转
    • JNC:不进位则跳转
    • JO:溢出则跳转
    • JNO:不溢出则跳转
  • A:above,无符号
    • JA:无符号大于则跳转
    • JNA:无符号不大于则跳转
    • JAE:无符号大于等于则跳转
    • JNAE:无符号不大于等于则跳转
  • G: great,有符号
    • JG:有符号大于则跳转
    • JNG:有符号不大于则跳转
    • JGE:有符号大于等于则跳转
    • JGE:有符号不大于等于则跳转
  • B: below,无符号小于
    • JB:无符号小于则跳转
    • JNB:无符号不小于则跳转
    • JBE:无符号小于等于则跳转
    • JNBE:无符号不小于等于则跳转
  • L:less,有符号小于
    • JL:有符号小手则跳转
    • JNL:有符号不小于则跳转
    • JLE:有符号小于等于则跳转
    • JNLE:有符号不小于等于则跳转
    • JP:奇偶位置位则跳转
    • JNP:奇偶位清除则跳转
    • JPEN:奇偶位相等则跳转
    • JPO:奇偶位不等则跳转
      CALL、RET、INT
  1. 子程序
  • CALL调用指令(段内,段间,涉及到CS和IP入栈)
1
call printf  //在扁平模型下只需要push eip
  • RET返回指令
  • RETN/RETF
    • 在扁平模型下不会涉及到CS寄存器,所以不考虑这种情况,忽略RETF即可
    • N表示near ,retn等价于pop eip
    • F表示far,retn等价于`pop eip;pop ecs``
  • RETN 操作数/RETF 操作数
    • 带有操作数的RETN/RETF指令则是在POP之后,执行ESP=ESP+操作数
1
2
3
4
5
6
7
ret       //cdecl,由调用者去负责栈平衡
 
//pop eip
 
ret 8     //stdcall,fastcal
//pop eip
//add esp,8 //被调用者栈平衡
  • RET既有可能是retn,也有可能是retf,由编译器决定。
  1. 中断INT指令
    调用:INT TYPE
    返回:INT iret

处理机控制指令

  1. 标志处理指令
  • CLC(Clear carry)进位位置0指令CF P=0
  • CMC(Complement carry)进位位求反指令~CF
  • STC(Set carry)进位位置1指令CF=1
  • CLD(Clear direction)方向标志置0指令DF=0
  • STD(Set direction)方向标志置1指令DF=1
  • CLI(Clear interrupt)中断标志置0指令IF=0
  • STI(Set interrupt)中断标志置1指令IF=1
  1. 其他处理机控制指令
  • NOP(No Opreation)无操作0x90
  • HLT(Halt) 停机
    -WAIT(Wait) 等待

INTEL和AT&T汇编的区别

  • windows使用的是intel汇编
  • Linux使用的是AT&T汇编
  • intel和AT&T都属于x86汇编,属于CISC
  • Android使用的是arm汇编,属于RISC
  • 寄存器前面加%
  • 数据流动方向相反
  • 操作数据的长度体现在指令的操作码中
  • []变成()
  • 地址+offset寻址
    • 常数在括号外
    • 乘法体现在操作数的次序上
  • 立即数
1
2
3
4
5
6
7
8
9
10
//立即数
//intel
moveax,8
mov ebx,Offffh
int 80h
 
//at&t
movI $8,%eax
movl $0xffff,%ebx
int $0x80

花指令

花指令是程序中的无用指令或者垃圾指令,故意干扰各种反汇编静态分析工具,但是程序不受任何影响,缺少了它也能正常运行。加花指令后,IDA Pro等分析工具对程序静态反汇编时,往往会出现错误或者遭到破环,加大逆向静态分析的难度,从而隐藏自身的程序结构和算法从而较好的保护自己。

1
2
3
4
5
6
7
8
//花指令1
//代码没有任何作用,但会混淆调试者
push edx
pop edx
inc ecx
dec ecx
add esp,1
sub esp,1
1
2
3
4
//花指令2
jmp Labe1
db opcode     //在jmp后面加上一个字节的机器码,并不完整(完整的汇编指令是一个机器码+操作数),但不影响程序的执行(jmp会跳过这条残缺的汇编指令)。在IDE工具反汇编的时候,看到机器码(以为紧接着的就是操作数,接着后面的反汇编都错位了)
Label1:

但IDE采用的反汇编算法是递归如果没有指令调整到这个位置,就拒绝对这条指令进行反汇编。

1
2
3
4
5
//花指令3
jz Label      //花指令对代码依旧没有影响,为0的时候会跳过花指令。
jnz Label     //0才跳转到 Label,为0则执行下面的花指令,IDE此时就认为这条花指令需要反汇编。
db opcode
Label:

C语言对应的底层汇编本质

栈的4种形态

  • FD:Full Descend Stack满递减栈
  • ED:Empty Descend Stack空递减栈
  • FA:Full Ascend Stack 满递增栈
  • EA:Empty Ascend Stack空递增栈
    满递减栈(FD)
  • :栈顶指针指向最后一个入栈的有效数据项,
  • 递减:栈往低地址生长,栈的增长方向和内存的增长方向相反
  • 入栈时:栈顶指针先减,再存数据;
  • 出栈时:先取数据,栈顶指针再加;
  • x86arm都是满递减栈,所以在这种情况下:
1
2
3
4
5
6
7
8
9
push eax
//等价于:
sub esp 4
mov [esp],eax
 
pop eax
//等价于
mov eax,[esp]
add esp,4
空递减栈(ED)

  • :栈顶指针指向下一个入栈的有效数据项,
  • 递减:栈往低地址生长,栈的增长方向和内存的增长方向相反
  • 入栈时:先存数据,栈顶指针再减;
  • 出栈时:栈顶指针先加,再取数据;
1
2
3
4
5
6
7
8
9
push eax
//等价于:
mov [esp],eax
sub esp 4
 
pop eax
//等价于
add esp,4
mov eax,[esp]
满递增栈(FA)

  • :栈顶指针指向最后一个入栈的有效数据项,
  • 递增:栈往高地址生长,栈的增长方向和内存的增长方向相同
  • 入栈时:栈顶指针先加,再存数据;
  • 出栈时:先取数据,栈顶指针再减;
1
2
3
4
5
6
7
8
9
push eax
//等价于:
add esp 4
mov [esp],eax
 
pop eax
//等价于
mov eax,[esp]
sub esp,4
空递增栈(EA)

  • :栈顶指针指向下一个入栈的有效数据项,
  • 递增:栈往高地址生长,栈的增长方向和内存的增长方向相同
  • 入栈时:先存数据,栈顶指针再加;
  • 出栈时:栈顶指针先减,再取数据;
1
2
3
4
5
6
7
8
9
push eax
//等价于:
mov [esp],eax
add esp 4
 
pop eax
//等价于
sub esp,4
mov eax,[esp]

X86系统中函数调约定

  • cdecl:
    • 参数从右往左依次入栈,栈对齐
    • 调用者栈平衡(程序员自己负责栈平衡)
    • 变参函数(比如printf)必须要使用cdecl调用约定,因为被调用者是不知道函数的参数的个数的,只有调用者才知道,必须要调用者自己负责栈平衡。
  • stdcall
    • 参数从右往左依次入栈(栈对齐)
    • 被调用者栈平衡(函数本身)
    • 驱动中使用的是stdcall
  • fastcall
    • 前两个参数放入ecx,edx,后面参数从右往左依次入栈(栈对齐),比如函数有5个参数,后面的第3,4,5参数从右往左依次入栈,第2参数放入edx,第1参数放入ecx。
    • 被调用者栈平衡
    • 在x64采用的都是fastcall,而且增加了r8,r9寄存器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
*  函数参数右往左次入栈,栈对齐,参数大小会提升到4个字节,比如char,short 1Byte/2Byte数据存放在4Byte的空间中
*    在printf中float会提升到double(4Byte->8Byte)
*                             
*  低   --------- <---esp         
*  |   |局部变量区|              /\
*  |   ---------- <---ebp       |
*  |   |  老ebp |               |
*  |   ----------                |
*  |   |  老eip |               |
*  内  ---------- <----ebp栈平衡 栈
*  存  |  参数1  |               增
*  增  ----------              
*  长  |   ...  |               方
*  方  ----------              
*  向  |  参数n  |               |  
*  |   ----------               |
*  \/
* 
*/

  • 如果函数声明的时候没有显示标明调用约定,默认是_cdecl,也可以设置默认成其他。
    demo:汇编分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
///在debug版本才能看到详细的汇编代码,release版本,编译优化之后,汇编变得非常简洁了
///caller 调用者
    fun1('a', 1, 2); //cdecl
00D320AD 6A 02                push        2 
00D320AF 6A 01                push        1 
00D320B1 6A 61                push        61h 
00D320B3 E8 35 F3 FF FF       call        fun1 (0D313EDh//push eip; jmp
00D320B8 83 C4 0C             add         esp,0Ch  //栈平衡,在main函数中做,即由调用者负责栈平衡
    fun2('b', 3, 4); //stdcall
00D320BB 6A 04                push        4 
00D320BD 6A 03                push        3 
00D320BF 6A 62                push        62h 
00D320C1 E8 39 F0 FF FF       call        fun2 (0D310FFh
    fun3('c', 5, 6); //fastcall
00D320C6 6A 06                push        6   //3参数从右往左依次入栈
00D320C8 BA 05 00 00 00       mov         edx,5  //2参数放入edx
00D320CD B1 63                mov         cl,63h  //1参数放入ecx。
00D320CF E8 06 F2 FF FF       call        fun3 (0D312DAh
 
/// callee    被调用者
int _cdecl fun1(char c, short s, int x)
{
00D31840 55                   push        ebp 
00D31841 8B EC                mov         ebp,esp 
00D31843 81 EC C0 00 00 00    sub         esp,0C0h //局部变量的空间
00D31849 53                   push        ebx  //备份
00D3184A 56                   push        esi 
00D3184B 57                   push        edi 
00D3184C 8B FD                mov         edi,ebp   //int 3填充局部变量空间
00D3184E 33 C9                xor         ecx,ecx 
00D31850 B8 CC CC CC CC       mov         eax,0CCCCCCCCh 
00D31855 F3 AB                rep stos    dword ptr es:[edi] 
00D31857 B9 03 C0 D3 00       mov         ecx,offset _8C6D00FD_TestAsm@cpp (0D3C003h
00D3185C E8 F6 FA FF FF       call        @__CheckForDebuggerJustMyCode@4 (0D31357h
    return c + s + x;
00D31861 0F BE 45 08          movsx       eax,byte ptr [c] 
00D31865 0F BF 4D 0C          movsx       ecx,word ptr [s] 
00D31869 03 45 10             add         eax,dword ptr [x] 
00D3186C 03 C1                add         eax,ecx  //结果放在eax寄存器中
}
00D3186E 5F                   pop         edi  //恢复,后进先出
00D3186F 5E                   pop         esi 
00D31870 5B                   pop         ebx 
00D31871 81 C4 C0 00 00 00    add         esp,0C0h  //释放局部变量的空间
00D31877 3B EC                cmp         ebp,esp 
00D31879 E8 EE F9 FF FF       call        __RTC_CheckEsp (0D3126Ch
00D3187E 8B E5                mov         esp,ebp 
00D31880 5D                   pop         ebp 
00D31881 C3                   ret  //pop eip ;jmp eip
int _stdcall fun2(char c, short s, int x)
{
00D318A0 55                   push        ebp 
00D318A1 8B EC                mov         ebp,esp 
00D318A3 81 EC C0 00 00 00    sub         esp,0C0h 
00D318A9 53                   push        ebx 
00D318AA 56                   push        esi 
00D318AB 57                   push        edi 
00D318AC 8B FD                mov         edi,ebp 
00D318AE 33 C9                xor         ecx,ecx 
00D318B0 B8 CC CC CC CC       mov         eax,0CCCCCCCCh 
00D318B5 F3 AB                rep stos    dword ptr es:[edi] 
00D318B7 B9 03 C0 D3 00       mov         ecx,offset _8C6D00FD_TestAsm@cpp (0D3C003h
00D318BC E8 96 FA FF FF       call        @__CheckForDebuggerJustMyCode@4 (0D31357h
    return c + s + x;
00D318C1 0F BE 45 08          movsx       eax,byte ptr [c] 
00D318C5 0F BF 4D 0C          movsx       ecx,word ptr [s] 
00D318C9 03 45 10             add         eax,dword ptr [x] 
00D318CC 03 C1                add         eax,ecx 
}
00D318CE 5F                   pop         edi 
00D318CF 5E                   pop         esi 
00D318D0 5B                   pop         ebx 
00D318D1 81 C4 C0 00 00 00    add         esp,0C0h 
00D318D7 3B EC                cmp         ebp,esp 
00D318D9 E8 8E F9 FF FF       call        __RTC_CheckEsp (0D3126Ch
00D318DE 8B E5                mov         esp,ebp 
00D318E0 5D                   pop         ebp 
00D318E1 C2 0C 00             ret         0Ch  //栈平衡,在fun2函数中做,即由被调用者负责栈平衡
 
int _fastcall fun3(char c, short s, int x)
{
00D31900 55                   push        ebp 
00D31901 8B EC                mov         ebp,esp 
00D31903 81 EC D8 00 00 00    sub         esp,0D8h 
00D31909 53                   push        ebx 
00D3190A 56                   push        esi 
00D3190B 57                   push        edi 
00D3190C 51                   push        ecx 
00D3190D 8D 7D E8             lea         edi,[ebp-18h
00D31910 B9 06 00 00 00       mov         ecx,6 
00D31915 B8 CC CC CC CC       mov         eax,0CCCCCCCCh 
00D3191A F3 AB                rep stos    dword ptr es:[edi] 
00D3191C 59                   pop         ecx 
00D3191D 66 89 55 EC          mov         word ptr [s],dx  //edx存放第2参数,放入局部变量区,位置随机
00D31921 88 4D F8             mov         byte ptr [c],cl  //ecx存放第1参数,放入局部变量区
00D31924 B9 03 C0 D3 00       mov         ecx,offset _8C6D00FD_TestAsm@cpp (0D3C003h
00D31929 E8 29 FA FF FF       call        @__CheckForDebuggerJustMyCode@4 (0D31357h
    return c + s + x;
00D3192E 0F BE 45 F8          movsx       eax,byte ptr [c] 
00D31932 0F BF 4D EC          movsx       ecx,word ptr [s] 
00D31936 03 45 08             add         eax,dword ptr [x] 
00D31939 03 C1                add         eax,ecx 
}
00D3193B 5F                   pop         edi 
00D3193C 5E                   pop         esi 
00D3193D 5B                   pop         ebx 
00D3193E 81 C4 D8 00 00 00    add         esp,0D8h 
00D31944 3B EC                cmp         ebp,esp 
00D31946 E8 21 F9 FF FF       call        __RTC_CheckEsp (0D3126Ch
00D3194B 8B E5                mov         esp,ebp 
00D3194D 5D                   pop         ebp 
00D3194E C2 04 00             ret         4

x64系统中函数调约定

  • x64x86的fastcall的区别:
      1. 比ecx(rcx),edx(rdx)寄存器多了2个r8,r9
      1. 参数入栈,会对齐到8个字节,参数的栈整体的大小是按照16个字节对齐,即能被16整除。
      1. 函数的前4个参数存放到了rcx,rdx,r8,r9四个寄存器中,但在栈出也会预留4个空间(称为shadow space,x32上不会预留)
      1. 统一调用者来负责栈的平衡(因为在x64统一使用fastcall,对于变参函数,只有调用者才知道参数个数,所以必须由调用者负责栈平衡)
      1. 局部变量空间分配和初始化由调用者完成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
int main()
{
00007FF69B5A2030 40 55                push        rbp 
00007FF69B5A2032 57                   push        rdi 
00007FF69B5A2033 48 81 EC 08 01 00 00 sub         rsp,108h  //局部变量空间由调用者分配和初始化
00007FF69B5A203A 48 8D 6C 24 40       lea         rbp,[rsp+40h
00007FF69B5A203F 48 8D 0D E3 0F 01 00 lea         rcx,[__CCDDBD56_TestAsm_x64@cpp (07FF69B5B3029h)] 
00007FF69B5A2046 E8 84 F3 FF FF       call        __CheckForDebuggerJustMyCode (07FF69B5A13CFh
/// caller 调用者
func(1, 2, 3, 4, 'a', 5, 6);
00007FF69B5A204B C7 44 24 30 06 00 00 00 mov         dword ptr [rsp+30h],6 
00007FF69B5A2053 66 C7 44 24 28 05 00 mov         word ptr [rsp+28h],5 
00007FF69B5A205A C6 44 24 20 61       mov         byte ptr [rsp+20h],61h 
00007FF69B5A205F 41 B9 04 00 00 00    mov         r9d,4 
00007FF69B5A2065 41 B8 03 00 00 00    mov         r8d,3 
00007FF69B5A206B BA 02 00 00 00       mov         edx,2 
00007FF69B5A2070 B9 01 00 00 00       mov         ecx,1 
00007FF69B5A2075 E8 14 F3 FF FF       call        std::basic_ostream<char,std::char_traits<char> >::sentry::sentry (07FF69B5A138Eh
}
00007FF69B5A207A 33 C0                xor         eax,eax 
00007FF69B5A207C 48 8D A5 C8 00 00 00 lea         rsp,[rbp+0C8h//调用者完成栈平衡,注意栈的整体大小要对齐到16个字节
00007FF69B5A2083 5F                   pop         rdi 
00007FF69B5A2084 5D                   pop         rbp 
00007FF69B5A2085 C3                   ret  
 
/// callee 被调用者
int func(int x1, int x2, int x3, int x4, char c, short s, int x)
{
00007FF69B5A1E40 44 89 4C 24 20       mov         dword ptr [rsp+20h],r9d  //shadow space
00007FF69B5A1E45 44 89 44 24 18       mov         dword ptr [rsp+18h],r8d 
00007FF69B5A1E4A 89 54 24 10          mov         dword ptr [rsp+10h],edx 
00007FF69B5A1E4E 89 4C 24 08          mov         dword ptr [rsp+8],ecx 
00007FF69B5A1E52 55                   push        rbp  //push 老的rbp
00007FF69B5A1E53 57                   push        rdi 
00007FF69B5A1E54 48 81 EC E8 00 00 00 sub         rsp,0E8h 
00007FF69B5A1E5B 48 8D 6C 24 20       lea         rbp,[rsp+20h
00007FF69B5A1E60 48 8D 0D C2 11 01 00 lea         rcx,[__CCDDBD56_TestAsm_x64@cpp (07FF69B5B3029h)] 
00007FF69B5A1E67 E8 63 F5 FF FF       call        __CheckForDebuggerJustMyCode (07FF69B5A13CFh
    return x1 + x2 + x3 + x4 + c + s + x;
00007FF69B5A1E6C 8B 85 E8 00 00 00    mov         eax,dword ptr [x2] 
00007FF69B5A1E72 8B 8D E0 00 00 00    mov         ecx,dword ptr [x1] 
00007FF69B5A1E78 03 C8                add         ecx,eax 
00007FF69B5A1E7A 8B C1                mov         eax,ecx 
00007FF69B5A1E7C 03 85 F0 00 00 00    add         eax,dword ptr [x3] 
00007FF69B5A1E82 03 85 F8 00 00 00    add         eax,dword ptr [x4] 
00007FF69B5A1E88 0F BE 8D 00 01 00 00 movsx       ecx,byte ptr [c] 
00007FF69B5A1E8F 03 C1                add         eax,ecx 
00007FF69B5A1E91 0F BF 8D 08 01 00 00 movsx       ecx,word ptr [s] 
00007FF69B5A1E98 8B 95 10 01 00 00    mov         edx,dword ptr [x] 
00007FF69B5A1E9E 03 D0                add         edx,eax 
00007FF69B5A1EA0 8B C2                mov         eax,edx 
00007FF69B5A1EA2 03 C8                add         ecx,eax 
00007FF69B5A1EA4 8B C1                mov         eax,ecx 
}
00007FF69B5A1EA6 48 8D A5 C8 00 00 00 lea         rsp,[rbp+0C8h
00007FF69B5A1EAD 5F                   pop         rdi 
00007FF69B5A1EAE 5D                   pop         rbp 
00007FF69B5A1EAF C3                   ret  //被调用者没有做栈平衡

函数传参的底层汇编

  • 传值:栈上是对实参值的拷贝
  • 传指针:栈上是对实参地址的拷贝
  • 传引用:同传指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    func1(&a); //传指针
00CE1946 8D 45 F4             lea         eax,[a]  //把a的地址传给eax
00CE1949 50                   push        eax  //入栈
00CE194A E8 91 F8 FF FF       call        func1 (0CE11E0h
00CE194F 83 C4 04             add         esp,4   //没有声明调用约定,默认是cdecl调用约定,需要调用者进行栈平衡
    func2(a); //传引用
00CE1952 8D 45 F4             lea         eax,[a]  //传引用和传指针完全一样
00CE1955 50                   push        eax 
00CE1956 E8 07 F9 FF FF       call        func2 (0CE1262h
00CE195B 83 C4 04             add         esp,4 
    func3(a); //传值
00CE195E 8B 45 F4             mov         eax,dword ptr [a]  //把a的值传给eax
00CE1961 50                   push        eax 
00CE1962 E8 6F F8 FF FF       call        func3 (0CE11D6h
00CE1967 83 C4 04             add         esp,4

语句的底层汇编

i++/++i
1
2
3
4
5
6
7
8
9
10
int i = 0;
00351825 C7 45 F8 00 00 00 00 mov         dword ptr [i],0 
    i++; //c语言中i++不是原子操作,在汇编中可能对应多个汇编指令,在单核多线程环境中中不是多线程安全的
0035182C 8B 45 F8             mov         eax,dword ptr [i] 
0035182F 83 C0 01             add         eax,1 
00351832 89 45 F8             mov         dword ptr [i],eax 
    i++;
00351835 8B 45 F8             mov         eax,dword ptr [i] 
00351838 83 C0 01             add         eax,1 
0035183B 89 45 F8             mov         dword ptr [i],eax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int i = 0;
00D11825 C7 45 F8 00 00 00 00 mov         dword ptr [i],0 
    //i++; //c语言中i++不是原子操作,在汇编中可能对应多个汇编指令,在单核多线程环境中中不是多线程安全的
    //++i;
    int a = 0;
00D1182C C7 45 EC 00 00 00 00 mov         dword ptr [a],0 
    int b = 0;
00D11833 C7 45 E0 00 00 00 00 mov         dword ptr [b],0 
    a = i++; //a = i; i = i + 1
00D1183A 8B 45 F8             mov         eax,dword ptr [i] 
00D1183D 89 45 EC             mov         dword ptr [a],eax  //a = i
00D11840 8B 4D F8             mov         ecx,dword ptr [i] 
00D11843 83 C1 01             add         ecx,1 
00D11846 89 4D F8             mov         dword ptr [i],ecx  //i = i + 1
    b = ++i; //i = i + 1; a = i;
00D11849 8B 45 F8             mov         eax,dword ptr [i] 
00D1184C 83 C0 01             add         eax,1  
00D1184F 89 45 F8             mov         dword ptr [i],eax  //i = i + 1
00D11852 8B 4D F8             mov         ecx,dword ptr [i] 
00D11855 89 4D E0             mov         dword ptr [b],ecx  //a = i
控制语句
for
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/// for模型
for:
    i=0;
    jmp A;
B:
    i++
A:
    cmp i,0ah
    jge OUT;
 
    ... //循环体要执行的代码
 
    jmp B;
OUT:
 
 
///对应汇编代码
    for ( i = 0; i < 10; i++)
00264488 C7 45 F8 00 00 00 00 mov         dword ptr [i],0  //i=0
0026448F EB 09                jmp         __$EncStackInitStart+5Eh (026449Ah//跳转到i<10?
00264491 8B 45 F8             mov         eax,dword ptr [i] 
00264494 83 C0 01             add         eax,1  //i++
00264497 89 45 F8             mov         dword ptr [i],eax 
0026449A 83 7D F8 0A          cmp         dword ptr [i],0Ah  //i-10
0026449E 7D 13                jge         __$EncStackInitStart+77h (02644B3h//大于等于10,跳出循环
    {
        printf("% d\n", i);
002644A0 8B 45 F8             mov         eax,dword ptr [i] 
002644A3 50                   push        eax  //入栈
002644A4 68 CC 7B 26 00       push        offset string "% d\n" (0267BCCh)   //把格式化描述符入栈
002644A9 E8 F9 CE FF FF       call        _printf (02613A7h//调用printf
002644AE 83 C4 08             add         esp,8  //栈平衡,printf是变参函数,是cdecl调用约定,需要调用者进行栈平衡
    }
002644B1 EB DE                jmp         __$EncStackInitStart+55h (0264491h//跳转到i++
}
002644B3 33 C0                xor         eax,eax  //跳出循环 retrun 0
002644B5 5F                   pop         edi 
002644B6 5E                   pop         esi 
002644B7 5B                   pop         ebx
while
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/// while模型
while:
A:
    cmp i,0ah;
    jge OUT;
 
    ... //循环体要执行的代码
 
    jmp A;
OUT:
 
 
/// while对应的汇编代码
    while (i < 10)
007344BA 83 7D F8 0A          cmp         dword ptr [i],0Ah  //i-10
007344BE 7D 0B                jge         __$EncStackInitStart+8Fh (07344CBh//大于等于10,则跳出循环
    {
        i++;
007344C0 8B 45 F8             mov         eax,dword ptr [i] 
007344C3 83 C0 01             add         eax,1 
007344C6 89 45 F8             mov         dword ptr [i],eax  //i++
    }
007344C9 EB EF                jmp         __$EncStackInitStart+7Eh (07344BAh//跳转到i<10?
    return 0;
007344CB 33 C0                xor         eax,eax  // retrun 0
}
do-while
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// do{}while()模型
do-while
A:
    ... //循环体要执行的代码
 
    cmp i,0ah;
    jl A;
OUT:
 
 
/// do{}while()对应的汇编代码
 do
    {
        i++;
006344D2 8B 45 F8             mov         eax,dword ptr [i] 
006344D5 83 C0 01             add         eax,1 
006344D8 89 45 F8             mov         dword ptr [i],eax  //i++
    } while (i < 10);
006344DB 83 7D F8 0A          cmp         dword ptr [i],0Ah  //i-10
006344DF 7C F1                jl          __$EncStackInitStart+96h (06344D2h) //小于10则跳转到i++ 
 
    return 0;
006344E1 33 C0                xor         eax,eax
if-else
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/// if-else模型
if-else
    cmpi,0
    jle ELSE;
 
    ...
 
    jmp OUT;
ELSE:
 
    ...
 
OUT:
 
 
/// if-else对应的汇编代码
if (i > 0)
008251E1 83 7D F8 00          cmp         dword ptr [i],0  //i-0
008251E5 7E 0B                jle         __$EncStackInitStart+0B6h (08251F2h)   //小于等于0,则跳转到i++
    {
        i--;
008251E7 8B 45 F8             mov         eax,dword ptr [i] 
008251EA 83 E8 01             sub         eax,1 
008251ED 89 45 F8             mov         dword ptr [i],eax 
    }
008251F0 EB 09                jmp         __$EncStackInitStart+0BFh (08251FBh//否则(即大于0),i--后,跳转到retrun 0退出判断
    else
    {
        i++;
008251F2 8B 45 F8             mov         eax,dword ptr [i] 
008251F5 83 C0 01             add         eax,1 
008251F8 89 45 F8             mov         dword ptr [i],eax 
    }
 
    return 0;
008251FB 33 C0                xor         eax,eax
switch语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/// switch模型
switch:
    cmp value,1
    je A;
    cmp value,2
    je B;
    cmp value,3
    je C;
    jmp DEFAULT;
A:
    ...
    jmp OUT
B:
    ...
    jmp OUT;
C:
    ...
    jmp OUT;
 
DEFAULT:
    ...
 
OUT:
 
/// seitch对应的汇编指令
  switch (value)
001C5286 8B 45 D0             mov         eax,dword ptr [value] 
001C5289 89 85 08 FF FF FF    mov         dword ptr [ebp-0F8h],eax  //value存入局部变量的内存
001C528F 83 BD 08 FF FF FF 01 cmp         dword ptr [ebp-0F8h],1  //value-1
001C5296 74 14                je          __$EncStackInitStart+100h (01C52ACh//相等,则跳到case 1
001C5298 83 BD 08 FF FF FF 02 cmp         dword ptr [ebp-0F8h],2  //value-2
001C529F 74 1A                je          __$EncStackInitStart+10Fh (01C52BBh//相等,则跳到case 2
001C52A1 83 BD 08 FF FF FF 03 cmp         dword ptr [ebp-0F8h],3  //value-3
001C52A8 74 20                je          __$EncStackInitStart+11Eh (01C52CAh//相等,则跳到case 3
001C52AA EB 2D                jmp         __$EncStackInitStart+12Dh (01C52D9h//前面都不符合则跳到default。
    {
    case 1:
        printf("1\n"); break;
001C52AC 68 D8 7B 1C 00       push        offset string "1\n" (01C7BD8h
001C52B1 E8 F1 C0 FF FF       call        _printf (01C13A7h
001C52B6 83 C4 04             add         esp,4 
001C52B9 EB 2B                jmp         __$EncStackInitStart+13Ah (01C52E6h//break
    case 2:
        printf("2\n"); break;
001C52BB 68 DC 7B 1C 00       push        offset string "2\n" (01C7BDCh
001C52C0 E8 E2 C0 FF FF       call        _printf (01C13A7h
001C52C5 83 C4 04             add         esp,4 
001C52C8 EB 1C                jmp         __$EncStackInitStart+13Ah (01C52E6h
    case 3:
        printf("3\n"); break;
001C52CA 68 E0 7B 1C 00       push        offset string "3\n" (01C7BE0h
001C52CF E8 D3 C0 FF FF       call        _printf (01C13A7h
001C52D4 83 C4 04             add         esp,4 
001C52D7 EB 0D                jmp         __$EncStackInitStart+13Ah (01C52E6h
    default:
        printf("default \n");
001C52D9 68 E4 7B 1C 00       push        offset string "default \n" (01C7BE4h
001C52DE E8 C4 C0 FF FF       call        _printf (01C13A7h
001C52E3 83 C4 04             add         esp,4 
    }
    return 0;
001C52E6 33 C0                xor         eax,eax
数组,结构体,结构体数组访问
  • struct access
    • 结构体首地址+成员相对结构体首地址偏移量(0)
1
2
3
4
g_stu.age = 18;
009E5202 C7 05 7C A1 9E 00 12 00 00 00 mov         dword ptr [g_stu (09EA17Ch)],12h  //结构体的首地址+0,age相对结构体首地址的偏移为0
    g_stu.sex = 'F';
009E520C C6 05 80 A1 9E 00 46 mov         byte ptr ds:[9EA180h],46h
  • array access
    • 数组首地址+元素偏移
1
2
3
g_a[i] = 1;
00A71A6B 8B 45 F4             mov         eax,dword ptr [i] 
00A71A6E C7 04 85 40 A1 A7 00 01 00 00 00 mov         dword ptr g_a (0A7A140h)[eax*4],1 //g_a[i] = 1;
  • 结体数组的访问
    • 结构体数组首地址+i*sizof(sruct)+成员相对其结构体首地址偏移量
1
2
3
4
5
6
        g_stua[i].age = 18;
00241A93 8B 45 F4             mov         eax,dword ptr [i] 
00241A96 C7 04 C5 D0 A2 24 00 12 00 00 00 mov         dword ptr g_stua (024A2D0h)[eax*8],12h  //g_stua[i].age = 18;
        g_stua[i].sex = 'F';
00241AA1 8B 45 F4             mov         eax,dword ptr [i] 
00241AA4 C6 04 C5 D4 A2 24 00 46 mov         byte ptr [eax*8+24A2D4h],46h

C++反汇编特点

  • This指针(对象的首地址,在函数参数固定的情况下,This指针是放在eax寄存器传给被调用者的)
  • 虚函数表指针(类中如果有虚函数,该对象头部都有虚函数表指针(存放在对象指向内存的前4Byte中)指向一个虚函数表,所以在调用的时候可以通过虚函数表指针从虚函数表中找到正确的虚函数来执行)

    汇编指令格式详解

  • 为什么mov有不同的指令形式?能不能自己诫算这个机器码?
  • 参考学习:
  • 可以用__asm{想要验证的汇编指令}在程序调试进行验证
    汇编指令格式
  1. 通用寄存器
  • X86:8个通用寄存器,X64:16个通用寄存器
    -eax,ebx,ecx,edx,esi,edi,esp,ebp(e表示extend)
    • rax,rbx,rcx,rdx,rsi,rdi,rsp,rbp(re表示re extend)
      r8,r9,r10,r11,r12,r13,r14,r15(新增寄存器)
  • 对通用寄存器编号,寄存器不同,指令不同
    • x86用3bit表示,x64需要用4bit表示
  1. 操作码
  • 每条指令都有一个或多个编码
  1. 7种寻址方式:
  • 寻址方式不同指令不同,比如mov指令,不同寻址方式,mov的指令也不一样
  • 立即数寻址: mov al,44h
  • 寄存器寻址: mov ds,ax
  • 直接寻址方式:mov al,DS: [2000h]
  • 寄存器间接寻址方式:mov ax,[bx]
  • 寄存器相对寻址方式:mov ax,[ebp + 1000]
  • 基址变址寻址方式:mov ax,[bx + si]
  • 相对基址变址寻址方式:mov ax,[bx +si*n + 1000];n=1,2,4,8
    X86汇编指令格式
  1. 字段(变长,1到17个字节(4+3+1+1+4+4)):
  • refix : 可以有,可以没有,最多可以有四个。比如rep movs byte ptr[edi],byte ptr[esi]
  • Opcode:1,2,3Byte,唯一必须的字段,其它都是optional的
  • ModR/M:一个字节
    • Mod:占两位,00寄存器间接寻址,01寄存器相对寻址偏移8位。10寄存器相对寻址偏移32位,11寄存器直接寻址。
    • Reg/Opcode:Reg/Opcode有时候是opcode的补充操作码。有时候是第个操作数寄存器(由指令定义决定)
    • R/M:RM占3位,指定寄存器编号
    • 其中中Mod和RM定义了32种寻址方式
  • SIB:ModRM的补充寻址方式基址变址寻址
    • eg:相对基址变址寻址[ebp+ebx*2+10h],
    • Scale倍率00表示*1,01表示*2,10表示*4,11表示*8
    • index:倍率寄存器编号
    • Bese基址寄存器编号
  • Dis:偏移,1个,2个或者4个字节;
  • IMM: 立即数,1,2,4个字节
  1. eg:手工根据汇编指令计算出机器码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//计算流程:Opcode 'mod  reg/opcode r/m' 'ss index base' disp imm
 
// mov r/m32 imm32 对应的机器码是C7 /0(reg/opcode)
// 寻址方式是寄存器相对寻址偏移8位 对应Mod:01
// reg/opcode是000
// ebp编号是101
// C7 45
// `SIB`字段没有
// disp是-38h,即C8
// imm32是1,低位优先,即01 00 00 00
// 合起来就是C7 45 C8 01 00 00 00
mov dword ptr [ebp-38h],1  //C7 45 C8 01 00 00 00
mov [ebp-38h],eax   //89 45 c8
mov
dword ptr [ebp+ebx-38b],0 //C7 84 1D C8 FF FF FF 00 00 00 00
mov ebx,eax //8B D8
 
ADD DWORD PTR DS[ESI+ECX*4],EAX //SiB:8Eh
X64汇编指令格式

@todo x64暂时先放一放

x64汇编

  • x64汇编不能像x86汇编那样嵌入到C语言代码中,而是需要把汇编独立写在一个.asm文件中。
  • @todo 待完善,x64很少遇到,暂时先放一放

    什么是逆向(静态)分析?

  • 正向开发:
    • C源程序(.c/.cpp)->汇编程序(.S) ->二进制(.exe/.dll/.sys或者.elf/.so)->加壳保护
  • 逆向开发:
    • C源程序(.c/.cpp)<-汇编程序(.S)<-二进制(.exe/.dll/.sys或者.elf/.so)<-加壳保护
  • 逆向分析的作用
    • 病毒木马样本分析,比如勒索软件
    • PC端和移动端竞品分析或破解
    • 和开发相辅相成,其的并发是基础
    • 工具:IDA PRO,APKTOOL,JEB, OllyDbg等
  • 防止逆向分析手段:
    • 1.加壳: VMProtect, ASProtect, ArmadiIIo, Theailda, UPX等人移动端壳,其中VMProteot(简称VMP)是目前最强的虚拟机保护壳,据说至今没有人能够公开号称成功还原过,并且支持内核驱动加密保护
    • 2.加花指令
    • 3.指令混淆: OLLVM(Obfuscator-Low Level Virtual Machine, 针对LLVM的代码混淆工具)
  • 逆向带有很强的技术性。逆向存在各行各业,尤其是制药,军工和高科技行业:中国是逆向大国。中国曾经逆向美制响尾蛇导弹失败。为什么失败呢?
    • 1958年9月24日温州以东空战台军发射了5枚美国AIM-9B响尾蛇导弹,其中一枚坠地而未爆炸。
    • 霹雳1:无线电制导
    • 霹雳2:红外线制导
    • 霹雳10:第四代空空导弹
    • 俄罗斯发动机

逆向的前置基础

  • C(C++)语言开发基础
  • X86 or ARM汇编基础
  • 程序调试基础(断点,跟踪等)
  • PE、ELF等可执行文件格式
  • 脱壳基础
    • 加壳原理
    • 识别各种壳
  • 逆向学习重要方法
    • 自己写一段代码,编译之后自己再逆向(自己既知道问题,又知道答案)

      IDA Pro

  • 全称: Interactive Disassembler Rrofessional
  • 一款逆向里必用的“神器",世界顶级的交互式反汇编工具。总部位于比利时列日市的Hex-Rays公司开发。作者是一仪编程天才,名叫llfak Guilfanov(伊尔法克·吉尔法诺夫)。
  • IDA是一个强大的二进制程序逆向分析工具,利用自身强大的反汇编引擎,能够将复杂的二进制程序反汇编成可供阅读和分析的汇编程序并可以在汇编程序的基础上,提供多种编辑加工的功能,汇编程序变得更加友好和可读。支持多个处理器平台(30到50多个)和多个格式的文件(exe,dll,sys, so, elf等),支持Win,Linux, MacOS等。
  • IDA采用递归下降反汇编算法,在区分数据与代码的同时还设法确定这些数据的类型,并尽其所能地注释生成的反汇编代码
  • IDA玉具还提供了强大的插件功能,其中的Hex-Rays插件甚至能够直接将汇编语言逆向成为可读性更强的C代码。
  • IDA图标头像: Ada Lovelace(1815-1852),英国数学家,穿孔机程序创始人:19世纪诗人拜伦的女儿。她建立了循环和了程序概念计算程序拟定“算法”,被视为世界主第一位软件设计师,她对讦算机的预见超前了整整一个世纪。美国国防部曾将种计算机语言命名为Ada语言,以纪念这位“世界上第一拉软件工程师"。IDA用她的头像作为图标是为了纪念这位女程序员
  • IDA是收费软件

    IDA安装目录简介

  • cfg:配置文件目录,包括基本IDA配置文件ida,cfg、GUI配置文件idagui.cfg以及文本模式用户界面配置文件idatui.cfg
  • dbgsrv: 调试端服务器,用于远程调试。
  • idc:包含IDA的内置脚本语言IDC所需的核心文件。
  • ids:包含符号文件:用于描述可被加载到IDA的二进制文件引用的共享库的内容和摘要信息,列出了由某个指定库导出的所有项目,比如描述某个函数所需的参数类型和数量的信息、函数的返回类型(如果有)以及与该函数的调用约定有关的信息。
  • loaders:包含在文件加载过程中用于识别和解析PE或ELF等文件格式的IDA扩展。
  • procs:包含所支持的处理器模块处理器模块为IDA提供机器语言与汇编语言的转换功能,并负责生成最终的汇编语言代码。
  • sig:包含IDA在各种模式匹配操作中利用的现有代码的签名。通过模式匹配,IDA能够将代码序列确定为已知的库代码,节省分析时间。签名由IDA的“快速的库识别和鉴定技术”(FLIRT)生成。比如通过匹配入口点(start,不是main)签名来识别编译器(VC,BORLAND,GCC等)
  • til:包含一些类型库信息,通过这些信息记录各种编译器库的数据结构的布局。

线性反汇编与递归下降反汇编

  • 什么是反汇编?
    • 反汇编:把机器语言转化为汇编语言
    • 反C:把汇编语言转化为C语言
    • 这是一个逆向过程
  • 线性扫描:从代码段第一个字节开始,逐条反汇编每条指令
    • 优点:能够覆盖程序的所有代码段
    • 缺点:如果代码中混有数据可能会得到错误结果。
  • 递归下降:根据一条指令是否被令一条引用来决定是否对它反汇编。
    • 顺序流指令:线性扫描;
    • 条件指令:将真的目标地址放入到延迟地址列表(延迟反汇编),为假分支继续线性反汇编。
    • 无条件跳转指令:直接把目标地址放入延迟地址列表,问题:jmp eax;(寄存器的值只有在程序运行的时候才能知道,IDA进行静态反汇编,拿不到值的)
    • 函数调用:将目标地址放入到延迟地址列表(call eax问题), 返回后线性扫描后续指令(如果返回地址被窜改,会有问题,ret指令:反汇编终止(因为函数在栈上,只能在运行的时候才能确定,静态分析ret的位置无法确定,)),从延迟地址列表里取一个地址继续反汇编。
    • 能够区分代码和数据,无法识别通过代码指针表来进行的间接跳转(switch语句)(启发式方法解决)
  • Windbg,objdump采用线性扫描;IDA PRO使用的递归下降法反汇编

启动与关闭IDA

IDA的启动
  • 32位与64位
    • 32位程序提供汇编转C语言伪代码功能(F5)
    • 64位程序提供64位汇编解析功能,64位程序无法将汇编转C语言伪代码
      NEW
  • 打开动态库、ex静态库、中间文件、apk等等二进制文件
  • NEW->选择需要逆向程序->默认选项(文件加载器,处理器类型)->在分析的过程中可能会提示带符号(只有调试自己的程序才有符号提示,选择加载符号,可读性更佳,如果错过了,还有另一次加载符号的机会File-Load file-PDB file)
  • 生成数据库文件(存放在目标逆向文件的同一路径下)
    • .id0二叉树形式的数据
    • .nam符号
    • .til分析到的一些数据类型
    • 以上数据合起来称为数据库(退出IDA后,默认会打包成一个.idb文件)
      GO
  • 用于调试正在运行的程序,或者暂时没有想好的情况
  • 直接进入IDA,没有任何内容,需要手动Load要分析的文件进来
    Previous
  • 加载上次文件
    • 继续上次的逆向工作IDA的基本功能
      打开加壳的文件
  • 加了upx壳的文件
    • 函数名称和导入表都无法解析出来
    • 解决方法:先脱壳,再用IDA进行分析
      如何获取被删除的.sys文件
  • 有些程序在释放出驱动,加载驱动之后会把驱动文件删除,避免被分析。
  • 思路:
    • HIPS拦截(文件过滤驱动),最方便,最准确
    • 内存拷贝
    • PE资源编辑器(PE Edit/PE Explorer,程序释放出驱动,说明驱动存放在程序中,即PE文件的资源节里面)
      IDA的关闭
  • 不需要保存:勾选DON’ T SAVE the database
  • 需要保存
    常见问题
  • 找不到模块
    • 只影响脚本,不影响ida反汇编功能
    • 可能是没有设置好python的环境路径,或者因为ida的python版本是2.0和安装的python3.0冲突

      IDA视窗与窗口

      汇编视图
  • “汇编视图(IDA View)“是IDAPro静态分析程序的最主要的界面,该界面显示了各个函数的汇编代码。
  • 汇编视图提供了2种形式的显示方法,一种是基于文本的显示,另1种是基于
    图形的显示,二者之间可以通过空格键进行快速的切换。
  • 默认显示图形还是文本可以通过选项设置
  • 在汇编视图里,既可能遇到代码指
    令也可能会遇到数据。在分析的时候,它们之间可以用命令进行相互转化和重命
    • 可以将光标放在对应的数据行,然后通过多次按下D捷键来改变该数据的类型(最常见的是从db/dw/dd之间切换)
    • 也可以按下C键来将对应的数据转化为代码指令;
    • 对于代码指令通过按键U来转化为数据;
    • 而对于函数名,变量名以及寄存器,可以通过N按键来修改名字,以提高代码的可读性。
      函数窗口
  • “函数窗口”列出了所有模块中存在的函数,双击任意函数,就可以定位到该函数的实际位置。
    • 如果加载符号,函数的返回类型,调用约定,函数名称,函数的参数列表,参数类型以注释的形式提供。
    • 如果未加载符号,那么函数名可能以sub_location的形式存在。
  • 如果函数解析不准确,可以修改或者删除
  • R:返回给调用者
  • F:far function
  • L:库函数
  • S:static function
  • B:用ebp+来访问形参,ebp-来访问局部变量
  • T:函数拥有类型信息
  • =:栈帧的指针指向的是栈帧的基地址
    名字窗口
  • “名字窗口"列出了IDA分析出的所有全局的名字。这些名字就是对虚拟内存地址的一个符号描述(比如变量函数名等)。鼠标双击这些名字就会快速跳到对应的汇编窗口对应的位置。
  • 名字前面的字符表示了不同的名字类别:
    • F:普通函数
    • L:库函数
    • I:导入函数的名字,无代码部分(现在是个箭头,I表示标号位置)
  • C:命名代代码。不属于任何函数
  • D:命名数据,比如全局变量
  • A:ASCII字符串,大于4个字符
    字符串窗口
  • “字符串窗口"列出了IDA识别出来的所有字符串该字符串窗口对于分析程序的关键点非常有用。因为过主一般都具有意义作可以猜测引用该字符串位置的代码的作用。尤其是那些进行安全验证的地方可以遇字符串进行快速定位。
  • 双击某个字符串,就可以快速定位到字符串定义和引用的位置。或者也可以通过"ALT+T"快捷键来查找字符串的引用位置。
  • 窗口右键setup配置
    PE区段窗口
  • View-open subviews-segmention或者CTRL+S快捷键
  • "PE区段窗口“列出了分析文件的所有PE区块:
    • HEADER:PE头
    • .text:代码段
    • .idata:导入表所在数据段
    • .rdata:只读数据段
    • .data:数据段
    • INIT:初始化段
    • .reloc:存放基地址重定向表的重定向节,用于当irmageBase改变后,对映像内的使用的地址进行重定位。
  • 用鼠标双击这些区段,就可以直接跳到对应的汇编代码或者数据位置。
    导入/出表窗口
  • 导入表(导入那些库函数,可以从侧面分析程序的功能)
  • 导出表(驱动时DriverEntry,应用程序则是Main)
    其他窗口
  • 输出窗口(ida分析过程中的信息、或者脚本运行的结果)
  • 程序内存布局彩条

    IDA基本操作

    查找
  • 文本查找 ATL+T/CTRL+T
  • 以Byte为单位查找二进制值 ATL+B/CTRL+T
    跳转
  • 双击就可以直接跳转
  • CTRL+S可以跳转到某个节
  • CTRL+E可以跳转到入口点
  • G可以跳转到指定目的地址
  • CTRL+L按照名字跳转
    编辑
  • 编辑修改都是为了提高代码可读性,使得分析的时候更加清晰
    1.重命名,快捷键N
    2.代码与数据转换:C,U,D,A
    3.注释:
    • 普通注释:
    • 可重复注释;如果一个程序位置引用了另外一个包含可重复注释的位置,则该注释会在第一个位置回显。)
      4.修改数组:Edit-Array
      5.修改函数栈指针(stdcall) EDIT->Functions->Change Stack Pointer(ALT+K)
      IDA PATCH 花指令方法
  • nop替换花指令
    • Edit-Patch program-Change byte
    • 或者点右键-Keypatch-Patcher
  • 查看 Patch的历史
    • Patched bytes
  • 应用到文件中
    • Apply patches to input file
      数据、代码交叉引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int read_id;
int write_id;
int ref_id;
 
void callfunc()
{
    printf ("callfunc\n");
}
 
int _trmain(int argc,_TCHAR* argv[])
{
    int *p = &ref_id; //地址引用
    *p = read_id;   //读引用
    write_id = *p;  //写引用
 
    callfunc();
 
    if(read_id==5)
        write_id = 2;
    else
        write_id = 1;
 
    callfunc();
 
    return 0;
}
  • 数据引用
    • View->Open Subview->Cross references或X(列举所有交叉引用当前符号的地方,比如strcpy,容易造成缓存区溢出的漏洞,可以找到所有调用的地方strcpy,更好利用漏洞)
    • o:地址应用
    • r:读引用
    • w:写引用
  • 代码引用
    • View->Open Subviews->Function calls(列举所有应用该函数的地方和该函数调用的函数)
    • p:函数引用
    • j:跳转引用
      反C插件Hex-rays
  • 将光标定位到该函数内部本后直接按下快捷键F5,那么,整函数就会被直接翻译成对应的C代码
  • 64位的IDA没有安装反C插件Hex-rays,所以要看C代码需要使用32位的IDA。
    结构体
    结构体(structures)视图
  • 结构体视图显示IDA决定在一个二进制文件中使用的任何复杂的数据结构布局。
  • 列举出了IDA在分析过程中识别出来的结构体信息,而那些IDA自己未成功识别的结构体,也可以通过IDA提供的方式来添加。
  • 数组的某个元素或者结构体的某个成员在汇编代码中的特征并不明显。所以需要手动把人工识别的结构体放在结构体视图中,只有在该视图里出现的结构体才可以被整合到IDA的汇编视图里,提高代码的可读性(用结构体成员访问去重命名汇编代码中的偏移)。
    添加IDA未成功识别的结构体
  1. 方式1: Insert快捷键添加
    • 在该视图里,通过Insert快捷键添加,在弹出的对话框里指定结构体的名字,或者直接从对话框不面的按钮"Add standard structure"中选择标准库中的结构体。
    • 在对话框中点击OK按钮之后,刚才定义的结构体名字将会在视图中出现。这个时候,将光标移动到结构体定义的最后行,按D键,可以往结构体里插入成员,再按D,可以切换成员的类型。按A键,可以往结构体里插入数组成员
    • 删除结构体中间的成员:U→开始菜单Edit->Shrink struct type
    • 在结构体中间增加一个成员: Edit->Expand struct type
    • 在结构体中删除最后一个成员: 将光标移动到结构体ENDS一行,按U
    • 重命名一个结构体成员:将光称移到该成员,按N键即可重命名。
    • 效率低
  2. 方式2:通过Local Types添加
    • 通过Local Types添加
    • 这种方式可以通过菜单View→OpenSubviews Local Types→INSERT插入种个用C结构体方式定义的结构体。这种方式比第一种方式更容易和方便。通过这种方式新插入的结构体不会直接插入structures视图而是放在了标准库中,因此必须通过第一种方式中的insert快捷键,然后选择从对话框下面的按钮Add standard structure中选择标准库中的结构体来加入结构体视图中。
    • 也可以通过在Local Type视图里双击该结构体,将它加入到Structures视图中。
  3. 方式3:通过头文件添加
    • lDA Pro支持直接从头文件中添加结构体。添加方法是从菜单FILE->LOAD FILE->Parse C headler file就可以将头文件中的结构体导入。
    • 与第2种方法类似,通过头文件添加的结构体也被导入到称准库中的后面,也需要通过第种方式中的insert快捷键,然后选择从对话框下面的按钮Add standard structure 中选择标准库中的刚插入的结构体加入到结构体视图中。
      在汇编代码中运用结构体
  • 可以往结构体视图加入IDA未识别的结构体。加完自己定义的结构体之后,就可以在汇编代码中运用结构体。
  • 对于普通代码,将光标移动到汇编代码中存器+偏移的位置,然后右键,在弹出的菜单中,选择Struct offset就会将结构体视图中与这个偏移匹配的结构体成员引入。这样汇编代码就由[eax+4h]变为了[eax+mystruct.member]
  • 而对于栈的变量(结构体汇编代码特征不明显,每个成员都会被IDA识别成一个独立的变量),如里想运用相应的结构体类型,方法如下:
    • 首先在函数内部栈空间上鼠标双击IDA会切换到函数的栈空间视图:
    • 这个时候,将光标移到对应的栈变量上,然后通过EditStruct Var(ALT+Q),选择要转换的结构体类型。
  • 还可以用N修改汇编代码中的结构体名,进一步增强可读性。

    IDA脚本编程

  • IDA集成了2个脚本引擎(一个是IDC,一个是Python),让用户从编程角度对IDA的操作进行全面控制,从而自动执行常规任务。以编程方式访问和查询IDA数据库,分析整理数据,可用于脱壳,去模糊,自动化分析方面。免去了手工操作的繁琐、低效和不便。
  • lDC,类似于C语言语法与编程凤格
    Hello world
  • 三种方式执行IDC或者Python代码
  1. 窗口 File->Script command
1
2
3
4
5
//IDC
Message("hello world");
 
//python 3.x
print("hello world")
  1. 文件 File->Script file
1
2
3
4
5
6
/// hello.idc
#include <idc.idc>
static main()
{
    Message ("hello world");
}
1
2
3
4
5
6
#hello.py
import idc
def print_hello();
    print "hellc world"
 
print_hello()
  1. 命令行
  • 单语句可以使用命令行,少量语句可以使用窗口,复杂的语句可以使用文件
    基础语法 @todo
    函数遍历@todo
    调用者遍历@todo
    交叉引用查找@todo

    发行版代码保护与反逆向方法

    Release编译优化
  • 一般拿到的程序都是release版本的
  • 与调试版的程序不同,发行版的程序做了很多的编译优化,其对应的汇编代码将会更加的难以理解,可阅读性也会更差一些且没有对应的PDB符号文件
  • 如下图所示在没有符号文件的时候、IDA识别出来的函数名都是自动命名,可读性就变得很差。
  • 甚至是通过花指令或者OLLVM或者加壳混淆对抗IDA等工具。这样分析起来就会更加困难一些。
反调试和反反调试
反调试
  • DebugProt
    • 对于未导出的函数,无法通过符号来找到DebugProt,只能通过硬编码来找到DebugProt
  • 在内核层
    • KdDisableDebugger 在内核里调用KdDisableDebugger来禁用内核调试
    • KdEnable Debugger 启用内核调试
  • 在应用层
    • 开两个线程来分别调用IsDebugerPresentCheckRemoteDebuggerPresent来检测自己是否被正在被调试,如果发现被调试,自己就退出,其他人就无法继续调试我了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// 没有加入反调试代码的时候MFC程序是可以被windbg调试的,加入反调试代码之后,windbg调试MFC程序的是,MFC就会一闪而过退出
/// 反调试线程函数,放在MFC函数的执行入口
UINT AntiDebug(PVOIDparam)
{
    while(g_bWillExit == FALSE)
    {
        HANDLE hRrocess = GetCurrentProcess();
        BOOL bDebuggerPresent=FALSE;
        CheckRemoteDebuggerPresent(hProces,&bDebuggerPresent);
        if(IsDebuggerPresent()||bDebuggerPresent)
        {
            ::ExitProce(0); //把当前进程结束掉
        }
        Sleep(5000); //每隔5s检测程序是否被调试
    }
    return 0;
}
 
AfxBeginThread((AFX_THREADPROC)AntiDebug, NULL); //启动反调试线程
  • Hook-Anti-debug
    • hook系统中一些与调试相关的函数,以可以防止各种调试器调试。
    • NTOpenThread() hook这个函数,可以防止调试器在程序内部创建线程
    • NTOpenProcess() hook这个函数,可以防止OD(OllyDbg)等调试工具在进程列表中看到我们
    • KiAttachProcess() hook这个函数,可以防止被附加上
    • NtReadVirtualMemory() hook这个函数,可以防止被读内存
    • NtWriteVirtualMemory() hook这个函数,可以防止被内存被写
  • hook这两个函数用来防止双机调试
    1
    2
    - `KdReceivePacket()` KDCOM.dll中Com串口接收数据函数
    - `KdSendPack()` KDCOM.dll中Com串口发送数据函数
    反反调试
  • ba w4 debugport 对DebugProt内存地址下断点
    这样一旦有程序代码在修改DebugPort,就会被断下,从而找到对应清零DebugPort的反调试代码,然后对这部分代码进行patch(用机器码0x90(nop)或 者0xC3(ret)取代)从而让它失去作用,当然有的程序会对代码进行校验,一旦发现代码被篡改,就会采取保护措施,比如抛出异常或者退出程序。
  • 针对调用系统函数如KaDisabeDebugger()来检测调试器存在,从而禁止被调试的方法,可以在对应的这些函数的地址下断点,然后对相关的代码进行patch,然后使该函数判断失效。
    • 比如bp KdDisableDebugger, eb xxx
  • 针对通过HOOK系统函数来防止进程被调试的方法,可以直接将
    这些系统函数的钩子直接恢复,可以通过检测和恢复这些函数钩子。
    花指令
    花指令是程序中的无用指令或者垃圾指令,故意干扰各种反汇编静态分析工具,但是程序不受任何影响,缺少了它也能正常运行。加花指令后,IDA Pro等分析工具对程序静态反汇编时,往往会出现错误或者遭到破环,加大逆向静态分析的难度,从而隐藏自身的程序结构和算法从而较好的保护自己。
  • 遇到花指令,需要手动udefine掉,很多的话需要写脚本剔除花指令、用插件模拟执行。
  1. 花指令1 inc+dec
1
2
3
4
5
6
7
8
//花指令1
//代码没有任何作用,但会混淆调试者
push edx
pop edx
inc ecx
dec ecx
add esp,1
sub esp,1
  1. 花指令2 jmp
    1
    2
    3
    4
    //花指令2
    jmp Labe1
    db opcode     //在jmp后面加上一个字节的机器码,并不完整(完整的汇编指令是一个机器码+操作数),但不影响程序的执行(jmp会跳过这条残缺的汇编指令)。在IDE工具反汇编的时候,看到机器码(以为紧接着的就是操作数,接着后面的反汇编都错位了)
    Label1:
    但IDE采用的反汇编算法是递归如果没有指令跳转到这个位置,就拒绝对这条指令进行反汇编。
  2. 花指令3 jz+jnz
1
2
3
4
5
//花指令3
jz Label      //花指令对代码依旧没有影响,为0的时候会跳过花指令。
jnz Label     //0才跳转到 Label,为0则执行下面的花指令,IDE此时就认为这条花指令需要反汇编。
db opcode //_emit 0e8h;call机器码
Label:
  • emit伪指令,在当前位置直接插入数据(指令),一般用来插入汇编里面没有的特殊指令,和db,dw效果相同。
  • 目的:编译器不认识的指令,拆成机器码来写。
  1. 花指令4 call+ret
  • call一个地址,在call下面随便写入花指令并记住花指令字节长度
  • 在call里面,也就是函数里面姚首先pop出压入的地址,.然后把这个地址减去花指令占用的字节数(绕过花指令),再重新push进栈,然后就ret(跳到了花指苓的下一条指令)
1
2
3
4
5
pop ebx //压栈地址弹出
inc ebx //压栈地址+1,绕过花指令
push ebx //返回地址完成加绕过了花指令
mov eax,0x1;设置返回值
ret

1
2
- call 0A04B0D7迷惑了反汇编器,认为call之后会返回继续执行call的下一条指令,所以从0A04B0D6位置开始反汇编,实际上0A04B0D6位置放了一个Byte的垃圾值,call只是直接jmp 0A04B0D70A04B0D6不会被执行
- 解决方法:在0A04B0D6执行`U`命令,然后在0A04B0D7执行`C`命令把这条花指令patch掉

1
- jmp到自己的指令也有问题,必须去掉OA04BODB的定义(`U`快捷键),重新定义OA04BODC处的指令(`C`快捷键)。

1
- 最后执行的是jmp eax (是为了对抗静态反汇编,运行时才计算)
  1. 花指令5 相对跳转指令构造绝对跳转
1
2
xor eax, eax
jz short near ptr loc_40100A
  • 构成了一个绝对跳转(在40100A前的都不会执行)。
  • 但反汇编编译器没有识别,继续对jz后面的指令比如0401009处的进行反汇编,出错。
动态计算目标地址
  • 递归下降反汇编的缺陷是无法获得动态计算目标地址
    • 除非使用脚本或者模拟器来模拟执行
  • 动态计算目标地址可以用来干扰IDA导致无法得知目标地址,无法得到这部分反汇编代码
  • 使用一个调用语句将个返回地址压入栈中,然后这个返回地址直接由栈进入寄存器,再给寄存器加上一个常量值,得到最后的目标地址;
  • 或者将寄存器中计算出来的返回地址移入栈顶部,然后返回指令(ret)将控制权转交给计算得出的位置。
  • 这些情况下,分析人员必须动手运行代码,才能确定程序的具体控制路径。
    OLLVM混淆
  1. LLVM
    • LLVM命名最早源自于底层虚拟机(Low Level VirtualMachine)的缩写,目前LLVM就是该项目的全称。
    • LLVM核心库提供了与编译器相关的支持可以作为多种语言编译器的后台来使用。能够进行程序语言的编译期优化、链接优化、在线编译优化、代码生成。
    • LLVM的项目是一个模块化和可重复使用的编译器和工具技术的集合。
    • LLVM是伊利诺伊大学的一个研究项自,提供一个现代化的,基于SSA的编译策略能够同时支持静态动态的任意编程语言的编译目标。自那时以来,已经成长为LLVM的主干项目,由不同的子项目组成,其中许多正在生产中使用的各种商业和开源的项目,以及被广泛用于学术研究。
  2. OLLVM (Obfuscator-LLVM)是瑞士西北应用科技大学安全实验室于2010年6月份发起的个项自,该项自旨在提供一套开源的针对LLVM的代码混淆工具,以增加对逆向工程的难度。OLLVM适用LLVM支持的所有语言(C,C++,Objective-C , Ada和 Fortran)和目标平台(x86, x86-64,PowerPC, PowerPC64,ARM,Thumb,SPARC,Alpha,CellsPU,MIPS,MSP430,SystemZ和XCore)混淆的方法有:
  • 控制流平展模式
    • control Flow Flattening控制流平展模式可以完全改变程序原本的控制流图经FLA(CCF)模式混淆后程序的执行流程已经被打乱,出现许多代码分支
    • 简而言之,增加很多条件分支
  • 指令替换模式
    • Instructions Substitution指令替换摸式主要是将正常的运算操作+,-,*,/,&,|等替换成功能相等但表达更复杂的形式
    • 简而言之,把简单的运算变成复杂的运算
  • 控制流伪造模式
    • Bogus Control FIow控制流伪造模式也是对程序的控制流做操作与CFF不同的是,BCF模式会在原代码的块前后随机插入不确定的代码块,然后新代码块再通过条件判断跳转原代码块中。甚至原代码块可能会被克隆并插入随机的垃圾指令。同一份代码多次BCF模式的混淆时,得到的是不同混淆效果。原本简单的if else分支代码变得异常复杂,加大了逆向的难度。
    • 简而言之:不仅会打乱流程,还会添加花指令
  • 利用OLLVM混淆Android Native代码
    编译OLLVM
  1. 环境
    • 虚拟机 ubuntu 16.04_64位,4核,16G内存,编译耗时30min左右
  2. 安装gitl和cmake:
    • sudo apt-get install git
    • sudo apt-get install cmake
  3. 下载和编译ollvm
  • 下载
    • cd ~/
    • mkdir workspace
    • cd workspace
    • git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator.git(不稳定,下载不下来就换网或者用VPN)
  • 编译
    • mkdir build
    • cd build
    • cmake -DCMAKE_BUILD_TYPE=Release ../obfuscator/,如果出错,换下面命令
    • cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF ../obfuscator/
  • make -j4(并行编译任务,一般为CPU核数的双倍)
  1. 添加环境变量,方便使用命令行在任何位置都能直接调用
  • ls bin/clang
  • export PATH=~/workspace/build/bin/:$PATH
  • echo $PATH
    使用OLLVM
  1. 没有混淆前的代码
  • mkdir hello
  • cd hello
  • vim hello.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main()
{
    int a=5;
    if(a<10)
    {
        a++;
    }
    else
    {
    }
    printf( "hello ollvm\n" );
    return 0;
}
  • gcc hello.c -o hello
  • ./hello
  • gcc hello.c -S
  • vim hello.s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/// 没有用OLLVM混淆之前代码反汇编的逻辑是很清晰的
        .file   "hello.c"
        .section        .rodata
.LC0:
        .string "hello ollvm"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    $5, -4(%rbp) //a=5
        cmpl    $9, -4(%rbp) //a<=9
        jg      .L2  //>9,则跳转.L2
        addl    $1, -4(%rbp) //a++
.L2:
        movl    $.LC0, %edi  //printf参数入栈
        call    puts
        movl    $0, %eax //retrun 0
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
        .section        .note.GNU-stack,"",@progbits
  1. 混淆之后的代码
  • 指令替换模式
    • clang hello.c -mllvm -sub -o hellosub(编译成可执行文件)
    • clang hello.c -emit-llvm -mllvm -sub -S -o hellosub.ll(编译成汇编文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
; ModuleID = 'hello.c'
source_filename = "hello.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
 
@.str = private unnamed_addr constant [13 x i8] c"hello ollvm\0A\00", align 1
 
; Function Attrs: noinline nounwind uwtable
define i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store i32 5, i32* %2, align 4
  %3 = load i32, i32* %2, align 4
  %4 = icmp slt i32 %3, 10
  br i1 %4, label %5, label %11
 
; <label>:5:                                      ; preds = %0
  %6 = load i32, i32* %2, align 4
  %7 = add i32 %6, 3116065 //+-,将简单指令变成复杂的指令
  %8 = add i32 %7, 1
  %9 = sub i32 %8, 3116065
  %10 = add nsw i32 %6, 1
  store i32 %9, i32* %2, align 4
  br label %12
 
; <label>:11:                                     ; preds = %0
  br label %12
 
; <label>:12:                                     ; preds = %11, %5
  %13 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str, i32 0, i32 0))
  ret i32 0
}
 
declare i32 @printf(i8*, ...) #1
 
attributes #0 = { noinline nounwind uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
 
!llvm.ident = !{!0}
 
!0 = !{!"Obfuscator-LLVM clang version 4.0.1  (based on Obfuscator-LLVM 4.0.1)"}
  • 控制流平展模式
    • clang hello.c -mllvm -fla -o hellofla(编译成可执行文件)
    • clang hello.c -emit-llvm -mllvm -fla -S -o hellofla.ll(编译成可执行文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
; ModuleID = 'hello.c'
source_filename = "hello.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
 
@.str = private unnamed_addr constant [13 x i8] c"hello ollvm\0A\00", align 1
 
; Function Attrs: noinline nounwind uwtable
define i32 @main() #0 {
  %1 = alloca i32
  %2 = alloca i32, align 4
  %3 = alloca i32, align 4
  store i32 0, i32* %2, align 4
  store i32 5, i32* %3, align 4
  %4 = load i32, i32* %3, align 4
  store i32 %4, i32* %1
  %5 = alloca i32
  store i32 1783974709, i32* %5
  br label %6
 
; <label>:6:                                      ; preds = %0, %19
  %7 = load i32, i32* %5
  switch i32 %7, label %8 [
    i32 1783974709, label %9
    i32 1316248837, label %13
    i32 1830851195, label %16
    i32 -89102849, label %17
  ]
 
; <label>:8:                                      ; preds = %6
  br label %19
 
; <label>:9:                                      ; preds = %6
  %10 = load volatile i32, i32* %1
  %11 = icmp slt i32 %10, 10
  %12 = select i1 %11, i32 1316248837, i32 1830851195
  store i32 %12, i32* %5
  br label %19
 
; <label>:13:                                     ; preds = %6
  %14 = load i32, i32* %3, align 4
  %15 = add nsw i32 %14, 1
  store i32 %15, i32* %3, align 4
  store i32 -89102849, i32* %5
  br label %19
 
; <label>:16:                                     ; preds = %6
  store i32 -89102849, i32* %5
  br label %19
 
; <label>:17:                                     ; preds = %6
  %18 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str, i32 0, i32 0))
  ret i32 0
 
; <label>:19:                                     ; preds = %16, %13, %9, %8
  br label %6
}
 
declare i32 @printf(i8*, ...) #1
 
attributes #0 = { noinline nounwind uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
 
!llvm.ident = !{!0}
 
!0 = !{!"Obfuscator-LLVM clang version 4.0.1  (based on Obfuscator-LLVM 4.0.1)"}
  • 控制流伪造模式
    • clang hello.c -mllvm -bcf -o hellobcf(编译成可执行程序)
    • clang hello.c -emit-llvm -mllvm -bcf -S -o hellobcf.ll(编译成可执行程序)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
; ModuleID = 'hello.c'
source_filename = "hello.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
 
@.str = private unnamed_addr constant [13 x i8] c"hello ollvm\0A\00", align 1
@x = common global i32 0
@y = common global i32 0
 
; Function Attrs: noinline nounwind uwtable
define i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store i32 5, i32* %2, align 4
  %3 = load i32, i32* %2, align 4
  %4 = icmp slt i32 %3, 10
  br i1 %4, label %5, label %8
 
; <label>:5:                                      ; preds = %0
  %6 = load i32, i32* %2, align 4
  %7 = add nsw i32 %6, 1
  store i32 %7, i32* %2, align 4
  br label %27
 
; <label>:8:                                      ; preds = %0
  %9 = load i32, i32* @x
  %10 = load i32, i32* @y
  %11 = sub i32 %9, 1
  %12 = mul i32 %9, %11
  %13 = urem i32 %12, 2
  %14 = icmp eq i32 %13, 0
  %15 = icmp slt i32 %10, 10
  %16 = or i1 %14, %15
  br i1 %16, label %17, label %29
 
; <label>:17:                                     ; preds = %8, %29
  %18 = load i32, i32* @x
  %19 = load i32, i32* @y
  %20 = sub i32 %18, 1
  %21 = mul i32 %18, %20
  %22 = urem i32 %21, 2
  %23 = icmp eq i32 %22, 0
  %24 = icmp slt i32 %19, 10
  %25 = or i1 %23, %24
  br i1 %25, label %26, label %29
 
; <label>:26:                                     ; preds = %17
  br label %27
 
; <label>:27:                                     ; preds = %26, %5
  %28 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str, i32 0, i32 0))
  ret i32 0
 
; <label>:29:                                     ; preds = %17, %8
  br label %17
}
 
declare i32 @printf(i8*, ...) #1
 
attributes #0 = { noinline nounwind uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
 
!llvm.ident = !{!0}
 
!0 = !{!"Obfuscator-LLVM clang version 4.0.1  (based on Obfuscator-LLVM 4.0.1)"}
  • OLLVM三种混淆模式同时作用在hello.c文件下
    • clang hello.c -mllvm -sub -mllvm -fla -mllvm -bcf -o helloall(编译成可执行文件)
    • clang hello.c -emit-llvm -mllvm -sub -mllvm -fla -mllvm -bcf -S -o helloall(编译成汇编文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
; ModuleID = 'hello.c'
source_filename = "hello.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
 
@.str = private unnamed_addr constant [13 x i8] c"hello ollvm\0A\00", align 1
@x = common global i32 0
@y = common global i32 0
 
; Function Attrs: noinline nounwind uwtable
define i32 @main() #0 {
  %1 = alloca i1
  %2 = alloca i32*
  %3 = alloca i1
  %4 = alloca i1
  %5 = load i32, i32* @x
  %6 = load i32, i32* @y
  %7 = sub i32 0, 1
  %8 = add i32 %5, %7
  %9 = sub i32 %5, 1
  %10 = mul i32 %5, %8
  %11 = urem i32 %10, 2
  %12 = icmp eq i32 %11, 0
  store i1 %12, i1* %4
  %13 = icmp slt i32 %6, 10
  store i1 %13, i1* %3
  %14 = alloca i32
  store i32 -1874118603, i32* %14
  br label %15
 
; <label>:15:                                     ; preds = %0, %156
  %16 = load i32, i32* %14
  switch i32 %16, label %17 [
    i32 -1874118603, label %18
    i32 -592237300, label %26
    i32 -775182973, label %60
    i32 -1921775119, label %63
    i32 1540843771, label %72
    i32 -1196761768, label %87
    i32 -2140678259, label %102
    i32 464701823, label %103
    i32 1734380246, label %131
    i32 -1548223981, label %147
    i32 1197949853, label %148
    i32 396207527, label %153
    i32 -58706652, label %154
  ]
 
; <label>:17:                                     ; preds = %15
  br label %156
 
; <label>:18:                                     ; preds = %15
  %19 = load volatile i1, i1* %4
  %20 = load volatile i1, i1* %3
  %21 = and i1 %19, %20
  • 分析汇编也可以把EFL x64文件加载到IDA中分析,可以看到混淆后的流程变得非常复杂了
  • 未混淆的
  • OLLVM指令替换模式把简单的运算变成复杂的运算
  • OLLVM控制流平展模式增加很多条件分支

  • OLLVM控制流伪造模式不仅会打乱流程,还会添加花指令

  • OLLVM三种混淆模式同时作用
加壳
  • 是指在一个程序的外面再包裹上另外一段代码,保护里面的代码不被非法修改或反编译的程序。它们一般都是先于程序运行,拿到控制权,然后完成它们保护软件的任务。
  • 壳的加载过程:
    • 保存现场(pushad/popad,pushfd/popfd)
    • 获取壳自己需要的API地址(LoadLibrary+ GetProcAddress)
    • 解密/解压原程序各个区块
    • IAT的初始化
    • 重定位
    • Hook-API(为了让程序解密之后,继续能和壳进行通信,程序执行这些API的时候重新回到壳里面)
    • 跳到OEP(原始入口点)
      壳的分类
  1. 压缩壳:
  • UPX
    • UPX是一个以命令行方式操作的可执行文件经典免费压缩程序,压缩算法自己实现,速度极快。

      (开源)主页:http://upx.sourceforge.net

  • ASPack
    • ASPack是Win32可执行文件庄缩软件,可压缩Windows 32位可执行文件(.exe)以及库文件(.dll、.ocx),文件压缩比率40%~70%

      主页:http://www.aspack.com

  1. 加密壳:
  • a. ASProtect

    • ASProtect是一款非常强大的Windows 32位保护工具,开发者是俄国人Aexey Solodovnikov,拥有压缩、加密、反踪代码、反-反汇编代、CRC校验和花指令等保护措施。
    • 使用Blowfish、Twofish、TEA等强劲的加密算法,还用RSA1024作为注册整钥生成器。它还通过API钩子(API hooks,包括Import hooks ( GPA hook)和Eport hooks)与加壳的程序进行通信。其至用到了多态变形引擎(Polymorphic Engine)反Apihook代码(Anti-Apihook Code)和BPE32的多态变形引擎(RPF32的Polvmornhic Engine).
    • ASProtect为软件开发人员提供SDK(在自己代码内部打桩),实现加密程序内外结合。

      主页:http://www.aspack.com/

  • b. Armadillo

    • Armadillo(穿山甲),是一款应用面较广的壳,可以运用各种手段来保护你的软件,同时也可以为软件加上种种限制,包括时间、次数,启动画面等等。很多商用软件采用其加壳。Armadllo中比较强太的保护选项是Nanomites保护(即cc保护),用的好能提高强度,其他选项没什么强度。

      主页:http://www.siliconrealms.com/

  • c. Themida
    • Themida是oreans(西班牙著名的软性系统保护公司)的一款商业壳。Themida 1.1以前版本带驱动,稳定性有些影响。
    • Themida最大特点就是虚拟机保护技术,在程序中用SDK将关键的代码让Themmida用虚拟机保护起来。
    • Themida的缺点是生成的软件有些大。
    • Winkcense壳和Themida是同一公司的一个系列产品,主要多了一个协议,可以设定使用时间,运行次数等功能,两者核心保护是一样的

      主页:www.oreans.com

  • d. VMProtect
    • VMProtect是一款纯虚拟机保护软件,是当前最强的虚拟机保护软件,经VMProtect处理过的代码,还原难度极大。
    • 流行的做法,先用VMProtect将核心代码处理一下(VMProtect的稳定性不是特别高,如果对整个程序进行加壳会影响程序的执行效率,折中方案:用SDK只对关键的代码处理),再选用—款兼容性好的壳保护。比如继续用Asprotect, Themida等加壳软件进一步保护。
    • VMP与传统的壳(压缩,加密)相比,它会修改目标,让目标的部分指令(形成虚拟机下的字节码)在它创建的虚拟机环境下运行,虚拟环境中无操作数比较指令条件跳转无条件跳转指令
    • vmp的虚拟机其实是一个字节码解释器,循环的读取指令并执行
    • 虚假跳转和垃圾指令, vmp会使用大量的虚拟跳转和垃圾指令将原有简单的代码变得复杂,类似OLLVM
    • vmp中只有一个逻辑运算指令nor,它可以模拟not and or xor 四个逻辑运算指令,进一步加大逆向分析的难度。

      主页:www.VMProtect.ru

      UPX加壳
  • 加壳过程:

    下载工具包:http://upx.sourceforge.net

    • cmd以管理员身份运行,upx.exe Xxx.exe加upx壳
  • 经过UPX压缩的win32/pe文件,包含三个区段:UPX0,UPX1,.rsrcUPX0,UPX1,UPX2(原文件无资源时)。

    • UPX0:在文件中没有内容它的Virtual size加上UPX1的构成了原文件全部区段需要的内存空间,相当于把原来区段合并了。
    • UPX1:起始位置为需解压缩的源数据,目标地址为UPX0基址。紧接着源数据块是UPX stub,即壳代码。典型的pushad/popad结构,常用ESP定律找到OEP来脱UPX。
    • .rsrc/UPX2:原文件有资源时,含原资源段的完整头部和极少部分资源数据(类型为ICON、 GROUP ICON、VERSION和MANIFEST),以保证explorer.exe能正常显示win32/pe文件图标、版本信息。还有就是UPX自己的Imports内容,导出表的库名和函数名(如果有)。
  • 用IDA打开加了UPX壳的.exe可以发现:

  • 很多数据IDA无法进行反汇编,看到的都是压缩的数据
  • 导出表的函数不属于程序本身,而是壳要用到的函数
  • 没有.text和.data节,取而代之是UPX0,UPX1
    VMP加壳
  • 方法1:加壳时,须告诉VMProtect要加密的代码地址(可以用调试器,如OllyDbg跟踪)然后将该地址添加到VMProtect 。
  • 方法2:由于VMP支持SDK,可以编程时插入一个标记,然后在加密时,VMProtect会认出这些标记,并在有标记的地方进行保护。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#define VMPBEGIN \
__asm_emit 0xEB \
__asm_emit 0x10 \
__asm_emit 0x56 \
__asm_emit 0x4D \
__asm_emit 0x50 \
__asm_emit 0x72 \
__asm_emit 0x6F \
__asm_emit 0x741 \
__asm_emit 0x65 \
__asm_emit 0x63 \
__asm_emit 0x74 \
__asm_emit 0x20 \
__asm_emit 0x62 \
__asm_emit 0x65 \
__asm_emit 0x67 \
__asm_emit 0x69 \
__asm_emit 0x6E \
__asm_emit 0x00
 
#define VMPEND \
__asm__emit 0xEB \
__asm_emit 0x0E \
__asm_emit 0x56 \
__asm_emit 0x4D \
__asm_emit 0x50 \
__asm_emit 0x72 \
__asm_emit 0x6F \
__asm_emit 0x74 \
__asm_emit 0x65 \
__asm_emit 0x63 \
__asm_emit 0x74 \
__asm_emit 0x20 \
__asm_emit 0x65 \
__asm_emit 0x6E \
__asm_emit 0x64 \
__asm_emit 0x00
 
 
//VMProtect 的SDK 标志
VMPBEGIN 
//要加密的核心代码片断 
VMPEND
  • 用IDA打开加了VMP壳的.exe可以发现:
  • 很多数据IDA无法进行反汇编,看到的都是压缩的数据
    • 导出表的函数不属于程序本身,而是壳要用到的函数
  • 多了.vmp0,.vmp1的节
    VMP加壳实战
  1. 开发一个简单的MFC程序
1
2
3
4
5
6
void CVMPDemoDlg::OnBnClickedOk()
{
    // TODO: 在此添加控件通知处理程序代码
    //CDialogEx::OnOK();
    MessageBox(_T("hello VMP"), _T("VMP"), MB_OK);
}
  1. 没加壳之前逆向分析
    • 可以直接搜索关键字hello 就可以找到MessageBox函数,按F5可以查看到反C代码

  2. 加壳再逆向分析 对照实验
    3.1 安装VMProtect,安装目录获取库文件:
  • VMProtect Ultimate\Include\C
    • VMProtectSDK.h 给应用程序加壳
    • VMProtectDDK.h 给驱动加壳
  • VMProtect Ultimate\Lib\Windows\
    • VMProtectDDK32.lib
    • VMProtectDDK32.sys
    • VMProtectDDK64.lib
    • VMProtectDDK64.sys
    • VMProtectSDK32.dll
    • VMProtectSDK32.lib
    • VMProtectSDK64.dll
    • VMProtectSDK64.lib

3.2 拷贝到工程目录下

1
2
- 把头文件拷贝到源代码目录下
- 把.dll/.sys和.lib文件拷贝到源代码目录下

3.3 包含头文件和加载.lib文件,给关键代码加上VMP壳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include "VMProtectSDK.h"
#pragma comment(lib,"VMProtectSDK32.lib")
 
//关键代码,加上VMP壳
void CVMPDemoDlg::OnBnClickedOk()
{
    VMProtectBegin("tagname");
    // TODO: 在此添加控件通知处理程序代码
    //CDialogEx::OnOK();
    MessageBox(_T("hello VMP"), _T("VMP"), MB_OK);
    VMProtectEnd();
}
 
/// 其他标记
//开始保护处标记
VMProtectBegin( const char *);
//开始虚拟化代码处标记(包括保护设置)
VMProtectBeginvirtualization( const char *);
//开始变异代码处标记(包括保护设置)
VMProtectBeginMutation(const charn *)
//开始虚拟+代码变异标记处
VMPrckctBeginLltra(cont char *)
VMProtectBeginvitualizationLoekEykey(const char *);
VMProtectBegimUltraLockEykKey(const char *);
 
//保护结束处标记
VMRrotectEnd(void)
 
 
//检测调试
BOOL VKMProtectlsDebuggerPresent(BOOL);
 
//检测虚拟机
BOOL VMProtectlsValidlmageCRC(void)
 
//映像文件CRC校验
BOoL VMProtectisValidimageCRC(void)
 
//解密被保护的名为字符串A,A表示是多字节字符串
char * VMProtectDecryptStringA(const char *value);
 
//解密被保护的名为字符串W,W表示宽字节字符串
wchar_t * VMProtectDecryptStringW(const wchar_t *value);
 
///eg
//char Decrypt(chtar key, char* buff, long len ){}
Decrypt(VMProtectDecryptStringA("mm1 23456"),//此时密文密钥key被解密
buff,
256
);

3.4 编译出release版本后,用VMProtect进行加壳


3.5 用IDA逆向分析

  • 可以直接搜索关键字hello 已经看不到结果了

去模糊化方法

  • 基于IDC脚本:使用IDCC执行每一项CPU操作,模拟程序执行,修该程序数据库。
    • 不足:复杂的情况(加了upx壳,aspack壳,或者版本更新,脚本不适用了)缺乏通用性
  • 模拟器去模糊:ida-x86emu(ALT+F8启动)
    • 没有Linux或者MacOS,也可以再Windows上用模拟器ida-x86emu去模拟执行
    • 在模拟程序运行的过程中,自然把花指令全部去掉,直接把正确的反汇编代码分析出来

      学习《IDA Pro权威指南》 和 《IDA代码破解揭秘》

      逆向分析实战

      脱壳原理和步骤
  1. 查看是否加壳以及壳的种类
    • 查壳工具:PEiD、ExeinfoPe
  2. 查找程序的真正入口点(OEP,Original Entry Point);

    如何定位OEP: https://bbs.pediy.com/thread-218605.htm

  3. 抓取内存映像文件
    • 用OD加载加了壳的程序,让其运行(加了壳的程序要运行,肯定会加载入内存中,把自己的壳脱掉,所以在内存中,程序是脱了壳的),当它脱完壳的时候,找到它的OEP后,把文件从内存中Dump出来
  4. 输入表重建
  • 在PE文件还在磁盘中适合IMP表和IAT表和IAT指向同一个结构体数组
  • 一旦PE文件运行起来之后(即加载入内存之后),导入表的OriginalFirstLink依然指向INT指向一个结构体数组(存放函数名+下标),但导入表的FirstLink和IAT表指向的是新的地方(存放函数的地址,而不是函数名+下标了),所以需要为抓取内存映像文件重建输入表(也就是导入表),否则抓取内存映像文件依然无法执行(因为调用的函数都不知道)
  • EXE 的所有的导入函数信息都会写入输入表(也就是导入表)中,在PE文件映射到内存后,windows将相应的DLL文件装入,EXE文件通过输入表找到相应的DLL中的导入函数,从而完成程序的正常运行,这一动态连接的过程都是由输入表参与的。
  • 重建输入表也有专门的工具可以处理
    UPX脱壳
    OllyDbg
  • OllyDbg常见快捷键
    • F8,单步执行,不进入call
    • F7,单步执行,进入call
    • F9(运行)
    • F4,执行到光标处,执行循环的时候很好用,比如光标在循环外,按F4可以跳出循环
    • CTRL+F9:执行直到返回
    • ALT+F9:从系统领空直接返回,比如调试当前处于系统API,按ALT+F9直接执行完系统API返回
    • F2,设置断点,int 3断点
    • F3,装入要调试的程序
    • Ctrl+ F2,重新开始调试程序,程序跑飞的时候用
  • OllyDbg单步跟踪原则:
    • 遇到近CALL 用F7
    • 遇到远CALL 用F8
    • 遇到跳转到向上地址(比如循环),光标选中下一个地址,按F4
    • 遇到大的跳转,要注意,离OEP就近了,按F8小心单步跟踪
  • OllyDbg抓取内存映像文件
    • OllyDbg->插件->OllyDumpEx->Dump process 抓取内存映像文件,为下步重建输入表做准备
      脱壳机
  • OllyDbg找OEP很繁琐,对于UPX壳这种简单的壳,可以使用upxshell.exe进行脱壳
    其他类型的脱壳
  • ASProtect脱壳
    • ASProtect unpacker,PE_Kill出品,功能强大,能自动修复SDK,ASProtect2.X之前的版本完全支持。
  • VMP脱壳

    https://bbs.pediy.com/forum-88.htm 加壳脱壳精华区

    实操练习

  1. 破解密钥

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2023-2-7 07:48 被公众号坚毅猿编辑 ,原因: 更新求职意向
收藏
点赞2
打赏
分享
最新回复 (1)
雪    币: 1486
活跃值: (1990)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huluxia 2023-2-8 15:57
2
1
游客
登录 | 注册 方可回帖
返回