第40章 64位调试

本文最后更新于:2022年5月27日 下午

第40章64位调试

本章将学习64位环境中的调试方法。64位环境中(x64+Windows OS 64位),32位进程与64位进程彼此共存,所以在64位环境中应当能够调试PE32与PE32+这2种文件。本章学习过程中还要就各种情形下调试的热点展开讨论,让大家进一步加深对64位环境下调试的理解。

本章讲解中将不会涉及有关IA-64调试的内容。IA-64(Itanium)搭载于高性能服务器中,我们一般不会接触到。由于Windbg与IDA Pro都支持x64与IA-64,所以可以正常使用它们调试IA-64。需要注意的是,IA-64的指令体系不同于x64/x86,详细分析代码时请参考Intel用户手册。

40.1 x64环境下的调试器

x64芯片从诞生之日起就完全支持X86,所以32位OS与64位OS都可以安装。Windows 64位OS不仅可以运行64位进程(PE32+类型),还可以同时(向下兼容)运行32位进程(PE32类型)。 在32位/64位CPU、OS、进程彼此共存的情形下整理了各OS与进程可用的调试器(参考表40-1)。

OS PE32 PE32+
32位 OllyDbg、 IDA Pro、 WinDbg IDA Pro(DisassembIe only)
64位 OllyDbg、 IDA Pro、 WinDbg IDA Pro、WinDbg

在32位OS中无法调试PE32+文件,但是使用IDA Pro可以查看PE32+文件内的反汇编代码。另外,令人遗憾的是,OllyDbg调试器并不支持PE32+文件。因此,进行PE32+调试时必须在64位OS中使用IDA Pro正式版本和Windbg 64位版本还有x64dbg。下面通过一个练习文件(WOW64Test_x64.exe)

来学习在64位环境(x64&Windows 7 64位)下调试的方法。

40.2 64位调试

本节继续以前面的WOW64测试文件(WOW64Test_x64.exe)作为练习示例进行64位调试练习。

练习示例:WOW64Test

先看示例文件的源代码(WOW64Test.cpp)。

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 "stdio.h"
#include "windows.h"
#include "Shlobj.h"
#include "tchar.h"
#pragma comment(lib, "Shell32.lib")

int _tmain(int argc, TCHAR* argv[])
{
HKEY hKey = NULL;
HANDLE hFile = INVALID_HANDLE_VALUE;
TCHAR szPath[MAX_PATH] = {0,};

////////////////
// system32 folder
if( GetSystemDirectory(szPath, MAX_PATH) )
{
_tprintf(L"1) system32 path = %s\n", szPath);
}

////////////////
// File size
_tcscat_s(szPath, L"\\kernel32.dll");
hFile = CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if( hFile != INVALID_HANDLE_VALUE )
{
_tprintf(L"2) File size of \"%s\" = %d\n",
szPath, GetFileSize(hFile, NULL));
CloseHandle(hFile);
}

////////////////
// Program Files
if( SHGetSpecialFolderPath(NULL, szPath,
CSIDL_PROGRAM_FILES, FALSE) )
{
_tprintf(L"3) Program Files path = %s\n", szPath);
}

////////////////
// Registry
if( ERROR_SUCCESS == RegCreateKey(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\ReverseCore", &hKey) )
{
RegCloseKey(hKey);
_tprintf(L"4) Create Registry Key : HKLM\\SOFTWARE\\ReverseCore\n");
}

return 0;
}

WOW64Test示例程序非常简单,它先调用GetSystemDirectory()、CreateFile()、SHGetSpecialFolderPath()、RegCreateKey()这4个API,然后输出运行结果。64位系统环境下,X86应用程序是通过WOW64模式运行的,所以系统文件夹与重要的注册表键都要重定向,本示例明确表明了这一点。

借助Visual C++2010 Express EditionX具将上述源代码分别编译为x86( WOW64Test_x86.exe)与x64程序(WOW64Test_x64.exe),然后运行,如图40-1所示。

下节分别调试这2个程序。

在Windows XP/Vista/7 64位系统下才能正常调试示例文件。

40.3 PE32: WOW64Test_x86.exe

Windows 64位OS中,PE32文件(SYS文件除外)通过WOW64模式运行。原32位环境中使用的逆向分析工具大部分可以照常使用,但是OllyDbg 1.10对x64环境支持得并不好,建议使用OllyDbg 2.0版本。

(1) OllyDbg 1.10无法直接在WOW64环境中运行,但是安装Olly Advanced(或AdvancedOlly)插件后,复选x64选项即可正常运行(参考图40-2)。

http://tuts4you.com/download.php7view.75

Olly Advanced是一个非常有用的插件,它修正了 OllyDbg本身的Bug,也提供了反调试等功能。大部分OllyDbg用户都安装使用。

(2)使用VC++ 2010编写的基于控制台的EXE文件中,用户代码一般都存在于代码节区的顶端位置,请记住这一点。

40.3.1 EP 代码

打开WOW64Test x86.exe文件后,调试器自动暂停在EP代码处,如图40-3所示。

前面已经分析过由VC++2010工具生成的(基于控制台的)PE32文件的EP代码,此处不再赘述。接下来直接查找main()函数。

40.3.2 Startup 代码

跟踪004013F5地址处的JMP 0040128F指令,岀现如图40-4所示的Startup代码

WOW64Test_x86是一个控制台程序,所以Startup代码内部存在调用main()函数的CALL指令。若刚开始学逆向分析技术时遇到CALL指令,建议跟踪进人函数,详细了解各函数的代码。

刚开始的时候不要跟踪进入得太深,深入1-depth查看代码即可。熟悉调试之后再逐渐加深,熟悉更深层次的代码。这种训练对于把握Visual C++的Startup代码与用户代码的区别有很大帮助。一名经过良好训练的逆向分析人员在实际的代码分析过程中能够快速跳过Startup代码,直接找到用户代码,即使在比较混乱的地方也不会轻易迷路而徘徊不定。

40.3.3 main()函数

下面查找main()函数。已知信息罗列如下,我们将通过这些信息来查找main()函数。

(1)被调试者(WOW64Test_x86.exe)是一个基于控制台的应用程序。

由此我们可以猜想到,WOW64Test_x86.exe调用main()函数前会先调用GetCommandLine()API。这是因为调用main()函数之前需要先把main(int argc,char* argv[])函数的参数存储到栈(x64环境下为寄存器)中。也就是说,在GetCommandLine()API设置好断点后,查看返回地址即可查找到调用main()函数的部分。此外,调用main()函数前argc与argv参数被存储在栈(x64环境下为寄存器)中,仔细查看栈(或者寄存器)也能直接找到调用main()函数的部分。

(2)调用GetSystemDirectory()、GetFileSize()、CreateFile()等API。

应用程序中使用的API很明确时,直接在相应API上设置断点,通过其返回地址也可以直接查找并进人main()函数代码。

(3) 在画面中输出由上述API获取的信息。

使用OllyDbg强大的字符串检索功能,可以直接查找并定位到指定代码处。下面我们使用第三项来查找main()函数。在OllyDbg代码窗口的鼠标右键菜单中选择Search for-All referenced strings项,如图40-5所示。

图40-5中列岀了程序中出现的所有字符串,双击顶端的00401058地址即显示出main()函数代码,如40-6所示。

可以清楚看到,函数栈帧从401000地址开始,即401000地址是mainO函数的开始部分(该地址就是第一个.text节区的起始地址)。在64位环境中调试PE32文件的方法与在32位环境中的调试

方法是一样的。但是部分代码逆向分析工具在64位环境中无法正常使用,所以使用前必须确认。

40.4 PE32+: WOW64Test_x64.exe

要在64位环境中正常调试PE32+文件,需要使用IDA Pro或Windbg调试工具(参考表40-1)。此处我们选用免费的WinDbg调试器来调试PE32+文件,学习调试过程中注意与前面介绍过的

OllyDbg+PE32的调试方法比较。

运行WinDbg64调试器,使用Open Executable…菜单,开始调试WOW64Test_x64.exe文件,如图40-7所示。

40.4.1系统断点

如图40-8所示,调试暂停在系统断点(ntdll.dll区域)。由于WinDbg中没有“暂停在进程EP处”的选项,所以需要从当前暂停位置直接转到EP处。

40.4.2 EP 代码

首先要获取EP地址,输入显示进程PE文件头的命令,如图40-9所示。

EP地址(RVA)为142C,使用g命令转到该地址处,如图40-10所示。

WinDbg默认仅显示1行命令,使用下列命令增加指令显示的条数。

1
u <address>L<line number>

图40-11是使用Visual C++ 2010工具创建的PE32+文件的EP Startup代码。请将它与图40-3中PE32文件的EP代码比较。CALL+JMP指令结构是一样的。

40.4.3 Startup 代码

跟踪(t)位于00000001’40001439地址处的JMP指令,增加指令显示的条数后[u eip L60],可

以看所有Startup代码,如代码40-2所示。

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
0:000> u eip L60
WOW64Test_x64+0x142c:
00000001`4000142c 4883ec28 sub rsp,28h
00000001`40001430 e8072a0000 call WOW64Test_x64+0x3e3c (00000001`40003e3c)
00000001`40001435 4883c428 add rsp,28h
00000001`40001439 e976feffff jmp WOW64Test_x64+0x12b4 (00000001`400012b4)
00000001`4000143e cc int 3
00000001`4000143f cc int 3
00000001`40001440 48894c2408 mov qword ptr [rsp+8],rcx
00000001`40001445 4881ec88000000 sub rsp,88h
00000001`4000144c 488d0d0dbf0000 lea rcx,[WOW64Test_x64+0xd360 (00000001`4000d360)]
00000001`40001453 ff151f7c0000 call qword ptr [WOW64Test_x64+0x9078 (00000001`40009078)]
00000001`40001459 488b05f8bf0000 mov rax,qword ptr [WOW64Test_x64+0xd458 (00000001`4000d458)]
00000001`40001460 4889442458 mov qword ptr [rsp+58h],rax
00000001`40001465 4533c0 xor r8d,r8d
00000001`40001468 488d542460 lea rdx,[rsp+60h]
00000001`4000146d 488b4c2458 mov rcx,qword ptr [rsp+58h]
00000001`40001472 e8f5750000 call WOW64Test_x64+0x8a6c (00000001`40008a6c)
00000001`40001477 4889442450 mov qword ptr [rsp+50h],rax
00000001`4000147c 48837c245000 cmp qword ptr [rsp+50h],0
00000001`40001482 7441 je WOW64Test_x64+0x14c5 (00000001`400014c5)
00000001`40001484 48c744243800000000 mov qword ptr [rsp+38h],0
00000001`4000148d 488d442448 lea rax,[rsp+48h]
00000001`40001492 4889442430 mov qword ptr [rsp+30h],rax
00000001`40001497 488d442440 lea rax,[rsp+40h]
00000001`4000149c 4889442428 mov qword ptr [rsp+28h],rax
00000001`400014a1 488d05b8be0000 lea rax,[WOW64Test_x64+0xd360 (00000001`4000d360)]
00000001`400014a8 4889442420 mov qword ptr [rsp+20h],rax
00000001`400014ad 4c8b4c2450 mov r9,qword ptr [rsp+50h]
00000001`400014b2 4c8b442458 mov r8,qword ptr [rsp+58h]
00000001`400014b7 488b542460 mov rdx,qword ptr [rsp+60h]
00000001`400014bc 33c9 xor ecx,ecx
00000001`400014be e8a3750000 call WOW64Test_x64+0x8a66 (00000001`40008a66)
00000001`400014c3 eb22 jmp WOW64Test_x64+0x14e7 (00000001`400014e7)
00000001`400014c5 488b842488000000 mov rax,qword ptr [rsp+88h]
00000001`400014cd 48890584bf0000 mov qword ptr [WOW64Test_x64+0xd458 (00000001`4000d458)],rax
00000001`400014d4 488d842488000000 lea rax,[rsp+88h]
00000001`400014dc 4883c008 add rax,8
00000001`400014e0 48890511bf0000 mov qword ptr [WOW64Test_x64+0xd3f8 (00000001`4000d3f8)],rax
00000001`400014e7 488b056abf0000 mov rax,qword ptr [WOW64Test_x64+0xd458 (00000001`4000d458)]
00000001`400014ee 488905dbbd0000 mov qword ptr [WOW64Test_x64+0xd2d0 (00000001`4000d2d0)],rax
00000001`400014f5 488b842490000000 mov rax,qword ptr [rsp+90h]
00000001`400014fd 488905dcbe0000 mov qword ptr [WOW64Test_x64+0xd3e0 (00000001`4000d3e0)],rax
00000001`40001504 c705b2bd0000090400c0 mov dword ptr [WOW64Test_x64+0xd2c0 (00000001`4000d2c0)],0C0000409h
00000001`4000150e c705acbd000001000000 mov dword ptr [WOW64Test_x64+0xd2c4 (00000001`4000d2c4)],1
00000001`40001518 488b05e9aa0000 mov rax,qword ptr [WOW64Test_x64+0xc008 (00000001`4000c008)]
00000001`4000151f 4889442468 mov qword ptr [rsp+68h],rax
00000001`40001524 488b05e5aa0000 mov rax,qword ptr [WOW64Test_x64+0xc010 (00000001`4000c010)]
00000001`4000152b 4889442470 mov qword ptr [rsp+70h],rax
00000001`40001530 ff152a7b0000 call qword ptr [WOW64Test_x64+0x9060 (00000001`40009060)]
00000001`40001536 89051cbe0000 mov dword ptr [WOW64Test_x64+0xd358 (00000001`4000d358)],eax
00000001`4000153c b901000000 mov ecx,1
00000001`40001541 e8aa290000 call WOW64Test_x64+0x3ef0 (00000001`40003ef0)
00000001`40001546 33c9 xor ecx,ecx
00000001`40001548 ff150a7b0000 call qword ptr [WOW64Test_x64+0x9058 (00000001`40009058)]
00000001`4000154e 488d0d7b7d0000 lea rcx,[WOW64Test_x64+0x92d0 (00000001`400092d0)]
00000001`40001555 ff15f57a0000 call qword ptr [WOW64Test_x64+0x9050 (00000001`40009050)]
00000001`4000155b 833df6bd000000 cmp dword ptr [WOW64Test_x64+0xd358 (00000001`4000d358)],0
00000001`40001562 750a jne WOW64Test_x64+0x156e (00000001`4000156e)
00000001`40001564 b901000000 mov ecx,1
00000001`40001569 e882290000 call WOW64Test_x64+0x3ef0 (00000001`40003ef0)
00000001`4000156e ff15d47a0000 call qword ptr [WOW64Test_x64+0x9048 (00000001`40009048)]
00000001`40001574 ba090400c0 mov edx,0C0000409h
00000001`40001579 488bc8 mov rcx,rax
00000001`4000157c ff15be7a0000 call qword ptr [WOW64Test_x64+0x9040 (00000001`40009040)]
00000001`40001582 4881c488000000 add rsp,88h
00000001`40001589 c3 ret
00000001`4000158a cc int 3
00000001`4000158b cc int 3
00000001`4000158c 4c8bdc mov r11,rsp
00000001`4000158f 49895b08 mov qword ptr [r11+8],rbx
00000001`40001593 49896b18 mov qword ptr [r11+18h],rbp
00000001`40001597 49897320 mov qword ptr [r11+20h],rsi
00000001`4000159b 49895310 mov qword ptr [r11+10h],rdx
00000001`4000159f 57 push rdi
00000001`400015a0 4154 push r12
00000001`400015a2 4155 push r13
00000001`400015a4 4156 push r14
00000001`400015a6 4157 push r15
00000001`400015a8 4883ec40 sub rsp,40h
00000001`400015ac 4d8b7908 mov r15,qword ptr [r9+8]
00000001`400015b0 4d8b31 mov r14,qword ptr [r9]
00000001`400015b3 8b4104 mov eax,dword ptr [rcx+4]
00000001`400015b6 498b7938 mov rdi,qword ptr [r9+38h]
00000001`400015ba 4d2bf7 sub r14,r15
00000001`400015bd 4d8be1 mov r12,r9
00000001`400015c0 4c8bea mov r13,rdx
00000001`400015c3 488be9 mov rbp,rcx
00000001`400015c6 a866 test al,66h
00000001`400015c8 0f85ed000000 jne WOW64Test_x64+0x16bb (00000001`400016bb)
00000001`400015ce 49637148 movsxd rsi,dword ptr [r9+48h]
00000001`400015d2 49894bc8 mov qword ptr [r11-38h],rcx
00000001`400015d6 4d8943d0 mov qword ptr [r11-30h],r8
00000001`400015da 488bc6 mov rax,rsi
00000001`400015dd 3b37 cmp esi,dword ptr [rdi]
00000001`400015df 0f8381010000 jae WOW64Test_x64+0x1766 (00000001`40001766)
00000001`400015e5 4803c0 add rax,rax
00000001`400015e8 488d5cc70c lea rbx,[rdi+rax*8+0Ch]

到现在为止,我们已经通过OllyDbg调试器看到过许多用VC编写的PE32文件的Startup代 码。但这还是第一次通过WinDbg调试器查看VC编写的PE32+文件,所以我们将Startup代码全部显示出来。与图40-4中PE32文件的Startup代码相比,它们看上去非常相似。像这样调试一般的应用程序时,由于没有符号文件(Symbol: *.pdb),代码中没有任何注释,看上去非常“荒凉”。若调试VC++文件的经验不多,在上述代码中每当遇到CALL指令时可以跟踪进人,查看代码(再次强调,我强烈建议初学者这样做,熟悉Startup代码是非常重要的)。

40.4.4 main()函数

WOW64Test_x64.exe是基于控制台的应用程序,在GetCommandLineW()函数处设置断点后,从断点处开始跟踪到main()函数调用处。首先在kernel32!GetCommandLineW()API处设置断点。

1
2
bp <address或模块名称!API名称>
这里为 bp kernel32!GetCommandLineW

接下来运行(g)调试器,调试器在GetCommandLineW()API处暂停,如图40-12所示。

然后查看栈中存储的返回地址(Return Address),如图40-13所示。

从图40-14可以看到,返回地址为00000001`40001381,跟踪转到该地址处。

地址0000000140001381 的MOV指令将GetCommandLineW() API的返回值(RAX)存储到.data节区的特定区域。接下来是多条CALL指令,调用多个函数切分获取的“command line”字符串,最终形成main()函数的argc、argv参数。0000000400013CF~0000000400013E4地址间的MOV指令用来为main()设置参数(RCX、RDX、R8寄存器)。紧接着,00000001400013EA地址处的CALL指令用来调用main()函数。下面看看存储在寄存器中的main函数的参数,如图40-15所示。

main(int argc,char* argv[])函数的第一个参数argc存储在RCX寄存器中,其值为1,表示无额外的命令参数。函数的第二个参数为argV[]数组,数组的起始地址存储在RDX寄存器中,其值为472F10,该地址中保存着数组元素argv[0]的值,是第一个命令行字符串的地址,如图40-16所示。

从图40-16中可以看到,argv[]参数(RDX)的起始地址为472F10, argv[0]=472F20,地址472F20中保存着第一个命令行字符串(C:\work\WOW64Test_x64.exe)。

可以像这样查看main()函数的argc与argv这2个参数。图40-15中R8寄存器的值00000000’00472FB0表示什么呢? main()函数的参数明明只有2个(RCX、RDX),那么这第三个参数R8寄存器中为什么会存储着值呢?下面分析一下图40-17。

从图40-17中可以看到,R8寄存器所指的是一个指针数组,数组的所有元素都指向栈区域。第一个元素所指的地址为00000000`00473120,进入该地址查看,如图40-18所示

在图40-19中可以看得更清楚,main()的第三个参数R8为系统环境变量字符串数组的地址,它不是用户编写的代码,是使用Visual C++ 2010工具编译代码时由编译器自动添加的参数。最后看一下main()函数本身的代码。

至此,我们准确找到了main()函数代码,从这里开始调试就可以了。下面请大家各自动手调试,好好练习一下。

WinDbg的基本使用方法请参考第39章。

40.5小结

本章学习了64位环境下调试PE32+文件的方法。前一章(64位计算)中我们学习了有关64位 环境中新增与改动的内容,如果完全掌握了这些内容,那么在64位环境下调试程序并没有想得那么难。由于x64、Windows OS 64位、PE32+等能够很好地向下兼容,所以如果熟悉了32位环境下的调试技术,就能快速适应64位环境,顺利调试64位程序。

WinDbg调试器的用户界面有些陌生,并且如果没有符号文件(*.pdb),代码的注释(特别是API的名称)会非常少,这给代码的阅读与分析造成了困难。要熟悉WinDbg这个调试工具,必须反复使用它,不断练习,除此之外别无他法(随着代码逆向分析水平的提高,各位调试内核驱动程序文件时会再次使用WinDbg这个调试工具)。当然,如果你还有余力,可以尝试使用IDA Pro, 它是一个非常强大的交互式反汇编工具。


第40章 64位调试
https://m0ck1ng-b1rd.github.io/1999/03/31/逆向工程核心原理/第40章 64位调试/
作者
何语灵
发布于
1999年3月31日
许可协议