Skip to main content

调试与逆向

在这小节,我们将学习简单的调试以及逆向技能,这对于恶意软件开发领域有这些帮助:理解当下的恶意软件的技术、功能、原理分析恶意软件并且改进,开发漏洞利用以及 Shellcode理解安全产品的原理以规避检测等

我们将主要使用 WinDBG 作为动态调试工具,IDA 作为逆向工具。两者相辅相成,动态与静态相结合。WinDBG 可以在微软商店中获取:

image.png

IDA 可以从 https://hex-rays.com/ida-free/ 下载,拥有专业版自然更好。


WinDBG

调试器是插入在目标应用程序和 CPU 之间的计算机程序,充当类似代理的角色。使用调试器使我们能够查看应用程序的内存和执行流程并与之交互。接下来的课程内容中,我们将与用户模式交互。

CPU以二进制级别处理代码,这对人类来说很难阅读和理解,而汇编语言引入了二进制内容和编程语言之间的一对一映射。尽管汇编语言应该是人类可读的,但它仍然是一种低级语言,并且需要时间来掌握。 操作码是由 CPU 解释为特定指令的二进制序列,这在调试器中显示为十六进制值以及汇编语言的翻译。

接下来,利用 WinDbg,我们将学习如何使用断点来单步执行和控制应用程序的流程。


自定义界面

为了能有一个舒适的调试环境,我们可以对 WinDBG 的界面进行自定义,这样当我们调试应用程序的时候,可以在用户界面中查看到所需的信息,例如内存汇编代码断点信息等。

在导航栏中选中 View,我们可以添加多个窗口,这里面对我们调试过程会十分有帮助的有 WinDBG 命令 (Command)寄存器 (Registers)内存 (Memory)栈 (Stack)汇编代码 (Disassembly)

image.png点中之后,对应的窗口会浮现出来,可以使其与 WinDBG 主窗口相互独立,也可以将其嵌入至 WinDBG 主窗口,我会更推荐将它们嵌入至主窗口。不过因为主窗口空间有限,如果添加的窗口数量过多,也会影响我们对信息的提取效率,诸如模块 (Module)、断点 (Breakpoints) 等窗口我们不一定要添加到主页面,而是通过 WinDBG 的命令来查看相关信息。

下图是个人偏好的一个界面布局,你们可能注意到我添加了 2 个内存窗口,因为我们还想查看 RSP 的状态。

image.png

当我们想要调试一个进程或者应用的时候,可以选择使用 WinDBG 启动目标程序,或者附加到正常运行的进程中。

image.png

附加上之后,进程会自动被设置一个软件断点,对应的汇编指令为 int 3

image.png

我们可以执行 WinDBG 命令 g 来继续执行。

image.png

在进入对 WinDBG 基本命令的学习之前,我们还需要知道调试符号。符号 (Symbol) 文件允许 WinDbg 使用名称而不是地址来引用内部函数结构体全局变量。例如,我们想给 kernelbase.dll 中的 CreateProcessA 函数设置软件断点,我们不需要先得到载入的 kernelbase.dll 中的 CreateProcessA 函数的地址,使用名称即可,命令为 bp kernelbase!CreateProcessA。在后面,我们也可以用符号来查看一些结构体。

符号文件以 .pdb 为拓展名,当应用程序的 pdb 与 PE 文件在同一目录下,符号文件会被自动载入从而识别应用程序中的符号。

image.png

这样,我们可以使用 process_calc!Main 来定位到 process_calc.exe 程序中的 Main 函数。

image.png



伪寄存器

除了 rsp, rip 等处理器寄存器外,在 WinDBG,还支持伪寄存器。WinDbg 中的伪寄存器不是实际的处理器寄存器,而是调试器本身提供的结构,允许我们快速方便地访问某些常用信息,而无需手动计算地址或使用较长的命令序列。我们会经常用到的伪寄存器有 $peb$teb$ip ,分别代表当前进程的 PEB当前线程的 TEB,当前的指令寄存器

0:000> r $peb
$peb=0000005c12c6f000
0:000> r $teb
$teb=0000005c12c70000
0:000> r $ip
$ip=00007ffb80a4cea4



基本命令

有了对 WinDBG 的初始了解以及配置了最适合自己的界面布局后,我们来学习 WinDBG 的基本常用命令。WinDBG 内置的命令数量十分庞大,并且考虑到该小节的内容是作为恶意软件开发的前置知识,因此我们不会过于深入。接下来,我们依次介绍以下这些常用命令。


反汇编

在 WinDBG 中,使用命令 u <内存地址> 来检视给定内存地址的汇编代码。例如,我们可以查看 user32.dll 中的 MessageBoxA 函数的汇编实现:

0:000> u user32!messageboxa
USER32!MessageBoxA:
00007ffb`7e9a7a90 4883ec38        sub     rsp,38h
00007ffb`7e9a7a94 4533db          xor     r11d,r11d
00007ffb`7e9a7a97 44391dcaf70300  cmp     dword ptr [USER32!gfEMIEnable (00007ffb`7e9e7268)],r11d
00007ffb`7e9a7a9e 742e            je      USER32!MessageBoxA+0x3e (00007ffb`7e9a7ace)
00007ffb`7e9a7aa0 65488b042530000000 mov   rax,qword ptr gs:[30h]
00007ffb`7e9a7aa9 4c8b5048        mov     r10,qword ptr [rax+48h]
00007ffb`7e9a7aad 33c0            xor     eax,eax

对于内存地址,可以是符号、内存地址、寄存器的形式,只要是合法的内存地址。可以通过 l* 来指定显示的行数。

0:000> u 00007ffb`7e9a7a90 l3
USER32!MessageBoxA:
00007ffb`7e9a7a90 4883ec38        sub     rsp,38h
00007ffb`7e9a7a94 4533db          xor     r11d,r11d
00007ffb`7e9a7a97 44391dcaf70300  cmp     dword ptr [USER32!gfEMIEnable (00007ffb`7e9e7268)],r11d


读取内存

我们可以使用命令 d* <内存地址> 来读取给定内存地址,星号可以是不同的数据类型,例如 byteword,dword 等。我们可以在微软文档 https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/d--da--db--dc--dd--dd--df--dp--dq--du--dw--dw--dyb--dyd--display-memor 中了解更加详细的用法。

0:000> dd rip
00007ffb`80a4cea4  4800ebcc c338c483 cccccccc cccccccc
00007ffb`80a4ceb4  245c8948 74894810 57551824 8d485641
00007ffb`80a4cec4  ff0024ac 8148ffff 000200ec 058b4800
00007ffb`80a4ced4  000bf648 48c43348 00f08589 8b4c0000
00007ffb`80a4cee4  0bc32705 058d4800 0005afe0 8948ff33
00007ffb`80a4cef4  c7502444 16482444 48001800 7024448d
00007ffb`80a4cf04  24448948 f18b4868 602444c7 01000000
00007ffb`80a4cf14  0100be41 89660000 4d70247c 2b74c085
0:000> db rip
00007ffb`80a4cea4  cc eb 00 48 83 c4 38 c3-cc cc cc cc cc cc cc cc  ...H..8.........
00007ffb`80a4ceb4  48 89 5c 24 10 48 89 74-24 18 55 57 41 56 48 8d  H.\$.H.t$.UWAVH.
00007ffb`80a4cec4  ac 24 00 ff ff ff 48 81-ec 00 02 00 00 48 8b 05  .$....H......H..
00007ffb`80a4ced4  48 f6 0b 00 48 33 c4 48-89 85 f0 00 00 00 4c 8b  H...H3.H......L.
00007ffb`80a4cee4  05 27 c3 0b 00 48 8d 05-e0 af 05 00 33 ff 48 89  .'...H......3.H.
00007ffb`80a4cef4  44 24 50 c7 44 24 48 16-00 18 00 48 8d 44 24 70  D$P.D$H....H.D$p
00007ffb`80a4cf04  48 89 44 24 68 48 8b f1-c7 44 24 60 00 00 00 01  H.D$hH...D$`....
00007ffb`80a4cf14  41 be 00 01 00 00 66 89-7c 24 70 4d 85 c0 74 2b  A.....f.|$pM..t+
0:000> dw rip
00007ffb`80a4cea4  ebcc 4800 c483 c338 cccc cccc cccc cccc
00007ffb`80a4ceb4  8948 245c 4810 7489 1824 5755 5641 8d48
00007ffb`80a4cec4  24ac ff00 ffff 8148 00ec 0002 4800 058b
00007ffb`80a4ced4  f648 000b 3348 48c4 8589 00f0 0000 8b4c
00007ffb`80a4cee4  2705 0bc3 4800 058d afe0 0005 ff33 8948
00007ffb`80a4cef4  2444 c750 2444 1648 1800 4800 448d 7024
00007ffb`80a4cf04  8948 2444 4868 f18b 44c7 6024 0000 0100
00007ffb`80a4cf14  be41 0100 0000 8966 247c 4d70 c085 2b74


读取结构体

我们可以使用 dt <结构体名称> 命令来显示结构体的结构,当然了,读取结构体需要符号文件的载入。在之前的小节,我们讲了 PEB 和 TEB,那么我们在 WinDBG 中查看一下 PEB 的结构吧。其中,使用 -r 选项可以递归地展示成员,因为结构体的成员也可能是结构体。

0:000> dt ntdll!_peb
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 BitField         : UChar
   +0x003 ImageUsesLargePages : Pos 0, 1 Bit
   +0x003 IsProtectedProcess : Pos 1, 1 Bit
   +0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
   +0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
   +0x003 IsPackagedProcess : Pos 4, 1 Bit
   +0x003 IsAppContainer   : Pos 5, 1 Bit
   +0x003 IsProtectedProcessLight : Pos 6, 1 Bit
   +0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
   +0x004 Padding0         : [4] UChar
   +0x008 Mutant           : Ptr64 Void
   +0x010 ImageBaseAddress : Ptr64 Void
   +0x018 Ldr              : Ptr64 _PEB_LDR_DATA
   +0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS
............
   +0x7ac CloudFileDiagFlags : Uint4B
   +0x7b0 PlaceholderCompatibilityMode : Char
   +0x7b1 PlaceholderCompatibilityModeReserved : [7] Char
   +0x7b8 LeapSecondData   : Ptr64 _LEAP_SECOND_DATA
   +0x7c0 LeapSecondFlags  : Uint4B
   +0x7c0 SixtySecondEnabled : Pos 0, 1 Bit
   +0x7c0 Reserved         : Pos 1, 31 Bits
   +0x7c4 NtGlobalFlag2    : Uint4B
   +0x7c8 ExtendedFeatureDisableMask : Uint8B
0:000> dt -r ntdll!_peb
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 BitField         : UChar
   +0x003 ImageUsesLargePages : Pos 0, 1 Bit
   +0x003 IsProtectedProcess : Pos 1, 1 Bit
   +0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
   +0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
   +0x003 IsPackagedProcess : Pos 4, 1 Bit
   +0x003 IsAppContainer   : Pos 5, 1 Bit
   +0x003 IsProtectedProcessLight : Pos 6, 1 Bit
   +0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
   +0x004 Padding0         : [4] UChar
   +0x008 Mutant           : Ptr64 Void
   +0x010 ImageBaseAddress : Ptr64 Void
   +0x018 Ldr              : Ptr64 _PEB_LDR_DATA
      +0x000 Length           : Uint4B
      +0x004 Initialized      : UChar
      +0x008 SsHandle         : Ptr64 Void
      +0x010 InLoadOrderModuleList : _LIST_ENTRY
         +0x000 Flink            : Ptr64 _LIST_ENTRY
         +0x008 Blink            : Ptr64 _LIST_ENTRY
      +0x020 InMemoryOrderModuleList : _LIST_ENTRY
         +0x000 Flink            : Ptr64 _LIST_ENTRY
         +0x008 Blink            : Ptr64 _LIST_ENTRY
      +0x030 InInitializationOrderModuleList : _LIST_ENTRY
         +0x000 Flink            : Ptr64 _LIST_ENTRY
         +0x008 Blink            : Ptr64 _LIST_ENTRY
      +0x040 EntryInProgress  : Ptr64 Void
      +0x048 ShutdownInProgress : UChar
      +0x050 ShutdownThreadId : Ptr64 Void
............
   +0x34e OemCodePage      : Uint2B
   +0x350 UseCaseMapping   : Uint2B
   +0x352 UnusedNlsField   : Uint2B
   +0x358 WerRegistrationData : Ptr64 Void
   +0x360 WerShipAssertPtr : Ptr64 Void
   +0x368 EcCodeBitMap     : Ptr64 Void
   +0x370 pImageHeaderHash : Ptr64 Void
   +0x378 TracingFlags     : Uint4B
   +0x378 HeapTracingEnabled : Pos 0, 1 Bit
   +0x378 CritSecTracingEnabled : Pos 1, 1 Bit
   +0x378 LibLoaderTracingEnabled : Pos 2, 1 Bit
   +0x378 SpareTracingBits : Pos 3, 29 Bits
   +0x37c Padding6         : [4] UChar
   +0x380 CsrServerReadOnlySharedMemoryBase : Uint8B
   +0x388 TppWorkerpListLock : Uint8B
   +0x390 TppWorkerpList   : _LIST_ENTRY
      +0x000 Flink            : Ptr64 _LIST_ENTRY
         +0x000 Flink            : Ptr64 _LIST_ENTRY
         +0x008 Blink            : Ptr64 _LIST_ENTRY
      +0x008 Blink            : Ptr64 _LIST_ENTRY
         +0x000 Flink            : Ptr64 _LIST_ENTRY
         +0x008 Blink            : Ptr64 _LIST_ENTRY
   +0x3a0 WaitOnAddressHashTable : [128] Ptr64 Void
   +0x7a0 TelemetryCoverageHeader : Ptr64 Void
   +0x7a8 CloudFileFlags   : Uint4B
   +0x7ac CloudFileDiagFlags : Uint4B
   +0x7b0 PlaceholderCompatibilityMode : Char
   +0x7b1 PlaceholderCompatibilityModeReserved : [7] Char
   +0x7b8 LeapSecondData   : Ptr64 _LEAP_SECOND_DATA
      +0x000 Enabled          : UChar
      +0x004 Count            : Uint4B
      +0x008 Data             : [1] _LARGE_INTEGER
         +0x000 LowPart          : Uint4B
         +0x004 HighPart         : Int4B
         +0x000 u                : <unnamed-tag>
         +0x000 QuadPart         : Int8B
   +0x7c0 LeapSecondFlags  : Uint4B
   +0x7c0 SixtySecondEnabled : Pos 0, 1 Bit
   +0x7c0 Reserved         : Pos 1, 31 Bits
   +0x7c4 NtGlobalFlag2    : Uint4B
   +0x7c8 ExtendedFeatureDisableMask : Uint8B

我们还可以使用 WinDBG 检视给定内存区域的结构体数据、查看结构体中的成员、显示结构体的尺寸。

0:000> dt ntdll!_peb @$peb
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   +0x002 BeingDebugged    : 0x1 ''
   +0x003 BitField         : 0x4 ''
   +0x003 ImageUsesLargePages : 0y0
   +0x003 IsProtectedProcess : 0y0
   +0x003 IsImageDynamicallyRelocated : 0y1
   +0x003 SkipPatchingUser32Forwarders : 0y0
   +0x003 IsPackagedProcess : 0y0
   +0x003 IsAppContainer   : 0y0
   +0x003 IsProtectedProcessLight : 0y0
   +0x003 IsLongPathAwareProcess : 0y0
   +0x004 Padding0         : [4]  ""
   +0x008 Mutant           : 0xffffffff`ffffffff Void
   +0x010 ImageBaseAddress : 0x00007ff6`4ba90000 Void
   +0x018 Ldr              : 0x00007ffb`80af4380 _PEB_LDR_DATA
   +0x020 ProcessParameters : 0x00000200`bdb86990 _RTL_USER_PROCESS_PARAMETERS
............
   +0x7c4 NtGlobalFlag2    : 0
   +0x7c8 ExtendedFeatureDisableMask : 0
0:000> dt ntdll!_peb @$peb ProcessParameters
   +0x020 ProcessParameters : 0x00000200`bdb86990 _RTL_USER_PROCESS_PARAMETERS
0:000> ?? sizeof(ntdll!_PEB)
unsigned int64 0x7d0



修改和写入内存

我们可以使用命令 e* 对给定内存地址进行数据写入或修改,星号为数据的类型。

0:000> dd rsp l1
0000005c`12a9ef80  00000000
0:000> ed rsp 41414141
0:000> dd rsp l1
0000005c`12a9ef80  41414141
0:000> da rsp
0000005c`12a9ef80  "AAAA"
0:000> ea rsp "BBBB"
0:000> da rsp
0000005c`12a9ef80  "BBBB"


搜索内存空间

当我们想在内存空间里搜索特定的字节序列或者字符串,可以使用 s 命令。我们可以指定不同的数据类型、内存范围、要搜索的内容。例如,在 x64 下,在所有用户态内存空间中搜索字符串 "dl3r",命令语句如下:

s -a 0 L?0x7fffffffffffffff "dl3r"

image.png

因为内存空间十分庞大,我们最好能在更精确的范围中进行搜索。


检视寄存器

我们可以使用命令 r 来查看所有寄存器,或者 r <寄存器> 查看单个寄存器。伪寄存器也可以用 r 命令来查看。

2:008> r
rax=0000000000000000 rbx=00007ffb80abcc50 rcx=00007ffb80a0f0f4
rdx=0000000000000000 rsi=000000b6da72e000 rdi=00007ffb80aa6c08
rip=00007ffb80a4cea4 rsp=000000b6da4ceda0 rbp=0000000000000000
 r8=000000b6da4ced98  r9=0000000000000000 r10=0000000000000000
r11=0000000000000246 r12=0000000000000040 r13=0000000000000001
r14=000001bf3e860000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!LdrpDoDebuggerBreak+0x30:
00007ffb`80a4cea4 cc              int     3
2:008> r rip
rip=00007ffb80a4cea4
2:008> r $peb
$peb=000000b6da72e000


计算器

WinDBG 的命令 ? <表达式> 可用作计算器,实现算数运算进制转换等任务。

image.png

命令 .formats <数据> 可将给定数值转换为不同数据类型:

image.png


列举模块

我们可以使用命令 lm 来列举当前进程载入的所有模块,或者检视指定的模块。使用命令 x 来查看模块中的符号

2:008> lm
start             end                 module name
00007ff6`4ba90000 00007ff6`4baea000   notepad    (pdb symbols)          C:\ProgramData\Dbg\sym\notepad.pdb\C694C0AA7279CC672966901283BF50541\notepad.pdb
00007ffb`6c470000 00007ffb`6c6fe000   COMCTL32   (deferred)             
00007ffb`7dd70000 00007ffb`7de0a000   msvcp_win   (deferred)             
00007ffb`7de10000 00007ffb`7df21000   ucrtbase   (deferred)             
00007ffb`7dff0000 00007ffb`7e016000   win32u     (deferred)             
00007ffb`7e020000 00007ffb`7e139000   gdi32full   (deferred)             
00007ffb`7e3a0000 00007ffb`7e743000   KERNELBASE   (pdb symbols)          C:\ProgramData\Dbg\sym\kernelbase.pdb\69B5D8B50C7EA66AAE7105C36531C37F1\kernelbase.pdb
00007ffb`7e930000 00007ffb`7eadb000   USER32     (deferred)             
00007ffb`7eae0000 00007ffb`7eb87000   msvcrt     (deferred)             
00007ffb`7fbb0000 00007ffb`7fc54000   sechost    (deferred)             
00007ffb`7fd40000 00007ffb`7fd69000   GDI32      (deferred)             
00007ffb`7fd70000 00007ffb`7fe32000   KERNEL32   (deferred)             
00007ffb`7ff70000 00007ffb`80061000   shcore     (deferred)             
00007ffb`80180000 00007ffb`8022e000   advapi32   (deferred)             
00007ffb`80230000 00007ffb`80347000   RPCRT4     (deferred)             
00007ffb`80510000 00007ffb`80899000   combase    (deferred)             
00007ffb`80970000 00007ffb`80b84000   ntdll      (pdb symbols)          C:\ProgramData\Dbg\sym\ntdll.pdb\ACBBF75A6C22094871DD84500F4F58F91\ntdll.pdb
2:008> lm m kernel*
Browse full module list
start             end                 module name
00007ffb`7e3a0000 00007ffb`7e743000   KERNELBASE   (pdb symbols)          C:\ProgramData\Dbg\sym\kernelbase.pdb\69B5D8B50C7EA66AAE7105C36531C37F1\kernelbase.pdb
00007ffb`7fd70000 00007ffb`7fe32000   KERNEL32   (deferred)             
2:008> x kernelbase!CreateProcess*
00007ffb`7e465717 KERNELBASE!CreateProcessInternalA$filt$1 (void)
00007ffb`7e4656f7 KERNELBASE!CreateProcessInternalA$filt$0 (void)
00007ffb`7e426e2c KERNELBASE!CreateProcessExtensions::ReleaseAppXContext (void)
00007ffb`7e3e0230 KERNELBASE!CreateProcessInternalW (void)
00007ffb`7e425840 KERNELBASE!CreateProcessInternalA (void)
00007ffb`7e462f6b KERNELBASE!CreateProcessInternalW$filt$1 (void)
00007ffb`7e4ffb20 KERNELBASE!CreateProcessWithTokenW (void)
00007ffb`7e462f91 KERNELBASE!CreateProcessInternalW$fin$2 (void)
00007ffb`7e4ffa90 KERNELBASE!CreateProcessAsUserA (void)
00007ffb`7e462f05 KERNELBASE!CreateProcessInternalW$fin$0 (void)
00007ffb`7e3e98e4 KERNELBASE!CreateProcessExtensions::PreCreationExtension (void)
00007ffb`7e4ffac0 KERNELBASE!CreateProcessAsUserW (void)
00007ffb`7e4ffaf0 KERNELBASE!CreateProcessWithLogonW (void)
00007ffb`7e465737 KERNELBASE!CreateProcessInternalA$fin$2 (void)
00007ffb`7e4d9880 KERNELBASE!CreateProcessExtensions::ErrorContext::LogError (public: void __cdecl CreateProcessExtensions::ErrorContext::LogError(long,struct Common::COMMON_STRING *))
00007ffb`7e4d9424 KERNELBASE!CreateProcessExtensions::CreateSharedLocalFolder (public: static long __cdecl CreateProcessExtensions::CreateSharedLocalFolder(struct Common::COMMON_STRING const &))
00007ffb`7e4f7ad0 KERNELBASE!CreateProcessAsUserA (CreateProcessAsUserA)
00007ffb`7e4257c0 KERNELBASE!CreateProcessA (CreateProcessA)
00007ffb`7e4231b0 KERNELBASE!CreateProcessAsUserW (CreateProcessAsUserW)
00007ffb`7e41fe80 KERNELBASE!CreateProcessW (CreateProcessW)



调用栈

有以下这么一个程序,执行了弹出 calc.exe 的 Shellcode。在 main 函数中,调用了 calc_exec 函数,而在 calc_exec 函数中,调用了数个 Windows API 来实现 Shellcode 执行。


#include <windows.h>
#include <stdlib.h>
#include <iostream>


unsigned char shellcode[] = {
  0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51,
  0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52,
  0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72,
  0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
  0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
  0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b,
  0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
  0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44,
  0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41,
  0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
  0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1,
  0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44,
  0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44,
  0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01,
  0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
  0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41,
  0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48,
  0xba, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d,
  0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5,
  0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
  0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0,
  0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89,
  0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};

void calc_exec()
{
    int length = sizeof(shellcode);
    void* exec = VirtualAlloc(0, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    RtlMoveMemory(exec, shellcode, length);
    HANDLE th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec, 0, 0, 0);
    WaitForSingleObject(th, 0xFFFFFFFF);
}

int main() {
    calc_exec();

    return 0;
}


在程序执行过程中,如果有地方报错了,我们可以根据函数的调用次序来追踪问题。例如,当程序执行到 WaitForSingleObject 处的时候,使用 WinDBG 命令 k 或者查看 WinDBG 栈窗口,我们可以清晰地看到调用次序:

image.png

image.png

对于调用栈的检视也是检测恶意软件行为的方式之一,尤其是用于检测 syscall 的调用。因为我们之前说过,用户态应用程序很少会直接使用 syscall,一般是 WinAPI -> NTAPI -> 内核。恶意软件开发者会通过 syscall 的调用绕过安全产品的检视,但如果检视调用栈,会发现端倪:NTAPI 的前一链并非对应的 WINAPI。



TEB 与 PEB

查看当前线程的 TEB/PEB,既可以执行命令 !teb/!peb,也可以使用 dt 命令查看。

1:001> !teb
TEB at 000000d59498e000
    ExceptionList:        0000000000000000
    StackBase:            000000d594e00000
    StackLimit:           000000d594dfc000
    SubSystemTib:         0000000000000000
    FiberData:            0000000000001e00
    ArbitraryUserPointer: 0000000000000000
    Self:                 000000d59498e000
    EnvironmentPointer:   0000000000000000
    ClientId:             0000000000007ad4 . 000000000000af5c
    RpcHandle:            0000000000000000
    Tls Storage:          0000022969a05290
    PEB Address:          000000d59498d000
    LastErrorValue:       0
    LastStatusValue:      c000000d
    Count Owned Locks:    0
    HardErrorMode:        0
1:001> dt ntdll!_TEB @$teb
   +0x000 NtTib            : _NT_TIB
   +0x038 EnvironmentPointer : (null) 
   +0x040 ClientId         : _CLIENT_ID
   +0x050 ActiveRpcHandle  : (null) 
   +0x058 ThreadLocalStoragePointer : 0x00000229`69a05290 Void
   +0x060 ProcessEnvironmentBlock : 0x000000d5`9498d000 _PEB
............
   +0x1838 LastSleepCounter : 0
   +0x1840 SpinCallCount    : 0
   +0x1844 Padding8         : [4]  ""
   +0x1848 ExtendedFeatureDisableMask : 0

使用 !teb 命令,我们可以更加容易看到当前线程的栈的区间



内存状态

命令 !address <内存地址> !vprot <内存地址> 可以显示有关给定内存地址的相关信息。!address 命令为我们提供有关特定内存地址或范围的详细信息,包括内存区域的类型 (堆、堆栈、映像等)、区域的大小内存权限 (读、写、执行权限) 以及内存的状态 (提交、保留、空闲)。 该命令显示的信息更加全面。

如果我们想快速地查看特定内存的权限和状态,!vprot 命令则更加直接和方便。

0:004> !address kernelbase!createprocessa

Usage:                  Image
Base Address:           00007ffb`7e3a1000
End Address:            00007ffb`7e530000
Region Size:            00000000`0018f000 (   1.559 MB)
State:                  00001000          MEM_COMMIT
Protect:                00000020          PAGE_EXECUTE_READ
Type:                   01000000          MEM_IMAGE
Allocation Base:        00007ffb`7e3a0000
Allocation Protect:     00000080          PAGE_EXECUTE_WRITECOPY
Image Path:             C:\Windows\System32\KERNELBASE.dll
Module Name:            KERNELBASE
Loaded Image Name:      C:\Windows\System32\KERNELBASE.dll
Mapped Image Name:      
More info:              lmv m KERNELBASE
More info:              !lmi KERNELBASE
More info:              ln 0x7ffb7e4257c0
More info:              !dh 0x7ffb7e3a0000


Content source: 1 (target), length: 10a840
0:004> !vprot kernelbase!createprocessa
BaseAddress:       00007ffb7e425000
AllocationBase:    00007ffb7e3a0000
AllocationProtect: 00000080  PAGE_EXECUTE_WRITECOPY
RegionSize:        000000000010b000
State:             00001000  MEM_COMMIT
Protect:           00000020  PAGE_EXECUTE_READ
Type:              01000000  MEM_IMAGE



软件断点

断点用于暂停程序的执行,当在特定地址设置软件断点时,调试器会用 int 3 指令替换该地址处的指令,该指令在执行时会导致执行暂停。调试器还记录它替换的指令,并在到达断点时恢复原始指令。在 WinDBG 中,我们可以使用命令 bp <地址> 来在特定内存地址处设置软件断点,当到达断点处,程序会被暂停,我们从而可以检视这个时刻的程序运行状态。

image.png

此外,命令 bl 用于罗列所有断点,bc 用于清除断点,bd 用于暂时禁用断点,而 be 用于重新启用断点。

image.png

WinDBG 也提供了查看断点的窗口:

image.png

不过,有时候应用程序不会在运行时就自动载入所有的模块,而是在后续运行中载入,考虑如下代码:

#include <iostream>
#include <windows.h>

typedef void (*calc_export)();

void run()
{
    HMODULE hModule = LoadLibraryA("D:\\tooling\\dllcpp\\x64\\Release\\dllcpp.dll");
    calc_export calc_ptr = (calc_export)GetProcAddress(hModule, "calc_export");
    calc_ptr();
}


int main()
{
    run();
}

因此,如果我们试图给 dllcpp!calc_export 设置断点,我们会被告知无法解析符号。对此,我们可以改用 bu 命令,表示给尚未解析的符号设置断点。

0:000> lm
start             end                 module name
00007ff6`68720000 00007ff6`68727000   dll_load C (private pdb symbols)  C:\ProgramData\Dbg\sym\dll_load.pdb\105D812B10354E099F90E97E006DF74A9\dll_load.pdb
00007ffb`63eb0000 00007ffb`63ecb000   VCRUNTIME140   (deferred)             
00007ffb`7de10000 00007ffb`7df21000   ucrtbase   (deferred)             
00007ffb`7e3a0000 00007ffb`7e743000   KERNELBASE   (deferred)             
00007ffb`7fd70000 00007ffb`7fe32000   KERNEL32   (deferred)             
00007ffb`80970000 00007ffb`80b84000   ntdll      (pdb symbols)          C:\ProgramData\Dbg\sym\ntdll.pdb\ACBBF75A6C22094871DD84500F4F58F91\ntdll.pdb
0:000> bp dllcpp!calc_export
Bp expression 'dllcpp!calc_export' could not be resolved, adding deferred bp
0:000> bc *
0:000> bu dllcpp!calc_export
0:000> g
*** WARNING: Unable to verify checksum for D:\tooling\dllcpp\x64\Release\dllcpp.dll
ModLoad: 00007ffb`13450000 00007ffb`13457000   D:\tooling\dllcpp\x64\Release\dllcpp.dll
Breakpoint 0 hit
dllcpp!calc_export:
00007ffb`13451000 4053            push    rbx


单步执行

在调试过程中,我们除了使用 g 来继续执行外,还可以单次执行下一条执行。那么,如果当前落入到调用函数的这条指令,下一步究竟是执行调用函数的指令的下一条指令,还是函数内的第一条指令呢?这就是命令 p 和命令 t 的区别

编译如下代码,并用 WinDBG 执行。

#include <iostream>
#include <windows.h>



void fun(int num)
{
    printf("In function fun\n");
    if (num == 2)
    {
        printf("num = 2!\n");
    }
}


int main()
{
    int a = 1;
    int num = 2;
    if (a < 2)
    {
        fun(num);
    }

}

在编译时,我们需要禁用优化,因为我们使用了一些常数,编译器会简化程序流程以提升性能

image.png

反汇编后的主函数如下所示,我们单次执行到 call step!fun 指令。

image.png

使用命令 p,会执行调用函数的指令的下一条指令 xor eax, eax

image.png

而如果使用命令 t,会进入函数 fun 并且到达函数内的第 1 条指令 mov  [rsp+8], ecx

image.png

当下一条指令并不是调用函数的指令,那么 p 与 t 效果相同。


执行至

我们已经知道了 p 的作用是执行下一条指令,实际上我们还可以指定调试器运行至下一个分支,或下一个返回指令

使用 ph 指令,可以执行至下一个分支,如下图所示,执行到了 jge step!main+0x24 指令处。

image.png

pt 指令,可以执行至下一个返回指令,即 ret 指令处。

image.png


解析 PE

我们还可以使用 WinDBG 在内存中解析 PE 文件。PE 文件被映射到内存中以被执行,这是一个将文件的特定元素逐字节复制到内存中的过程,PE 格式包含了加载程序用来完成此操作的信息。

例如,可选头中的 ImageBase 包含映射在内存中的起始地址,SectionAlignment 包含内存中各节的对齐系数,ImageSize 包含整个映像所需的内存量等。可选头还包含数据目录,有着诸多重要数据结构的 RVA, 使得它们可以在内存中被快速定位到。同样,节头也包含重要信息,例如 VirtualSize 包含内存中该节的大小,VirtualAddress 表示其 RVA。

PE 格式的数据结构在磁盘上与在内存中基本相同,但也存在一些例外以及造成的重要差异。造成这些差异的原因有几个,第 1 个是当文件在磁盘和内存中时不同的对其系数,这意味着相同的数据可能位于磁盘和内存中的不同偏移处。第 2 个原因是并非文件的所有元素都被映射到内存。例如,调试信息可能就不会被映射到内存。此外,个别字段由加载程序更新,例如 IAT 的条目。尽管一些元素在内存中的偏移量可能与在磁盘上的偏移量不完全相同,但这些元素的顺序是一致的。


PE 头

载入一我们在之前内容中编译的文件,查看导入的模块列表:

image.png

使用命令 dt ntdll!_IMAGE_DOS_HEADER <内存地址> 检视主程序的 NTDOS

0:000> dt ntdll!_IMAGE_DOS_HEADER 00007ff7`cc8b0000
   +0x000 e_magic          : 0x5a4d
   +0x002 e_cblp           : 0x90
   +0x004 e_cp             : 3
   +0x006 e_crlc           : 0
   +0x008 e_cparhdr        : 4
   +0x00a e_minalloc       : 0
   +0x00c e_maxalloc       : 0xffff
   +0x00e e_ss             : 0
   +0x010 e_sp             : 0xb8
   +0x012 e_csum           : 0
   +0x014 e_ip             : 0
   +0x016 e_cs             : 0
   +0x018 e_lfarlc         : 0x40
   +0x01a e_ovno           : 0
   +0x01c e_res            : [4] 0
   +0x024 e_oemid          : 0
   +0x026 e_oeminfo        : 0
   +0x028 e_res2           : [10] 0
   +0x03c e_lfanew         : 0n240

0x3c 处的 e_lfanew 值为十进制的 240,2400xf0

image.png

 

 

PE 节


导入与导出
















IDA