我开始阅读《黑客》,即利用的艺术,我对有关内存检查的一些事情感到困惑。
当我反汇编 main 时,我得到了各个汇编指令所在的所有内存的输出,对吧?每条指令不一定需要将内存地址加一,这就是为什么我可能会在后面加上 <main+1> 和 <main +3> 。所以命令这个截图显示程序的前 20 条指令。每个信息包含多少字节信息?
现在我很困惑,当使用 ir eip 时,我得到了这个寄存器的位置,即 0x8048384 (所以它存储为 main 的第一条指令吗?)在屏幕截图上。它旁边的值,是它保存的值,这里我想知道,它存储的是0x00fc45c7,但是这条指令不在上面的输出中?我认为它应该指向包含程序下一条指令的内存。
现在最大的困惑来了,我可以观察存储 $eip 的内存并一次观察多个单元,这可以在这个截图。然而,在使用 x/2x 的屏幕截图中,您可以看到内存中存储了两个值,并且大小都是 4 个字节?然后使用x/12,0x8048384中突然有4个字,而其他4个字0x8048394?
我似乎只是不明白存储在内存地址中的值如何根据我使用的单位而有所不同。我还认为每个内存地址应该只包含一个字节的信息?
如果您需要对问题进行任何澄清,请发帖,英语不是我的母语,我不确定我的解释是否正确。
感谢您提前的帮助
答案1
每个信息包含多少字节信息?
从一个字节push %ebp
到几个字节的任意位置;指令没有固定长度。命令的输出例如
objdump -d a.out
可以使指令长度更清晰(并且将显示指令是什么,这可能有用):
08048400 <main>:
8048400: 55 push %ebp
8048401: 89 e5 mov %esp,%ebp
8048403: 83 ec 08 sub $0x8,%esp
8048406: 90 nop
8048407: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp)
804840e: 89 f6 mov %esi,%esi
8048410: 83 7d fc 09 cmpl $0x9,0xfffffffc(%ebp)
现在我很困惑,当使用 ir eip 时,我得到了这个寄存器的位置......
(gdb) disassemble main
Dump of assembler code for function main:
0x8048400 <main>: push %ebp
0x8048401 <main+1>: mov %esp,%ebp
0x8048403 <main+3>: sub $0x8,%esp
0x8048406 <main+6>: nop
0x8048407 <main+7>: movl $0x0,0xfffffffc(%ebp)
0x804840e <main+14>: mov %esi,%esi
0x8048410 <main+16>: cmpl $0x9,0xfffffffc(%ebp)
...
(gdb) i r eip
eip 0x8048406 0x8048406
在我的版本中,该程序eip
指向在 下停止程序nop
的命令。b main
gdb
现在最大的困惑来了,我可以观察内存
$eip
存储在哪里
(gdb) x/10b $eip
0x8048406 <main+6>: 0x90 0xc7 0x45 0xfc 0x00 0x00 0x00 0x00
0x804840e <main+14>: 0x89 0xf6
回想一下上面的输出objdump
:
8048406: 90 nop
8048407: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp)
804840e: 89 f6 mov %esi,%esi
10 字节的转储$eip
显示了接下来的三个指令:90,然后是 c7 45 fc 00 00 00 00,然后是 89 f6。
我似乎只是不明白存储在内存地址中的值如何根据我使用的单位而有所不同。我还认为每个内存地址应该只包含一个字节的信息?
gdb
允许通过不同的宽度或逻辑单元进行检查。我们可以说x/3i
显示接下来的三个指令,例如:
(gdb) x/3i $eip
0x8048406 <main+6>: nop
0x8048407 <main+7>: movl $0x0,0xfffffffc(%ebp)
0x804840e <main+14>: mov %esi,%esi
使用的最佳宽度选择将取决于具体情况。在 8 位系统上,您需要查看字节。应检查 64 位系统上的内存地址g
是否为巨型或 8 字节。更改选区的宽度可能会改变数字gdb
显示的数字,因为不同的宽度可能会导致不同的位模式,从而导致不同的数字。此外,CPU 的字节顺序可能会使事情变得复杂。
考虑:
#include <stdio.h>
char *pointer = "test";
int main(void) {
printf("%s\n", pointer);
}
如果我们在检查时选择不同的宽度,pointer
我们将从不同的位模式中得到不同的数字:
(gdb) p pointer
$1 = 0x8048488 "test"
(gdb) x/4c pointer
0x8048488 <_IO_stdin_used+4>: 116 't' 101 'e' 115 's' 116 't'
(gdb) x/4t pointer
0x8048488 <_IO_stdin_used+4>: 01110100 01100101 01110011 01110100
(gdb) x/t (int *)pointer
0x8048488 <_IO_stdin_used+4>: 01110100011100110110010101110100
另一个问题是英特尔系统是小尾数法(英特尔可能不认为这是一个问题)。因此,如果仔细观察的话,由于这种复杂性,位模式x/4t
会略有不同。x/t (int *)
我们可以指示gdb
进入大端模式,
(gdb) set endian big
The target is assumed to be big endian
(gdb) x/t (int *)pointer
0x88840408: Cannot access memory at address 0x88840408
但现在我们必须向后输入内存地址!该htonl
函数调用可以将 32 位小端值反转为大端值:
$ cfu 'printf("%d\n", htonl(0x88840408))'
134513800
然后gdb
我们可以尝试该地址:
(gdb) x/t (int *)134513800
0x8048488 <_IO_stdin_used+4>: 01110100011001010111001101110100
现在,四个字符的位模式与大端形式匹配。将这些结果放入表格可能会有所帮助:
--> big endian reads this way
t e s t
x/4t (char) 01110100 01100101 01110011 01110100
big endian 01110100 01100101 01110011 01110100
<-- yaw siht sdaer naidne elttil dna
t s e t
little endian 01110100 01110011 01100101 01110100
test
这是可以通过 来显示字符串的位模式的三种方式(多种方式中的一种)
gdb
。将显示不同的数字,具体取决于您告诉将输入分为多少位gdb
以及系统的字节顺序,所有这些都来自相同的位模式。