如何将主函数编写为整数数组中的汇编操作码?

逆向工程 视窗 部件 C 海湾合作委员会
2021-06-30 22:07:40

我的操作系统是 Windows 7 64 位,我的处理器是在 x64 架构上运行的 Intel Core i7-4700MQ。我的程序是 32 位的。

最近,我在这里阅读了一篇文章描述了如何在 C 中将 main 函数简单地编写为整数、字符、浮点数、双精度数等的常量数组是可能和合法的。 这篇文章在编写过程中一步一步地进行在 linux 中汇编,然后将其转换为常量整数数组以打印“Hello world!\n\0”。我以前从未见过这样的程序,所以我决定制作一个可以在 Windows 上运行的程序会很酷。

在我的程序中,我显示了一个 ANSI 样式的 MessageBox,标题为“Made by CaptainObvious!\n\0”和消息“C 和程序集是给真正的人\n\0”。我的 C 程序中有可用的内联汇编代码,如下所示:

int main( )
{
    __asm__
    (
        "sub $0x10, %esp;\n"
        "movl $0x0, 0x0(%esp);\n"
        "lea message, %eax;\n"
        "movl %eax, 0x4(%esp);\n"
        "lea title, %eax;\n"
        "movl %eax, 0x8(%esp);\n"
        "movl $0x00000040, 0xc(%esp);\n"
        "call _MessageBoxA@16;\n"
        "movl $0, %eax;\n"
        "leave;\n"
        "ret;\n"

        "message: .ascii \"C and assembly are for real men.\\n\\0\";"
        "title: .ascii \"Made by CaptainObvious!\\n\\0\";"
    );

    return 0;
}

同样为了确保我可以实现文章中使用的技术,我编写了一个简单的操作码数组,用于在执行后立即返回值 10。测试成功,该数组如下所示:

const char main[] = { 0x55, 0x89, 0xe5, 0x83, 0xe4, 0xf0, 0x83, 0xec, 0x10, 0xe8, 0, 0, 0, 0, 0xb8, 10, 0, 0, 0, 0xc9, 0xc3 };

然而,当我尝试将更复杂的程序转换为整数常量数组时,我的程序无法使用 SIGILL(非法指令)执行。这是来自 gdb 的相关反汇编输出:

0x00403040  and    $0xffffff89,%ebp
0x00403043  push   %ebp
0x00403044  add    %ch,%al
0x00403046  lock in $0x83,%al ; <--- SIGILL
; ...

看到 gdb 的反汇编输出,我怀疑我的整数数组是错误的,但是虽然我发现了一些错误,但它仍然抛出与上面显示的完全相同的 SIGILL 异常。这是当前的整数数组:

const unsigned int main[] =
{
    0x5589e583, 0xe4f0e800, 0x00000083, 0xec10c704, 0x24000000,
    0x008d053d, 0x00000089, 0x4424048d, 0x055f0000, 0x00894424,
    0x08c74424, 0x0c400000, 0x00e80000, 0x0000b800, 0x000000c9,
    0xc3432061, 0x6e642061, 0x7373656d, 0x626c7920, 0x61726520,
    0x666f7220, 0x7265616c, 0x206d656e, 0x2e0a004d, 0x61646520,
    0x62792043, 0x68726973, 0x204f2754, 0x6f6f6c65, 0x210a00b8,
    0x00000000, 0xc9c39090

};

目前为了获取十六进制操作码,我使用 MinGW 包附带的 objdump.exe 程序转储了我的程序的 .text 部分。这是我的程序在转储 .text 部分时的输出:

Disassembly of section .text:

00000000 <_main>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 e4 f0                and    $0xfffffff0,%esp
   6:   e8 00 00 00 00          call   b <_main+0xb>
   b:   83 ec 10                sub    $0x10,%esp
   e:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
  15:   8d 05 3d 00 00 00       lea    0x3d,%eax
  1b:   89 44 24 04             mov    %eax,0x4(%esp)
  1f:   8d 05 5f 00 00 00       lea    0x5f,%eax
  25:   89 44 24 08             mov    %eax,0x8(%esp)
  29:   c7 44 24 0c 40 00 00    movl   $0x40,0xc(%esp)
  30:   00
  31:   e8 00 00 00 00          call   36 <_main+0x36>
  36:   b8 00 00 00 00          mov    $0x0,%eax
  3b:   c9                      leave
  3c:   c3                      ret

0000003d <message>:
  3d:   43                      inc    %ebx
  3e:   20 61 6e                and    %ah,0x6e(%ecx)
  41:   64 20 61 73             and    %ah,%fs:0x73(%ecx)
  45:   73 65                   jae    ac <title+0x4d>
  47:   6d                      insl   (%dx),%es:(%edi)
  48:   62 6c 79 20             bound  %ebp,0x20(%ecx,%edi,2)
  4c:   61                      popa
  4d:   72 65                   jb     b4 <title+0x55>
  4f:   20 66 6f                and    %ah,0x6f(%esi)
  52:   72 20                   jb     74 <title+0x15>
  54:   72 65                   jb     bb <title+0x5c>
  56:   61                      popa
  57:   6c                      insb   (%dx),%es:(%edi)
  58:   20 6d 65                and    %ch,0x65(%ebp)
  5b:   6e                      outsb  %ds:(%esi),(%dx)
  5c:   2e 0a 00                or     %cs:(%eax),%al

0000005f <title>:
  5f:   4d                      dec    %ebp
  60:   61                      popa
  61:   64 65 20 62 79          fs and %ah,%fs:%gs:0x79(%edx)
  66:   20 43 61                and    %al,0x61(%ebx)
  69:   70 74                   jo     df <title+0x80>
  6b:   61                      popa
  6c:   69 6e 4f 62 76 69 6f    imul   $0x6f697662,0x4f(%esi),%ebp
  73:   75 73                   jne    e8 <title+0x89>
  75:   21 0a                   and    %ecx,(%edx)
  77:   00 b8 00 00 00 00       add    %bh,0x0(%eax)
  7d:   c9                      leave
  7e:   c3                      ret
  7f:   90                      nop

这让我回到我的问题,我如何将程序集转换为我的主函数的整数操作码的常量数组

2个回答

您正在小端机器上执行代码。这意味着这段代码:

   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 e4 f0                and    $0xfffffff0,%esp
   6:   e8 00 00 00 00          call   b <_main+0xb>

将表示为 32 位无符号十六进制整数:

  0x83e58955  0x00e8f0e4 ...

你所拥有的是:

const unsigned int main[] =
{
    0x5589e583, 0xe4f0e800, ...

你看得到差别吗?

除了爱德华什么说一下小字节序,如果装配检查objdump的,你会看到e8 00 00 00 00在说明书0631,这两个翻译成“叫下一个指令”或“拨打ADRESS只是我自己的后面”。这是因为偏移部分 - 00000000- 是虚拟的。编译器在运行时不知道真正的函数地址,所以它只发出一个00000000地址,并在目标文件的不同部分创建重定位条目。当程序运行时,加载器会将这些重定位条目解析为真实的。(*)

但是,如果您只在程序中写入十六进制字节,则不会有重定位条目,因此程序实际上只会调用(跳转到)当前地址后面的“下一个”地址。这有一个有趣的副作用,即被调用的代码被执行,返回到被调用的地方,然后再次执行。预期的函数不会被调用。您可能应该尝试使用调试器单步执行您的程序,以查看发生了什么。

将程序集与您的 C 程序进行比较,您会发现第一个调用 (at 6) 并不直接对应于原始程序中的任何内容。这很可能设置堆栈金丝雀,您的示例并不真正需要它。我只是用nops(0x90)替换这5个字节

第二个调用位于0x31,是对 MessageBoxA 的调用。我们需要修复它以调用真正的MessageBoxA函数。不幸的是,这比你想象的要困难得多,这些天。在旧版本的 windows 中,提供MessageBoxA函数 ( user.dll)的 DLL总是在相同的地址加载,因此您只需找出它是哪个地址,然后调用该地址即可。但是,由于您的技术正是恶意软件在缓冲区溢出攻击中使用的技术,因此发明了 ASLR - 每次您的程序运行时,user.dll都会获得不同的基地址,这意味着MessageBoxA位于不同的地址,因此您不知道该调用什么.

击败 ASLR 并非易事(并非有意为之),而且显然超出了这个问题的范围。

但是,为了不让您失望太多,并为您提供一个工作示例,您可以将“字节码”放入一个函数中,并使用 main 修补该函数:

int main(void) {
    int func[]={ .... };   // your original byte code here
    int *patchpos=(int *)((char *)func+0x32); // this is the address part of the call at 0x31;
    int patchoffs=((char *)MessageBoxA)-((char *)patchpos)+4; // calculate the relative offset of MessageBoxA from the instruction behind patchpos
    *patchpos=patchoffs;   // patch the call
    (*(int (*)())func)();    // call func
}

免责声明:我现在无法测试它,也许我的偏移量之一是错误的。如果它不起作用,请在调试器中单步执行。

这样,编译器仍会为 发出重定位条目MessageBoxA,并且 C 代码可以确定真正的函数地址。如果你想摆脱它,并将字节数组保留在main.在运行时(因为 ASLR 很难)。

(*) 我在这里稍微简化了一点,并省略了 .plt 部分中的蹦床跳跃。但是这个答案已经足够长了,而且我不确定 Windows 是否以与 linux 相同的方式处理这个问题。