a1p*_*yte 4 x86 assembly shellcode
两年后,我又回来了。试图再次解决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),但事实并非如此。我错了吗,还是这一切似乎都错了?
但是有些事情感觉不对
对,他们是。一本书有印刷错误并不罕见。通常,当您看到某些会伤脑筋的东西时,应该寻找勘误的已发布清单。出版商的网站和作者的网站都是不错的选择。我不知道这本书的确切名称是什么,所以我自己也无法搜索,但是您应该可以轻松找到它。
当然,可能不是那么简单。信誉较差的出版商的书籍通常不会提供勘误表,而人气较低的书籍通常没有足够的读者来发现错误。您可以通过找到作者的电子邮件地址并通知他们发现的错误来发挥作用。或者,如果您不确定它们是否是错误的,请要求作者进行澄清。(您不希望作者为您提供个人教程,但是关于他们书中所发表内容的具体问题总是公平的。)
lea 0xffffffd8(%ebp), %edi,据我了解,第一部分意味着将基本指针乘以0xffffffd8,这似乎是错误的
在这种情况下,您对代码执行的理解是不正确的。我责怪这种痴迷的AT&T语法。转换为Intel语法为:
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”(或“ typo”)。
但请看下一条指令:
Run Code Online (Sandbox Code Playgroud)repz movsl %ds:(%esi), %es:(%edi)
该重复字符串操作前缀事业的字符串指令(在这种情况下,MOVSL)要重复在指定的次数ECX寄存器,从而使以前的指令是大概应该初始化ECX。初始化ECX为30h 很有意义,因为那是先前在堆栈(subl $0x30, %esp)上分配的空间量。
但是这里还有另一个错误:(REPZ或等价的REPE)前缀在MOVS指令中没有意义!该[N]Z/ [N]E通常意味着零标志用作二次终止条件,但此举不会设置任何标志,所以也没有任何意义的写REPZ MOVS!那应该是REP MOVS。
坦白说,就我而言,整个拆卸过程都是可疑的。我开始怀疑这本书是否值得写。为什么会显示未优化的汇编代码?如果您想学习汇编语言,则不想学习如何编写次优的代码。如果您想学习逆向工程,那么研究未优化的代码就没有意义,因为这不是编译器将生成的内容。漏洞利用也是一样。我想不出为什么要浪费时间在未优化的代码上的充分理由。只有一堆分散注意力的声音并没有教您任何有用的东西。
例如,您在一开始就看到未优化代码的标志:没有忽略基本指针(EBP)的初始化。
该REPZ MOVS说明(以及相关的必需说明)的目的对我来说也是一个完全的谜。我什至看不到编译器在禁用优化的情况下生成它们的原因。
我猜想作者必须关闭优化,因为否则,整个“数组”分配/初始化将被省去。不是最好的例子。
此序列也必须是一个错误:
Run Code Online (Sandbox Code Playgroud)sar $0x1f, %eax shr $0x1f, %eax
将无符号右移31是有道理的(将符号位隔离为优化的有符号除以2的一部分),但是在有符号右移之后立即执行则无意义。(作为sar %eax该优化部门的一部分的期望值稍后出现,采用的是典型的GAS格式,该格式忽略了$ 1的立即付款。)
如果所有(或什至大多数)代码都是这样,我的建议是要么放弃那本书,再去找另一本书,要么自己编译和反汇编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