【问题标题】:Difficulty understanding logic in disassembled binary bomb phase 3拆解二元炸弹第三阶段逻辑难理解
【发布时间】:2020-07-19 03:37:54
【问题描述】:

我有以下来自二元炸弹实验室的汇编程序。目标是在不触发explode_bomb 函数的情况下确定运行二进制文件所需的关键字。我评论了我对该程序的程序集的分析,但我无法将所有内容拼凑在一起。

我相信我拥有我需要的所有信息,但我仍然无法看到实际的底层逻辑,因此我被卡住了。我将非常感谢任何帮助!

以下是反汇编程序本身:

0x08048c3c <+0>:     push   %edi
   0x08048c3d <+1>:     push   %esi
   0x08048c3e <+2>:     sub    $0x14,%esp
   0x08048c41 <+5>:     movl   $0x804a388,(%esp)
   0x08048c48 <+12>:    call   0x80490ab <string_length>
   0x08048c4d <+17>:    add    $0x1,%eax
   0x08048c50 <+20>:    mov    %eax,(%esp)
   0x08048c53 <+23>:    call   0x8048800 <malloc@plt>
   0x08048c58 <+28>:    mov    $0x804a388,%esi
   0x08048c5d <+33>:    mov    $0x13,%ecx
   0x08048c62 <+38>:    mov    %eax,%edi
   0x08048c64 <+40>:    rep movsl %ds:(%esi),%es:(%edi)
   0x08048c66 <+42>:    movzwl (%esi),%edx
   0x08048c69 <+45>:    mov    %dx,(%edi)
   0x08048c6c <+48>:    movzbl 0x11(%eax),%edx
   0x08048c70 <+52>:    mov    %dl,0x10(%eax)
   0x08048c73 <+55>:    mov    %eax,0x4(%esp)
   0x08048c77 <+59>:    mov    0x20(%esp),%eax
   0x08048c7b <+63>:    mov    %eax,(%esp)
   0x08048c7e <+66>:    call   0x80490ca <strings_not_equal>
   0x08048c83 <+71>:    test   %eax,%eax
   0x08048c85 <+73>:    je     0x8048c8c <phase_3+80>
   0x08048c87 <+75>:    call   0x8049363 <explode_bomb>
   0x08048c8c <+80>:    add    $0x14,%esp
   0x08048c8f <+83>:    pop    %esi
   0x08048c90 <+84>:    pop    %edi
   0x08048c91 <+85>:    ret  

以下块包含我的分析

  5 <phase_3>
  6 0x08048c3c <+0>:     push   %edi // push value in edi to stack
  7 0x08048c3d <+1>:     push   %esi // push value of esi to stack
  8 0x08048c3e <+2>:     sub    $0x14,%esp // grow stack by 0x14 (move stack ptr -0x14 bytes)
  9 
 10 0x08048c41 <+5>:     movl   $0x804a388,(%esp) // put 0x804a388 into loc esp points to
 11 
 12 0x08048c48 <+12>:    call   0x80490ab <string_length> // check string length, store in eax
 13 0x08048c4d <+17>:    add    $0x1,%eax // increment val in eax by 0x1 (str len + 1) 
 14 // at this point, eax = str_len + 1  = 77 + 1 = 78
 15 
 16 0x08048c50 <+20>:    mov    %eax,(%esp) // get val in eax and put in loc on stack
 17 //**** at this point, 0x804a388 should have a value of 78? ****
 18 
 19 0x08048c53 <+23>:    call   0x8048800 <malloc@plt> // malloc --> base ptr in eax
 20 
 21 0x08048c58 <+28>:    mov    $0x804a388,%esi // 0x804a388 in esi 
 22 0x08048c5d <+33>:    mov    $0x13,%ecx // put 0x13 in ecx (counter register)
 23 0x08048c62 <+38>:    mov    %eax,%edi // put val in eax into edi
 24 0x08048c64 <+40>:    rep movsl %ds:(%esi),%es:(%edi) // repeat 0x13 (19) times
 25 // **** populate malloced memory with first 19 (edit: 76) chars of string at 0x804a388 (this string is 77 characters long)? ****
 26 
 27 0x08048c66 <+42>:    movzwl (%esi),%edx // put val in loc esi points to into edx
***** // at this point, edx should contain the string at 0x804a388?
 28 
 29 0x08048c69 <+45>:    mov    %dx,(%edi) // put val in dx to loc edi points to
***** // not sure what effect this has or what is in edi at this point
 30 0x08048c6c <+48>:    movzbl 0x11(%eax),%edx // edx = [eax + 0x11]
 31 0x08048c70 <+52>:    mov    %dl,0x10(%eax) // [eax + 0x10] = dl
 32 0x08048c73 <+55>:    mov    %eax,0x4(%esp) // [esp + 0x4] = eax
 33 0x08048c77 <+59>:    mov    0x20(%esp),%eax // eax = [esp + 0x20]
 34 0x08048c7b <+63>:    mov    %eax,(%esp) // put val in eax into loc esp points to
***** // not sure what effect these movs have
 35 
 36 // edi --> first arg
 37 // esi --> second arg
 38 // compare value in esi to edi
 39 0x08048c7e <+66>:    call   0x80490ca <strings_not_equal> // store result in eax
 40 0x08048c83 <+71>:    test   %eax,%eax 
 41 0x08048c85 <+73>:    je     0x8048c8c <phase_3+80>
 42 0x08048c87 <+75>:    call   0x8049363 <explode_bomb>
 43 0x08048c8c <+80>:    add    $0x14,%esp
 44 0x08048c8f <+83>:    pop    %esi
 45 0x08048c90 <+84>:    pop    %edi
 46 0x08048c91 <+85>:    ret 

更新:

在调用 strings_not_equal 之前检查寄存器时,我得到以下信息:

eax            0x804d8aa        134535338
ecx            0x0      0
edx            0x76     118
ebx            0xffffd354       -11436
esp            0xffffd280       0xffffd280
ebp            0xffffd2b8       0xffffd2b8
esi            0x804a3d4        134521812
edi            0x804f744        134543172
eip            0x8048c7b        0x8048c7b <phase_3+63>
eflags         0x282    [ SF IF ]
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x0      0
gs             0x63     99

我使用 Hopper 得到以下反汇编伪代码:

我什至尝试使用在 eax 中找到的数字和之前看到的字符串作为我的关键字,但它们都不起作用。

【问题讨论】:

  • 感谢您发布评论良好的反汇编和详细说明您认为代码的作用。如果只有更多的人在发布炸弹实验室问题时会付出如此多的努力......
  • rep movsl 将 32 位长字从地址 %esi 复制到地址 %edi,每次都增加 4,次数等于 ecx。把它想象成memcpy(edi, esi, ecx*4)。请参阅 felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq(在 Intel 表示法中是 movsd)。
  • 所以不是 19 字节,而是 19*4=76 字节。
  • @NateEldredge 啊,我明白这是有道理的,因为内存位置 0x80490ab 的字符串长度为 77。非常感谢您提供的链接!
  • @fuz 谢谢你,不幸的是它还没有帮助我把所有东西拼凑起来

标签: c assembly x86 reverse-engineering att


【解决方案1】:

该函数从静态存储中将字符串的修改副本复制到分配的缓冲区中。


这看起来很奇怪。 malloc 的大小取决于strlen+1,但memcpy 的大小是编译时常量?您的反编译显然表明地址是一个字符串文字,所以看起来很好。

可能因为自定义 string_length() 函数而错过了优化,该函数可能只在另一个 .c 中定义(并且编译炸弹时没有针对跨文件内联进行链接时优化)。所以size_t len = string_length("some string literal"); 不是编译时常量,编译器发出了对它的调用,而不是能够使用字符串的已知常量长度。

但可能他们在源代码中使用了strcpy,编译器将其内联为rep movs。由于它显然是从字符串文字复制的,因此长度是编译时常量,它可以优化 strcpy 通常必须做的那部分工作。通常,如果您已经计算出长度,最好使用memcpy 而不是让strcpy 在运行中再次计算它,但在这种情况下,它实际上帮助编译器为该部分生成更好的代码,而不是它们通过string_length 的返回值到 memcpy,同样是因为 string_length 无法内联和优化。


   <+0>:     push   %edi // push value in edi to stack
   <+1>:     push   %esi // push value of esi to stack
   <+2>:     sub    $0x14,%esp // grow stack by 0x14 (move stack ptr -0x14 bytes)

这样的评论是多余的;指令本身已经说明了这一点。这是保存两个调用保留寄存器,以便函数可以在内部使用它们并在以后恢复它们。

您对sub的评论更好;是的,grow the stack 是这里的高级语义。这个函数为本地人保留了一些空间(并且函数参数用mov而不是pushed存储)。


rep movsd 复制 0x13 * 4 字节,递增 ESI 和 EDI 以指向复制区域的末尾。所以另一条movsd 指令将复制另外 4 个与前一个副本连续的字节。

代码实际上复制了另一个 2,但它没有使用movsw,而是使用movzw 字加载和mov 存储。 总共复制了 78 个字节。

  ...
      # at this point EAX = malloc return value which I'll call buf
<+28>:    mov    $0x804a388,%esi            # copy src = a string literal in .rodata?
<+33>:    mov    $0x13,%ecx
<+38>:    mov    %eax,%edi                  # copy dst = buf
<+40>:    rep movsl %ds:(%esi),%es:(%edi)   # memcpy 76 bytes and advance ESI, EDI

<+42>:    movzwl (%esi),%edx
<+45>:    mov    %dx,(%edi)        # copy another 2 bytes (not moving ESI or EDI)
 # final effect: 78-byte memcpy

在某些(但不是全部)CPU 上,只使用具有适当计数的rep movsb 或rep movsw 会很有效,但这不是编译器在这种情况下选择的。 movzx 又名 AT&T movz 是一种很好的方法来进行窄负载而不会受到部分注册惩罚。这就是编译器这样做的原因,因此他们可以编写一个完整的寄存器,即使他们只会使用存储指令读取该寄存器的低 8 位或 16 位。

在将字符串文字复制到 buf 之后,我们有一个字节加载/存储,它使用 buf 复制一个字符。 请记住,此时 EAX 仍然指向 buf,即malloc 返回值。 所以它正在制作字符串文字的修改副本。

<+48>:    movzbl 0x11(%eax),%edx
<+52>:    mov    %dl,0x10(%eax)             # buf[16] = buf[17]

也许如果源没有击败常量传播,那么编译器可能只是将最终字符串放入您可以找到它的.rodata,从而简化了这个炸弹阶段。 :P

然后它将指针存储为堆栈参数以进行字符串比较。

<+55>:    mov    %eax,0x4(%esp)               # 2nd arg slot = EAX = buf
<+59>:    mov    0x20(%esp),%eax              #  function arg = user input?
<+63>:    mov    %eax,(%esp)                  # first arg slot = our incoming stack arg
<+66>:    call   0x80490ca <strings_not_equal>

如何“作弊”:用 GDB 查看运行时结果

一些炸弹实验室只允许您在测试服务器上在线运行炸弹,该服务器会记录爆炸。你不能在 GDB 下运行它,只能使用静态反汇编(如objdump -drwC -Mintel)。所以测试服务器可以记录你有多少失败的尝试。例如就像我在 google 上找到的 CS 3330 at cs.virginia.edu,其中完整的信用需要少于 20 次爆炸。

使用 GDB 通过函数检查内存/寄存器的一部分,这比仅通过静态分析工作要容易得多,实际上,仅在最后检查单个输入的情况下使这个函数变得微不足道。例如看看其他 arg 被传递给strings_not_equal。 (特别是如果您使用 GDB 的 jump 或 set $pc = ... 命令跳过炸弹爆炸检查。)

在调用 strings_not_equal 之前设置断点或单步。 使用 p (char*)$eax 将 EAX 视为 char* 并显示(0 终止的)C 字符串从那个地址开始。此时 EAX 保存了缓冲区的地址,正如您从存储到堆栈中看到的那样。

复制/粘贴该字符串结果,您就完成了。

具有多个数字输入的其他阶段通常不那么容易使用调试器进行操作,并且至少需要一些数学运算,但链表阶段也需要您以正确的顺序获得一系列数字以进行列表遍历如果您知道如何使用调试器设置寄存器以使比较成功,那么您将变得微不足道。

【讨论】:

  • 非常感谢!我终于明白了。
【解决方案2】:

rep movsl 将 32 位长字从地址 %esi 复制到地址 %edi,每次都增加 4,次数等于 %ecx。把它想象成memcpy(edi, esi, ecx*4)。

参见https://felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq(它是英特尔表示法中的 movsd)。

所以这是复制19*4=76 字节。

【讨论】:

  • 谢谢,如果您能帮助我更好地理解它们,我还有一些其他让我有点困惑的事情(列在底部)
猜你喜欢
  • 1970-01-01
  • 2016-08-17
  • 2016-02-11
  • 2013-11-07
  • 2016-08-13
  • 2016-09-05
  • 1970-01-01
相关资源
最近更新 更多