反汇编代码分析

text::masm32汇编

text::逆向工具


一、函数分析

1 系统函数

系统函数是库中自带的函数,相关程序如OD可以将其直接分析出来。

下面仅举出一些例子,实际系统函数有很多。

系统函数关闭优化:优化——启动内部函数——否。

// 相关系统函数
// 图形显示函数
MessageBoxA(); //ASCII码版本
MessageBoxW(); //Unicode版本

// 输出
printf();

// 网络函数
sokcet();
;strcmp(a,b)
;取a,b前四位的地址
mov edx,dword ptr [esp+4]
mov ecx,dword ptr [esp+8]
;检测最后两位是否为00,保证4字节对齐
test edx,00000003
jnz alignment
start:
;比较第一位
mov eax,dword ptr [edx]
cmp al,byte ptr [ecx]
jne false
;判断是否是'\0'
or al,al
jz end
;比较第二位
cmp ah,byte ptr [ecx+1]
jne false
or ah,ah
jz end
;右移位,继续比较
shr eax,10
cmp al,byte ptr [ecx+3]
jne false
or al,al
jz end
cmp ah,byte ptr [ecx+4]
jne false
or ah,ah
jz end
;比较接下来四个字符
add ecx,4
add,edx,4
;判断结尾,没有结尾继续回到上面重复比较
or ah,ah
jnz start

2 用户函数

用户自己编写的函数。

call会入栈EIP一次,retn会出栈一次,所以一段栈中的存储大致如下:
【栈顶】局部变量 | 上一函数EBP | 上一函数的EIP 【栈底】

所以一般而言:

[EBP - ?? ]:本函数的局部变量
[EBP + ??]:上一函数的局部变量

;空函数必做的事 void fun(){}
push ebp ;保存上一函数的栈底,同时esp会增加,esp此时指向了当前函数的栈底。
mov ebp,esp ;把esp给ebp,让ebp做栈底,之后操作改变的偶数esp的值。
pop ebp ;出栈,获得上一函数栈底,esp此时指向的应该是上一函数栈顶。
retn

;分配空间 void fun(){int a=3,b=5; char c=1;}
push ebp
mov ebp,esp
sub esp,0C ;局部变量存在栈,开辟空间,会4对齐,所以是0x0C
mov dword ptr [local.1],3
mov dword ptr [local.2],5
mov byte ptr [lcoal.3+3],1
mov esp,ebp ;复原基址,返回上一函数
pop ebp
ret

;参数传递
;void main(){ printf(add(3,6)); }
;int add(int a,int b){ return a+b; }
;主函数
push 6 ;传参,逆向入栈,便于正向出栈
push 3
call add
add esp,8 ;恢复栈,保持栈平衡
;add函数
push ebp
mov ebp,esp
mov eax,dword ptr [ebp+8]
mov eax,dword ptr [ebp+0C]
push ebp
ret

3 调用约定

VC:配置属性——C/C++——高级——调用约定

调用约定可以写在函数前,如 int __cdecl add(){}

C中不加说明默认__cdecl

C++也一样,但是默认调用方式可以在IDE(开发环境)中修改。

带有可变参数的函数必须用__cdecl方式。如:int printf(char* fmtstr,...);

调用约定分类:

  1. cdecl:
    • __cdecl是C Declaration的缩写,所以参数是从右到左依次入栈,并且参数是由调用者清除,称为手动清栈。(注:不是指程序员清栈)
  2. stdcall:

    • API 调用是 standardcall,是C++的标准调用方式,所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是 this 指针。
    • 这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是 retn X,X表示参数占用的字节数(内存空间大小)。CPU在ret之后自动弹出X个字节的栈空间,称为自动清栈。
  3. fastcall:

    • __fastcall是编译器指定的快速调用方式。
    • fastcall通常规定将前两个(或若干个)参数由寄存器传递,其余参数还是通过堆栈传递。
    • 不同编译器编译的程序规定的寄存器不同,返回方式和stdcall相同。
; 1.cdecl

;示例代码int add(int a,int b){ return a+b; }
;调用代码
push ebp
mov ebp,esp
mov eax,dword ptr [arg.1]
mov eax,dword ptr [arg.2]
pop ebp
ret
;主函数
push [arg.2] ;参数从右往左入栈
push [arg.1]
call func
add esp,8 ;调用者清栈
; 2.stdcall


;示例代码
;调用代码
push ebp
mov ebp,esp
mov eax,dword ptr [arg.1]
mov eax,dword ptr [arg.2]
pop ebp
ret 8 ;自动清栈
;主函数
push [arg.2] ;参数从右往左入栈
push [arg.1]
call func
; 3.fastcall


;示例代码(假设有三个参数)
;调用代码
push ebp
mov ebp,esp
sub esp,8
mov dword ptr [local.2],edx
mov dword ptr [local.1],ecx
mov eax,dword ptr [local.1]
add eax,dword ptr [local.2]
add eax,dword ptr [arg.3]
pop ebp
ret 8 ;自动清栈
;主函数
push [arg.3]
mov edx,push [arg.2] ;参数从右往左入栈
mov ecx,push [arg.1]
call func

二、变量分析

1 指针

;定义int *p = &i;
lea edx,[local.2] ;edx = &i
mov dword ptr [local.1],edx ;p = edx;

;指针操作
mov ebx,p
add word ptr [ebx],0x333 ;i+=0x333(此处前面要加上单元大小,否则默认byte)

2 i++和++i

int i = 0;
;int j = i++(先赋值再加)
mov dword ptr [local.2],0 ;j = 0
mov ecx,dword ptr [local.1] ;ecx = i
mov dword ptr [local.2],ecx ;j = ecx
mov edx,dword ptr [local.1] ;edx = i
add edx,1 ; edx = edx + 1
mov dword ptr [local.1],edx ;i = edx

;int j = ++i(先加再赋值)
mov ecx,dword ptr [local.1] ;ecx = i
add ecx,1 ; ecx = ecx + i
mov dword ptr [local.1],ecx ;i = ecx
mov edx,dword ptr [local.1] ;edx = i
mov dword ptr [local.2],edx ;j = edx

;完全优化:int i=0;int j=i++;printf("%d",j);j=++i;printf("%d",j);
;编译器会直接先输出0,然后i += 2,然后输出2。

3 浮点数

st0-st7(MMX,FPU):80位两用寄存器器。

;老汇编
fld fstp fadd

;新汇编
;float fl = 8.765f;
movss xmm0,dword ptr [0A32118h]
movss dword ptr [fl],xmm0
;fl++;
movss xmm0,dword ptr [fl]
addss xmm0,dword ptr [0A32114h]
movss dword ptr [fl],xmm0

;时间优化
movups xmm0,dqword ptr ds:[0x2F2118] ;将内存地址0x2F2118处的8字节数据(即一个双精度浮点数)加载到XMM0寄存器中。movups表示以128位为单位进行加载,因为XMM0寄存器是128位的。
movsd qword ptr ss:[esp],xmm0 ;将XMM0寄存器中的数据转存到栈顶(ESP寄存器指向的位置)中。这里使用了movsd指令,表示以64位为单位进行转存。

三、结构分析

1 if-else

int main()
{
int a = 3,b = 2; //正常赋值
if(a > b)
// cmp [a],[b]
// jle printf("a <= b\n");不满足条件则转移,否则顺序向下执行。
{
printf("a > b\n");
}
else
// jmp short 01282100;else直接被翻译成jmp跳转到结束位置
{
printf("a <= b\n");
}
// 设此处地址01282100
return 0;
}

2 switch-else

2.1 普通形式

int main()
{
printf("switch-case\n");
int a = 3;
// mov dword ptr [ebp-4],3

switch(a)
// mov eax,dword ptr [ebp-4] ;这里他会自动创建一个匿名变量,值同a
// mov dword ptr [ebp-8],eax ;之后他会用这个匿名变量进行比较
{
case 1:
// cmp dword ptr [ebp-8],1
// je addr
printf("1\n");
break;
// jmp addr ;不写break会没有jmp导致向下执行。
case 2:
printf("2\n");
break;
case 3:
printf("3\n");
break;
default:
// jmp addr
printf("default\n");
break;
}
printf("end\n");
}

// 整体结构如下
// 1.赋值
mov eax,dword ptr [ebp-4]
mov dword ptr [ebp-8],eax
// 2.一堆cmp和je
cmp dword ptr [ebp-8],1
je addr
cmp dword ptr [ebp-8],2
je addr
cmp dword ptr [ebp-8],3
je addr
jmp addr // defalut
// 3.每个case执行的操作,顺序存储
call printf(1)
jmp addr
call printf(2)
jmp addr
call printf(3)
jmp addr
printf("default")

2.2 跳转表

int main()
{
printf("switch-case\n");
int a = 0x20;
switch(a)
{
case 0x13:printf("0x13\n");break;
case 0x15:printf("0x15\n");break;
case 0x10:printf("0x10\n");break;
case 0x20:printf("0x20\n");break;
case 0x22:printf("0x22\n");break;
default:printf("default");break;
}
printf("end\n");
}
00341080  PUSH EBP
00341081 MOV EBP,ESP
00341083 SUB ESP,8
00341086 PUSH 汇编语言.00342100 ; /Arg1 = 00342100 ASCII "switch-case"
0034108B CALL 汇编语言.00341040 ; \汇编语言.00341040
00341090 ADD ESP,4

00341093 MOV DWORD PTR SS:[EBP-8],20
0034109A MOV EAX,DWORD PTR SS:[EBP-8]
0034109D MOV DWORD PTR SS:[EBP-4],EAX
003410A0 MOV ECX,DWORD PTR SS:[EBP-4]

003410A3 SUB ECX,10 ; a-10,就是a减去case中的最小值
003410A6 MOV DWORD PTR SS:[EBP-4],ECX
003410A9 CMP DWORD PTR SS:[EBP-4],12 ;因为case中最大为0x22,最小0x10,所以范围是12。如果a-10不落在0-12中表示不是这里的面的值,直接下面跳转结束,否则会有一个跳转表,计算出的值会对应跳转到一个指定的地址,输出指定的值。
003410AD JA SHORT 汇编语言.0034110B

003410AF MOV EDX,DWORD PTR SS:[EBP-4] ;落在范围内则找表跳转到指定位置
003410B2 MOVZX EAX,BYTE PTR DS:[EDX+341144]
003410B9 JMP DWORD PTR DS:[EAX*4+34112C]

003410C0 PUSH 汇编语言.00342110 ; /Arg1 = 00342110 ASCII "0x13"
003410C5 CALL 汇编语言.00341040 ; \汇编语言.00341040
003410CA ADD ESP,4
003410CD JMP SHORT 汇编语言.00341118

003410CF PUSH 汇编语言.00342118 ; /Arg1 = 00342118 ASCII "0x15"
003410D4 CALL 汇编语言.00341040 ; \汇编语言.00341040
003410D9 ADD ESP,4
003410DC JMP SHORT 汇编语言.00341118

003410DE PUSH 汇编语言.00342120 ; /Arg1 = 00342120 ASCII "0x10"
003410E3 CALL 汇编语言.00341040 ; \汇编语言.00341040
003410E8 ADD ESP,4
003410EB JMP SHORT 汇编语言.00341118

003410ED PUSH 汇编语言.00342128 ; /Arg1 = 00342128 ASCII "0x20"
003410F2 CALL 汇编语言.00341040 ; \汇编语言.00341040
003410F7 ADD ESP,4
003410FA JMP SHORT 汇编语言.00341118

003410FC PUSH 汇编语言.00342130 ; /Arg1 = 00342130 ASCII "0x22"
00341101 CALL 汇编语言.00341040 ; \汇编语言.00341040
00341106 ADD ESP,4
00341109 JMP SHORT 汇编语言.00341118

0034110B PUSH 汇编语言.00342138 ; /Arg1 = 00342138 ASCII "default"
00341110 CALL 汇编语言.00341040 ; \汇编语言.00341040
00341115 ADD ESP,4

00341118 PUSH 汇编语言.00342140 ; /Arg1 = 00342140 ASCII "end"
0034111D CALL 汇编语言.00341040 ; \汇编语言.00341040
00341122 ADD ESP,4

00341125 XOR EAX,EAX
00341127 MOV ESP,EBP
00341129 POP EBP
0034112A RETN

3 for

for (int i = 1; i <= 10; ++i)
{
printf("%d", i);
}

;优化/Od(无优化)
push ecx ;入栈ecx,不需要关心ecx的值,只是开辟栈存i而已
00591091 MOV DWORD PTR SS:[EBP-4],1 ;赋初值
00591098 JMP SHORT 汇编语言.005910A3 ;进入循环
;循环
0059109A MOV EAX,DWORD PTR SS:[EBP-4] ;++i
0059109D ADD EAX,1
005910A0 MOV DWORD PTR SS:[EBP-4],EAX
005910A3 CMP DWORD PTR SS:[EBP-4],0A ;i>10结束
005910A7 JG SHORT 汇编语言.005910BC
005910A9 MOV ECX,DWORD PTR SS:[EBP-4]
005910AC PUSH ECX ;Arg2
005910AD PUSH 汇编语言.00592108 ;Arg1 = 00592108 ASCII "%d"
005910B2 CALL 汇编语言.00591040 ;printf("%d",i);
005910B7 ADD ESP,8
005910BA JMP SHORT 汇编语言.0059109A

;优化/O1(大小优化,指令变少)
;可能还会有 mov edi, dword ptr [<&MSUCR100printf>],call edi的指令,这也是一种优化
00311034 PUSH ESI ;保护esi
0031103F XOR ESI,ESI ;esi=0
00311042 INC ESI ;先i=1
;循环
00311043 PUSH ESI ;Arg2
00311044 PUSH 汇编语言.00312108 ;Arg1,ASCII "%d"
00311049 CALL 汇编语言.00311006
0031104E INC ESI
0031104F POP ECX ;两次pop相当于上面的ADD ESP,8,保证堆栈平衡
00311050 POP ECX
00311051 CMP ESI,0A
00311054 JLE SHORT 汇编语言.00311043 ;i<=10继续循环
;循环结束
00311056 POP ESI ;取出esi

;优化/O2(速度优化)
00851040 PUSH ESI
0085104E MOV ESI,1 ;i=1
;循环开始
00851053 PUSH ESI ; Arg2
00851054 PUSH 汇编语言.00852108 ; Arg1 = 00852108 ASCII "%d"
00851059 CALL 汇编语言.00851010 ; 汇编语言.00851010
0085105E INC ESI
0085105F ADD ESP,8
00851062 CMP ESI,0A
00851065 JLE SHORT 汇编语言.00851053
;循环结束
00851067 POP ESI

;优化/Ox(完全优化)
;该实例代码优化结果基本同/O2

4 while/do-while

; while(i<=10){}
loop:
cmp dowrd ptr [local1],0A
jg end
statement
jmp loop
end:
...

; do{}while(i<=10);
loop:
statement
cmp dowrd ptr [local.1],0A
jle loop

5 逻辑运算

;a || b
cmp dword ptr [a],0
jne true
cmp dwrod ptr [b],0
jne true

;a && b
cmp dword ptr [a],0
je false
cmp dwrod ptr [b],0
je false

;b = !a
cmp dword ptr [a],0
sete al
mov dword ptr [b],eax

;b = ~a
mov ecx,dword ptr [a]
not ecx
mov dword ptr [b],ecx

;c = a ^ b
movzx eax,byte ptr [b]
movzx ecx,byte ptr [a]
xor eax,ecx
mov byte ptr [c],al

四、汇编指令

1 repne和scasb

常用于:

  1. 寻找字符串中字符,字符存在al中。
  2. 计算字符串长度。
  3. 在内存中地位一串特征码。
;scasb,编译后如下:(相当于cmp byte ptr[edi],al。同时还会根据DF的值对edi加n,取决于操作的内存大小)
scas byte ptr [edi]
;同理两个
scasw; scas word ptr [edi]
scasd; scas dword ptr [edi]


;repnz/repne scasb,编译后如下:(当ecx!=0且ZF=0,重复后面的指令(scas byte ptr es:[edi]),且每次ecx--)
repne scas byte ptr es:[edi]

2 repe和cmpsb

常用于比较字符串是否相等

;同理上面
;cmpsb,cmpsw,cmpsd
cmps byte ptr [edi],byte ptr [esi]
cmps word ptr [edi],byte ptr [esi]
cmps dword ptr [edi],byte ptr [esi]

;repe/repz(当ecx!=0且ZF=1重复后面语句并ecx--)
repe cmpsb
;汇编编写字符串比较函数
;__declspec(naked)告诉编译器该函数是纯汇编实现,不自动添加寄存器保护和堆栈平衡
; 窄字节
__declspec(naked) int asm_strcmp(char *s1,char* s2)
{
__asm
{
push ebp
mov ebp,esp ;构建新栈底
push ecx
push edi
push esi
push edx

xor al,al
mov edi,[ebp+4+4] ;s1
mov ecx,-1
CLD
repnz scasb
not ecx

mov edi,[ebp+4+4] ;s1
mov esi,[ebp+4+8] ;s2
repz cmpsb

xor eax,eax
xor edx,edx
mov al,[edi-1]
mov dl,[esi-1]
sub eax,edx

pop edx
pop esi
pop edi
pop ecx
pop ebp

retn
}
}

; 宽字节
__declspec(naked) int asm_strcmp(wchar_t *s1,wchar_t* s2)
{
__asm
{
push ebp
mov ebp,esp ;构建新栈底
push ecx
push edi
push esi
push edx

xor ax,ax ;区别窄字节
mov edi,[ebp+4+4] ;s1
mov ecx,-1
CLD
repnz scasw ;区别窄字节
not ecx

mov edi,[ebp+4+4] ;s1
mov esi,[ebp+4+8] ;s2
repz cmpsw ;区别窄字节

xor eax,eax
xor edx,edx
mov al,[edi-2] ;区别窄字节
mov dl,[esi-2] ;区别窄字节
sub eax,edx

pop edx
pop esi
pop edi
pop ecx
pop ebp

retn
}
}

3 rep和串操作

;串存储
;stosb,stosw,stosd | stos byte ptr [edi]
move byte ptr [edi],al
move word ptr [edi],ax
move dword ptr [edi],eax

;rep sto byte ptr [edi](ecx计数,edi存值,每次存完edi += n)
rep stosb

;串载入(只能载入一段,eax中的值会被一直覆盖)
;lodsb,lodsw,lodsd | lods byte ptr [edi]
mov al, byte ptr [esi]
mov ax, word ptr [esi]
mov eax, dword ptr [esi]
;rep lods byte ptr [edi](ecx计数,edi存值,每次存完edi += n)
rep lodsb
; 自定义移动字符串,效率远高于memset
int src[10];
__asm
{
mov ecx,len(src)
xor eax,eax
lea edi,src
rep stosd
}

五、可能的花指令

对程序没有实际影响,却会干扰破解者进行破解。

;不带进位循环左移32位等于没移
rol i,0x20

六、壳