两年后,我又回来了。试图再次解决shellcoders手册,但我仍然发现不一致。本书提供了以下功能:
int triangle (int width, in height){
int array[5] = {0,1,2,3,4};
int area;
area = width * height/2;
return (area);
}
以及函数的以下反汇编:
0x8048430 <triangle>: push %ebp
0x8048431 <triangle+1>: mov %esp, %ebp
0x8048433 <triangle+3>: push %edi
0x8048434 <triangle+4>: push %esi
0x8048435 <triangle+5>: sub $0x30,%esp
0x8048438 <triangle+8>: lea 0xffffffd8(%ebp), %edi
0x804843b <triangle+11>: mov $0x8049508,%esi
0x8048440 <triangle+16>: cld
0x8048441 <triangle+17>: mov $0x30,%esp
0x8048446 <triangle+22>: repz movsl %ds:( %esi), %es:( %edi)
0x8048448 <triangle+24>: mov 0x8(%ebp),%eax
0x804844b <triangle+27>: mov %eax,%edx
0x804844d <triangle+29>: imul 0xc(%ebp),%edx
0x8048451 <triangle+33>: mov %edx,%eax
0x8048453 <triangle+35>: sar $0x1f,%eax
0x8048456 <triangle+38>: shr $0x1f,%eax
0x8048459 <triangle+41>: lea (%eax, %edx, 1), %eax
0x804845c <triangle+44>: sar %eax
0x804845e <triangle+46>: mov %eax,0xffffffd4(%ebp)
0x8048461 <triangle+49>: mov 0xffffffd4(%ebp),%eax
0x8048464 <triangle+52>: mov %eax,%eax
0x8048466 <triangle+54>: add $0x30,%esp
0x8048469 <triangle+57>: pop %esi
0x804846a <triangle+58>: pop %edi
0x804846b <triangle+59> pop %ebp
0x804846c <triangle+60>: ret
出于学术原因,我试图分解并解释装配的每一行。但有些事情只是感觉不对,例如:lea 0xffffffd8(%ebp), %edi
,我的理解是第一部分意味着将基指针乘以0xffffffd8,这似乎是不正确的。另一个例子是mov $0x30, $esp
,为什么要将文字值移入堆栈指针寄存器。我能理解它是mov $0x30, (%ebp)
,但似乎并非如此。我错了,或者这一切看起来都错了?
答案 0 :(得分:4)
但有些事情只是感觉不对
是的,他们是。一本书出现印刷错误并不罕见。通常情况下,当你看到一些让你挠头的东西时,你应该寻找已发布的勘误表。出版商的网站是一个好看的地方,作者也是如此。我不知道这本书的确切名称是什么,所以我不能自己搜索,但你应该能够轻松找到它。
当然,它可能不那么简单。声誉较差的出版商的书籍通常不会提供勘误列表,而不那么受欢迎的书籍通常没有足够的读者来捕捉错误。您可以通过查找作者的电子邮件地址并通知他们您找到的错误来完成您的工作。或者,如果您不确定它们是否有错误,要求作者澄清。 (您不希望作者为您提供个人教程,但关于他们书中发布的内容的具体问题总是公平的游戏。)
lea 0xffffffd8(%ebp), %edi
,我的理解是第一部分意味着将基指针乘以0xffffffd8
,这似乎不正确
在这种情况下,您了解代码的作用是不正确的。我责备这种疯狂的AT&amp; T语法。英特尔语法的翻译是:
lea edi, DWORD [ebp + ffffffd8h]
相当于:
lea edi, DWORD [ebp - 28h]
所以这实际上相当于:
mov edi, ebp
sub edi, 28h
现在,LEA
指令可以进行乘法,这是正确的。好吧,有点儿。它可以按某些常数进行缩放,如2,4和8,它们具有与乘法相同的效果。但是这种形式不是对乘法进行编码(或者,更确切地说,它是1的缩放)。
mov $0x30, $esp
,为什么要将文字值移入堆栈指针寄存器。我能理解它是mov $0x30, (%ebp)
,但似乎并非如此。
是的,将文字移动到堆栈指针是一件非常奇怪的事情。永远不要说永远,但这应该尖叫“bug”(或“拼写错误”)。
但请看下一条指令:
repz movsl %ds:(%esi), %es:(%edi)
repeat string operation prefixes导致字符串指令(在这种情况下,MOVSL
)重复ECX
寄存器中指定的次数,以便之前的指令可能 应该初始化ECX
。将ECX
初始化为30h是有意义的,因为这是先前在堆栈上分配的空间量(subl $0x30, %esp
)。
但是这里还有另一个错误:REPZ
(或等效的REPE
)前缀对MOVS
指令没有意义! [N]Z
/ [N]E
通常意味着零标志用作辅助终止条件,但移动不设置任何标志,因此编写REPZ MOVS
没有意义!那应该是REP MOVS
。
例如,您可以在最开始看到未经优化的代码的告示标志:不要忽略基本指针(EBP
)的初始化。
REPZ MOVS
指令的目的(以及相关的必要指示)对我来说也是一个完全的谜。我甚至看不出编译器在禁用优化的情况下生成这些的原因。
我想作者已经关闭优化,但是,否则整个“数组”分配/初始化将被省略。不是最好的例子。
此序列也 是错误的:
sar $0x1f, %eax shr $0x1f, %eax
31的无符号右移是有意义的(将符号位作为优化的有符号除法的一部分隔离2),但在有符号右移后不执行此操作。 (作为此优化部门的一部分的预期sar %eax
将在稍后的版本中以省略$ 1的典型GAS格式出现。)
如果所有(或甚至大多数)代码都是这样的,我建议要么放弃那本书并找到另一本书,要么自己编译和反汇编C函数。
非破坏的C编译器将为该C函数生成以下代码:
; Load second parameter from stack into EAX.
movl 8(%esp), %eax
; Multiply that second parameter by the first parameter.
; (Could just as well have used a second movl, and then done a reg-reg imull.)
imull 4(%esp), %eax
; Make a copy of that result in EDX.
movl %eax, %edx
; Optimized signed divide-by-2:
shrl $31, %eax
addl %edx, %eax
sarl $1, %eax ; GAS encodes this as 'sarl %eax', making the $1 implicit
ret
或者,如果禁用了优化(这在不同的编译器中变化多了,那么查看未经优化的代码的另一个原因是愚蠢的,但你可以得到基本的想法):
; Set up a stack frame
pushl %ebp
movl %esp, %ebp
; Allocate space on the stack for the pointless "array" array,
; and store the values in that space.
; (Why 32 bytes instead of only 30? To keep the stack pointer aligned.)
subl $32, %esp
movl $0, -24(%ebp)
movl $1, -20(%ebp)
movl $2, -16(%ebp)
movl $3, -12(%ebp)
movl $4, -8(%ebp)
; Get first parameter from the stack.
movl 8(%ebp), %eax
; Multiply it by the second parameter.
imull 12(%ebp), %eax
; Make a copy of the result.
movl %eax, %edx
; Optimized signed divide-by-2 (some compilers will always apply this
; strength-reduction, even when optimizations are disabled; others won't
; and will go ahead and emit the IDIV instruction you might expect):
shrl $31, %edx
addl %edx, %eax
sarl $1, %eax
; Store the result in a temporary location in memory.
movl %eax, -4(%ebp)
; Then load it back into EAX so it can be returned.
movl -4(%ebp), %eax
; Tear down the stack frame and deallocate stack space.
leave
ret