2016-07-14 75 views
2

我正在学习逆向工程的基础。虽然倒车的crackme它发生在我身上看到这种模式在几乎每一个函数的开头:EBX寄存器用于内存访问的模式是什么?

0x08048766
pushl %ebp        
movl %esp, %ebp    
pushl %ebx    # because ebx is a callee-saved register 
subl $0x14,%esp  # of course $0x14 changes depending on the function 
calll 0x08048766 
addl $0x1a5f, %ebx  # also this value sometime changes depending on the function 

哪里有,不只是这一个功能:

movl 0(%esp), %ebx   
retl 

所以基本上,这是正常的,每个函数首先初始化寄存器ebpesp。然后寄存器ebx被推入堆栈,这也是为ebx完全可以理解的是一个被调用方保存寄存器,它随后用于在函数来引用一些静态数据(从.rodata),例如:

leal -0x17b7(%ebx), %eax 
movl %eax, 0(%esp) 
calll printf 

现在最有趣的(也是我不明白的)部分:如果我理解正确,ebx首先用esp(这个使用函数0x08048766)指出的值初始化,为什么?那里有什么?这不是一个未初始化的问题吗?

然后将另一个值添加到ebx。这个值代表什么?

我想更好地了解在这种情况下如何使用寄存器ebx以及如何计算它指向的地址。

您可以看看完整的程序here,但不幸的是没有任何C源代码可用。

+1

@Abhineet都能跟得上。 OP了解这些。正如你在我的回答中看到的那样,'ebx'是这个代码中的PIC寄存器,你在那个wikipedia页面上找不到任何有关PIC的信息。您的评论在这里没有用,抱歉。 –

+0

@JonathonReinhart好吧,所以我明白我做了一个无用的评论。你能否发布一些链接/参考文献来阅读关于GOT和PIC的更多信息? PIC是否与Windows中的ASLR相同?是-fPIC ==/DYNAMICBASE(VC++)? – Abhineet

+3

在Windows上不需要PIC。 PE文件是可重定位的 - 加载程序在加载时修复它们(其中包括对全局变量的内部引用)。 ['/DYNAMICBASE'](https://msdn.microsoft.com/en-us/library/ff820372.aspx)不会改变任何东西,除非在标题中设置一个标志来告诉加载器它可以随意地随机在运行时重新发布。 –

回答

7

此代码似乎已编译为-fPIC。 PIC代表“与位置无关的代码”,这意味着它可以加载到任何地址,并且仍然能够访问它的全局变量。

在这种情况下,ebx被称为PIC寄存器,它被用来指向GOT(全局偏移表)的末尾。 GOT已经(从程序的基地址*)偏移到正在使用的每个全局变量。

很多时候,了解这些事情的最好方法是自己编译一些代码,然后查看输出。当你有你的符号时,它特别容易。

让我们做一个实验:

照片。ç

int global; 

int main(void) 
{ 
    global = 4; 
    return 0; 
} 

编译

$ gcc -v 
... 
gcc version 5.3.1 20160406 (Red Hat 5.3.1-6) (GCC) 

$ gcc -m32 -Wall -Werror -fPIC -o pic pic.c 

(略)

$ readelf -S pic 
Section Headers: 
    [Nr] Name    Type   Addr  Off Size ES Flg Lk Inf Al 
    [13] .text    PROGBITS  080482f0 0002f0 000182 00 AX 0 0 16 
    [15] .rodata   PROGBITS  08048488 000488 00000c 00 A 0 0 4 
    [22] .got    PROGBITS  08049ffc 000ffc 000004 04 WA 0 0 4 
    [23] .got.plt   PROGBITS  0804a000 001000 000014 04 WA 0 0 4 
    [24] .data    PROGBITS  0804a014 001014 000004 00 WA 0 0 1 
    [25] .bss    NOBITS   0804a018 001018 000008 00 WA 0 0 4 

拆卸(Intel语法,因为AT &牛逼驱使我坚果)

$ objdump -Mintel -d --no-show-raw-insn pic 

080483eb <main>: 
80483eb: push ebp 
80483ec: mov ebp,esp 
80483ee: call 804840b <__x86.get_pc_thunk.ax> ; EAX = EIP + 5 
80483f3: add eax,0x1c0d   ; EAX = 0x804a000 (.got.plt, end of .got) 
80483f8: lea eax,[eax+0x1c]  ; EAX = 0x804a01C (.bss + 4) 

80483fe: mov DWORD PTR [eax],0x4 ; set `global` to 4 
8048404: mov eax,0x0 
8048409: pop ebp 
804840a: ret  

0804840b <__x86.get_pc_thunk.ax>: 
804840b: mov eax,DWORD PTR [esp] 
804840e: ret  
804840f: nop 

说明

在这种情况下,我的GCC决定使用eax作为PIC寄存器,而不是ebx

另外,请注意,编译器(GCC 5.3.1)在这里做了一些有趣的事情。它不是通过GOT访问变量,它主要使用GOT作为“锚点”,而是直接偏移到.bss部分中的变量。


返回代码:

pushl %ebp        
movl %esp, %ebp    
pushl %ebx    ; because ebx is a callee-saved register 
subl $0x14,%esp  ; end of typical prologue 

calll 0x08048766  ; __i686_get_pc_thunk_bx 
         ; Gets the current value of EIP after this call into EBX. 
         ; There is no other way to do this in x86 without a call 

addl $0x1a5f, %ebx  ; Add the displacement to the end of the GOT. 
         ; This displacement of course changes depending on 
         ; where the function is. 
         ; EBX now points to the end of the GOT. 

leal -0x17b7(%ebx), %eax ; EAX = EBX - 0x17b7 
movl %eax, 0(%esp)   ; Put EAX on stack (arg 0 to printf) 
          ; EAX should point to some string 
calll printf 

在你的代码也,它实际上并没有“用” GOT(否则,我们将看到第二存储器解除参考);它将其用作字符串的锚点,可能位于GOT之前的只读数据部分(.rodata)。

如果你看一下在功能0x08048766,你会看到它看起来是这样的:

mov (%esp),%eax ; Put return address (pushed onto stack by call insn) 
        ; in eax 
ret     ; Return 
+0

这是一个非常详细和明确的答案,毫无疑问,我可以找到最好的答案。 谢谢! (只是一点修正:EAX + 0x804a01C应该是EAX = 0x804a01C,我认为) – TTK

+0

@TTK不客气。感谢您指出了这一点;我修复了它。我强烈建议编写代码(比我在这里展示的更大,更复杂的程序),并将其拆解。 –

+0

@TTK另外,您可以像我一样查看节标题('readelf -S')来验证我对您的程序的分析。只有你提供的反汇编(这对你的问题是足够的,不用担心),很难说清楚。但它是有道理的,因为'printf'需要一个指向字符串的指针(不是GOT条目)。 –

相关问题