0%

C++反汇编与逆向分析 - 变量位置与访问方式

变量的作用域指的是变量在源码中可以被访问到的范围。全局变量属于进程作用域,在整个进程中都可以访问到这个全局变量;静态变量属于文件作用域,在当前源码文件中可以访问;局部变量属于函数作用域,在函数内可以访问到;

运行环境:

  • 操作系统: Windows 7家庭版
  • 编译器:VC6 VS2013

变量识别

1
2
3
4
5
6
7
8
9
10
11
12
13
static int g_nTest = 0x8765093;

int main(int argc, char* argv[])
{
int n = 999;
scanf("%d",&n);
scanf("%d",&g_nTest);
scanf("%d",&argc);
printf("n = %d\n",n + argc);
printf("g_nTest = %d\n",g_nTest + argc);
printf("Hello World!\n");
return 0;
}

全局变量

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
00401010                          | 55                   | push ebp                                        |
00401011 | 8BEC | mov ebp,esp |
00401013 | 83EC 44 | sub esp,44 |
00401016 | 53 | push ebx |
00401017 | 56 | push esi |
00401018 | 57 | push edi |
00401019 | 8D7D BC | lea edi,dword ptr ss:[ebp-44] |
0040101C | B9 11000000 | mov ecx,11 |
00401021 | B8 CCCCCCCC | mov eax,CCCCCCCC |
00401026 | F3:AB | rep stosd |
00401028 | C745 FC E7030000 | mov dword ptr ss:[ebp-4],3E7 |局部变量
0040102F | 8D45 FC | lea eax,dword ptr ss:[ebp-4] |
00401032 | 50 | push eax |
00401033 | 68 48504200 | push test.425048 | 425048:"%d"
00401038 | E8 23010000 | call test.401160 | printf
0040103D | 83C4 08 | add esp,8 |
00401040 | 68 307A4200 | push test.427A30 |全局变量
00401045 | 68 48504200 | push test.425048 | 425048:"%d"
0040104A | E8 11010000 | call test.401160 |
0040104F | 83C4 08 | add esp,8 |
00401052 | 8D4D 08 | lea ecx,dword ptr ss:[ebp+8] |参数访问
00401055 | 51 | push ecx |
00401056 | 68 48504200 | push test.425048 | 425048:"%d"
0040105B | E8 00010000 | call test.401160 |
00401060 | 83C4 08 | add esp,8 |
00401063 | 8B55 FC | mov edx,dword ptr ss:[ebp-4] | edx:EntryPoint
00401066 | 0355 08 | add edx,dword ptr ss:[ebp+8] | edx:EntryPoint
00401069 | 52 | push edx | edx:EntryPoint
0040106A | 68 3C504200 | push test.42503C | 42503C:"n = %d\n"
0040106F | E8 6C000000 | call test.4010E0 |
00401074 | 83C4 08 | add esp,8 |
00401077 | A1 307A4200 | mov eax,dword ptr ds:[427A30] |
0040107C | 0345 08 | add eax,dword ptr ss:[ebp+8] |

在IDA中识别 为这样:

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
.text:00401000 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401000 _main proc near ; CODE XREF: start+AF↓p
.text:00401000
.text:00401000 var_4 = dword ptr -4
.text:00401000 argc = dword ptr 4
.text:00401000 argv = dword ptr 8
.text:00401000 envp = dword ptr 0Ch
.text:00401000
.text:00401000 push ecx
.text:00401001 lea eax, [esp+4+var_4]
.text:00401005 mov [esp+4+var_4], 3E7h
.text:0040100D push eax
.text:0040100E push offset aD ; "%d"
.text:00401013 call _scanf
.text:00401018 push offset dword_408030 ;全局变量通过地址访问
.text:0040101D push offset aD ; "%d"
.text:00401022 call _scanf
.text:00401027 lea ecx, [esp+14h+argc]
.text:0040102B push ecx
.text:0040102C push offset aD ; "%d"
.text:00401031 call _scanf
.text:00401036 mov edx, [esp+1Ch+argc]
.text:0040103A mov eax, [esp+1Ch+var_4]
.text:0040103E add eax, edx
.text:00401040 push eax
.text:00401041 push offset aND ; "n = %d\n"
.text:00401046 call sub_401080
.text:0040104B mov ecx, dword_408030
.text:00401051 mov edx, [esp+24h+argc]
.text:00401055 add ecx, edx
.text:00401057 push ecx
.text:00401058 push offset aGNtestD ; "g_nTest = %d\n"
.text:0040105D call sub_401080
.text:00401062 push offset aHelloWorld ; "Hello World!\n"
.text:00401067 call sub_401080
.text:0040106C xor eax, eax
.text:0040106E add esp, 30h
.text:00401071 retn
.text:00401071 _main endp
.text:00401071

全局代码

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
// Test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
int GetInt()
{
return 3;
}
int GetInt1()
{
return 4;
}
int GetInt2()
{
return 5;
}
int GetInt3()
{
return 6;
}
int GetInt4()
{
return 7;
}
static int g_nTest = GetInt();
static int g_nTest1 = GetInt1() + g_nTest;
static int g_nTest2 = GetInt2() / 7;
static int g_nTest3 = GetInt3() % -8;
static int g_nTest4 = GetInt4();
int main(int argc, char* argv[])
{
int n = 999;
scanf("%d", &n);
scanf("%d", &g_nTest);
scanf("%d", &argc);
printf("n = %d\n", n + argc);
printf("g_nTest = %d\n", g_nTest + argc);
printf("Hello World!\n");
return 0;
}


// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧:
// 1. 使用解决方案资源管理器窗口添加/管理文件
// 2. 使用团队资源管理器窗口连接到源代码管理
// 3. 使用输出窗口查看生成输出和其他消息
// 4. 使用错误列表窗口查看错误
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件

这里直接反汇编Release版本:

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
start:
.text:00401407 ; __unwind { // __SEH_prolog4
.text:00401407 call sub_4016AF
.text:0040140C jmp loc_401285
.text:0040140C ; } // starts at 401407
.text:0040140C start endp ; sp-analysis failed

;进入loc_401285
.text:00401285 ; __unwind { // __SEH_prolog4
.text:00401285 push 14h
.text:00401287 push offset unk_402560
.text:0040128C call __SEH_prolog4
.text:00401291 push 1
.text:00401293 call sub_401487
.text:00401298 pop ecx
.text:00401299 test al, al
.text:0040129B jz loc_4013F1
.text:004012A1 xor bl, bl
.text:004012A3 mov [ebp-19h], bl
.text:004012A6 ; __try { // __except at loc_4013C0
.text:004012A6 and dword ptr [ebp-4], 0
.text:004012AA call sub_401455
.text:004012AF mov [ebp-24h], al
.text:004012B2 mov eax, dword_403044
.text:004012B7 xor ecx, ecx
.text:004012B9 inc ecx
.text:004012BA cmp eax, ecx
.text:004012BC jz loc_4013F1
.text:004012C2 test eax, eax
.text:004012C4 jnz short loc_40130F
.text:004012C6 mov dword_403044, ecx
.text:004012CC push offset unk_4020F8
.text:004012D1 push offset unk_4020EC
.text:004012D6 call _initterm_e
.text:004012DB pop ecx
.text:004012DC pop ecx
.text:004012DD test eax, eax
.text:004012DF jz short loc_4012F2
.text:004012DF ; } // starts at 4012A6
.text:004012E1 mov dword ptr [ebp-4], 0FFFFFFFEh
.text:004012E8 mov eax, 0FFh
.text:004012ED jmp loc_4013E1
.text:004012F2 ; ---------------------------------------------------------------------------
.text:004012F2
.text:004012F2 loc_4012F2: ; CODE XREF: start-128↑j
.text:004012F2 push offset unk_4020E8
.text:004012F7 push offset unk_4020CC
.text:004012FC call _initterm
.text:00401301 pop ecx
.text:00401302 pop ecx
.text:00401303 mov dword_403044, 2
.text:0040130D jmp short loc_401314
.text:0040130F ; ---------------------------------------------------------------------------

;继续进入_initterm
.rdata:004020D0 dd offset sub_401273 ; set_newMode 库函数
.rdata:004020D4 dd offset sub_401070 ;这几个函数分别代表我们写的函数
.rdata:004020D8 dd offset sub_401000
.rdata:004020DC dd offset sub_401020
.rdata:004020E0 dd offset sub_401040
.rdata:004020E4 dd offset sub_401060

静态局部变量

静态变量分为全局静态变量和局部静态变量,全局静态变量和全局变量类似,只是全局静态变量只能在本文件中使用;

局部静态便令比较特殊,它不会随作用域的结束而小时,并且在未进入作用域之前就已存在,其生命周期也和全局变量相同。局部静态变量会预先被当作全局变量处,而它的初始化部分只是在做赋值操作

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

void ShowStatic(int nNumber)
{
static int g_snNumber = nNumber; //定义局部静态变量,赋值为参数
printf("%d\n",g_snNumber); //打印静态变量
}

int main(int argc, char* argv[])
{
for (int i = 0; i < 5; i++)
{
ShowStatic(i);
}
return 0;
}

00401020 push ebp
00401021 mov ebp,esp ;保存寄存器环境
00401023 sub esp,40h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-40h] ;
0040102C mov ecx,10h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
9: static int g_snNumber = nNumber;
00401038 xor eax,eax ;清空EAX寄存器
0040103A mov al,[`ShowStatic'::`2'::$S1 (00427c4c)] ;取00427c4c处1字节送入al中
0040103F and eax,1 ;将eax与数值1做与运算,eax最终结果只能是0或1
00401042 test eax,eax ;比较eax,不等于0则跳转,跳转到0040105e处
00401044 jne ShowStatic+3Eh (0040105e)
00401046 mov cl,byte ptr [`ShowStatic'::`2'::$S1 (00427c4c)] ;将之前比较是否为0的地址放入cl中
0040104C or cl,1 ;将cl与数值1做或运算,cl最低为将被置1,其他不变
0040104F mov byte ptr [`ShowStatic'::`2'::$S1 (00427c4c)],cl ;将cl值存入00427c4c中
00401055 mov edx,dword ptr [ebp+8] ;参数存入edx中
00401058 mov dword ptr [___decimal_point_length+0A9Ch (00427c48)],edx ;edx赋值到00427c48,将局部静态变量赋值为EDX寄存器中保存的数据
10: printf("%d\n",g_snNumber);
0040105E mov eax,[___decimal_point_length+0A9Ch (00427c48)] ;静态局部变量的访问,与全局变量一样
00401063 push eax
00401064 push offset string "%d\n" (0042201c)
00401069 call printf (00401110)
0040106E add esp,8
11: }
00401071 pop edi
00401072 pop esi
00401073 pop ebx
00401074 add esp,40h
00401077 cmp ebp,esp
00401079 call __chkesp (00401190)
0040107E mov esp,ebp
00401080 pop ebp
00401081 ret

cl中存储的是局部变量的标志,这个标志占1字节,通过位运算,将标志中的一个数据置为1,以此判断局部静态变量是否被初始化过。

当有两个静态局部变量,它们会公用一个标记位的内存;

堆在C/C++中使用malloc和new实现堆空间申请,返回的数据便是申请的对空间的地址;保存堆空间首地址的变量大小为4字节的指针类型,其访问方式按作用域来划分;

堆识别在于:堆标志(全FD)、签名、地址、内存中的真相

地址特性

判断依据就是使用调试器装载程序,记住栈地址和内存地址首地址,然后运行到代码;

堆反调试

  • Peb+2的byte是调试标志位
1
2
3
4
5
6
7
//反调试代码
__asm
{
mov eax, fs:[18]
mov eax, fs:[30]
mov [eax + 0x2], 0
}
  • Peb+0x68的地方调试时值非0,运行时值为0,,为堆类型
1
2
3
4
5
6
__asm
{
mov eax, fs:[18]
mov eax, fs:[30]
mov [eax + 0x68], 0
}