理解简单 C++ 程序的 RE 输出

逆向工程 部件 C++
2021-06-30 02:48:18

我正在尝试学习 RE,我决定通过逆向工程一个简单的 C++ 程序来探索这个领域,我在这里发布:

简单的 C++ 程序

#include <iostream>

int main()
{
    int value;
    std::cout << "Hello Word";
    std::cin >> value;

    return 0;
}

我使用 MSVC 编译器(VS 2017,x86 版本)编译它。我想了解它背后的汇编语言。

对于 RE,我使用 Ghidra 工具。我目前只专注于理解汇编部分,而不是反编译部分,因为目前那个对我来说意义不大,但也许如果我理解了汇编部分,我将能够解码反编译的输出。

组装输出:

                     //
                     // .text 
                     // ram: 00401000-00401f9a
                     //
                     **************************************************************
                     *                          FUNCTION                          *
                     **************************************************************
                     int __cdecl main(void)
     int               EAX:4          <RETURN>
     int               EAX:4          value                                   XREF[1]:     00401032(W)  
     char *            Stack[-0x8]:4  hello_string                            XREF[2]:     0040100d(W), 
                                                                                           0040102b(R)  
     undefined1        Stack[-0xc]:1  local_c                                 XREF[1]:     00401021(*)  
                     .text$mn                                        XREF[3]:     0040012c(*), 00400204(*), 
                     main                                                         __scrt_common_main_seh:00401500(
00401000 55              PUSH       EBP
00401001 8b ec           MOV        EBP,ESP
00401003 83 ec 08        SUB        ESP,0x8
00401006 a1 04 30        MOV        EAX,[___security_cookie]                         = BB40E64Eh
         40 00
0040100b 33 c5           XOR        EAX,EBP
0040100d 89 45 fc        MOV        dword ptr [EBP + hello_string],EAX
00401010 8b 0d 54        MOV        ECX,dword ptr [->MSVCP140.DLL::std::cout]        = 000027ec
         20 40 00
00401016 e8 25 00        CALL       std::operator<<<struct_std::char_traits<char>_>  basic_ostream<char,struct_std::c
         00 00
0040101b 8b 0d 4c        MOV        ECX,dword ptr [->MSVCP140.DLL::std::cin]         = 0000284a
         20 40 00
00401021 8d 45 f8        LEA        EAX=>local_c,[EBP + -0x8]
00401024 50              PUSH       EAX
00401025 ff 15 34        CALL       dword ptr [->MSVCP140.DLL::std::basic_istream<
         20 40 00
0040102b 8b 4d fc        MOV        ECX,dword ptr [EBP + hello_string]
0040102e 33 c0           XOR        EAX,EAX
00401030 33 cd           XOR        ECX,EBP
00401032 e8 fe 02        CALL       @__security_check_cookie@4                       undefined @__security_check_cook
         00 00
00401037 8b e5           MOV        ESP,EBP
00401039 5d              POP        EBP
0040103a c3              RET

我正在尝试逐行遵循汇编代码。我了解那里提到的寄存器和堆栈分配的一些基础知识,但是我在dword ptr MOV指令和CALL指令中迷失了方向

如果有人可以向我详细说明那里实际发生的事情,我将开始更好地理解在执行 RE 任务时如何处理汇编代码。

4个回答

前两行是函数序言,你会在几乎每个函数的开头看到它们:

PUSH       EBP
MOV        EBP,ESP

它们基本上保存当前EBP值,然后将当前值存储ESP到 中EBP,以便可以通过EBPESP在堆栈上分配内存时必须更改)访问局部变量和函数参数在这种情况下,通过堆栈传递的函数参数将被[RBP + offset]while 局部变量访问[RBP - offset]

下一行是关于内存分配 - 函数保留8字节供自己使用:

SUB        ESP,0x8

在接下来的三行中,有一个防止缓冲区溢出的保护

MOV        EAX,[___security_cookie]
XOR        EAX,EBP
MOV        dword ptr [EBP + hello_string],EAX

此处[EBP + hello_string]包含打开ASLR时的随机值在每个程序执行期间,存储在那里的值可能会有所不同,这将使潜在的攻击者难以预测它 - 如果他未能预测,稍后将在CALL @__security_check_cookie@4.

重要的是要注意,您命名的局部变量(偏移量)hello_string与“Hello Word”字符串没有任何共同点——它只是为了防止漏洞利用。

现在,让我们暂时跳过接下来的两行——我们稍后再回来讨论它们。

所以,现在我们有:

MOV        ECX,dword ptr [->MSVCP140.DLL::std::cin]
LEA        EAX=>local_c,[EBP + -0x8]
PUSH       EAX
CALL       dword ptr [->MSVCP140.DLL::std::basic_istream<

第一行将全局cin对象作为第一个参数传递给运算符函数。这是因为它是__thiscall约定(来源

是 C++ 成员函数使用的默认调用约定,这些函数不使用变量参数 [...] 并且this指针通过寄存器 ECX 传递,而不是在 x86 体系结构上的堆栈上。

然后,value传递指向局部变量的指针调用>>运算符成员函数cin

剩下的几行:

MOV        ECX,dword ptr [EBP + hello_string]
XOR        EAX,EAX
XOR        ECX,EBP
CALL       @__security_check_cookie@4       
MOV        ESP,EBP
POP        EBP

设置EAX(将包含函数返回值)为0,然后检查是否发生缓冲区溢出,然后ESP在为局部变量分配空间之前恢复该值和EBP函数开头值(EBP必须保留,否则其他函数可能会更改它然后ESP将更改为错误的值)。

有两行还没有分析:

MOV        ECX,dword ptr [->MSVCP140.DLL::std::cout]
CALL       std::operator<<<struct_std::char_tra

它们之所以有问题,有两个原因:

  1. 的第二个参数<<没有被传递(尽管它是必需的)。
  2. 正如您可能注意到的,与这种cin情况相反,正在调用非成员函数,因此第一个参数应该通过堆栈传递,而不是按照__thiscall惯例:
    1. >>for ( cin, int) 是成员函数:source
    2. <<for ( cout, const char*) 是非成员函数:source

正如您在@blabb 发布的反汇编中看到的那样,这两个参数确实是通过堆栈传递的,而在您的参数中 - 只有一个传递而不是堆栈传递。

因此,此代码似乎不起作用 - 它甚至没有引用您要打印的字符串。

我认为在这种情况下,hello_string实际上是指安全 cookie 的存储位置。

0040100d堆栈中 cookie 被保存到堆栈中。你可以在这里阅读更多

看起来您的代码有问题,操作符<< 似乎没有参数。

您可以尝试在字符串上添加 '\n' 或使用<< std::endl并查看生成的代码是否不同吗?

默认情况下,不会为所有函数启用__security_cookie
msvc vs 2017 不会为有问题的代码插入 __security_check,
无论是优化还是不优化

我不得不用#pragma 强制它

source , 用于编译和反汇编的命令行如下

来源

:/>dir /b si*
simp.cpp

:/>type simp.cpp
#include <iostream>
#pragma strict_gs_check(on) <<< had to force stricter security checks
int main() {
    int value;
    std::cout << "Hello Word";
    std::cin >> value;
    return 0;
}

\编译并链接到

:/>cl /Zi /W4 /analyze /Od /GS /EHsc simp.cpp /link /release
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27025.1 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

simp.cpp
Microsoft (R) Incremental Linker Version 14.16.27025.1
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:simp.exe
/debug
/release
simp.obj

:/>simp.exe
Hello Word

p

拆卸

:/>cdb -c "uf simp!main;q" simp.exe | awk "/Reading/,/quit/"
0:000> cdb: Reading initial command 'uf simp!main;q'
simp!main:
013a1190 55              push    ebp
013a1191 8bec            mov     ebp,esp
013a1193 83ec08          sub     esp,8
013a1196 a16c904201      mov     eax,dword ptr [simp!__security_cookie (0142906c)]
013a119b 33c5            xor     eax,ebp
013a119d 8945fc          mov     dword ptr [ebp-4],eax
013a11a0 68f0214101      push    offset simp!__xt_z+0x10 (014121f0)
013a11a5 6830ab4201      push    offset simp!std::cout (0142ab30)
013a11aa e861010000      call    simp!std::operator<<<std::char_traits<char> > (013a1310)
013a11af 83c408          add     esp,8
013a11b2 8d45f8          lea     eax,[ebp-8]
013a11b5 50              push    eax
013a11b6 b9b8aa4201      mov     ecx,offset simp!std::cin (0142aab8)
013a11bb e8b0280000      call    simp!std::basic_istream<char,std::char_traits<char> >::operator>> (013a3a70)
013a11c0 33c0            xor     eax,eax
013a11c2 8b4dfc          mov     ecx,dword ptr [ebp-4]
013a11c5 33cd            xor     ecx,ebp
013a11c7 e8e63a0200      call    simp!__security_check_cookie (013c4cb2)
013a11cc 8be5            mov     esp,ebp
013a11ce 5d              pop     ebp
013a11cf c3              ret
quit: