标 题: 谁动了我的EIP 作 者: riverfor <mailto: [email protected]> 日 期: 2005-1-22 关键词: stack, shellcode 内 容;

写在前面的前面
  我是一个Linux fan, 本文所讲述的系统为X86架构上的Linux, 我用的linux为Redhat ES 和 knoppix, 使用的gcc都是Version3.2, 本文的例子都是基于此. 同时,我又是一个初学者,所以本文的思想都体现的是我对我所找到的相关文档的理解, 写下这片文章的目的除了以文档的形式总结一下自己所学的东西,同时在看国内前辈的文章时,感觉到前辈们虽然很理解Stack overflow,但是没有全面地讲述出来,我才艺不精,但自认文笔清晰,谨以此文献给广大Linux初级爱好者,也感谢高手们斧正。
  本文的例子中,注释带[paltform]表示不同的Linux发行版或编译器,可能需要改变

** 简介 **
  1, 关于EIP: 存放进程下一步指令的地址. 

/*
* 谁动了我的EIP: DEMO 1
* 输出结果:  x = 0
*/
void ch_eip() {
    int *ret;
    ret = (int *)&ret + 2;      // [paltform]
    (*ret) += 7;              // [paltform]
}
int main() {
    int x = 0;
    ch_eip();
    x = 1;
    print("x = %d\n", x);
}
  ch_eip()被调用时,系统把ch_eip()调用返回后将要执行的指令的地址压入栈,而我们的ch_eip()改变了这个值,让其变成了print...这个指令的地址, 所以ch_eip()返回后,系统把经过我们更改的值弹入eip, 因此跳过了x = 1,我们得到了x = 0.

** 关于栈 **
  1, 定义
    /------------------\  Higer
    |                  |  memory
    |       Stack      |  addresses
    |                  |
    |------------------|
    |   (Initialized)  |
    |        Data      |
    |  (Uninitialized) |
    |------------------|
    |                  |
    |       Text       |  Lower
    |                  |  memory
    \------------------/  addresses

  2, 特点
    ABCD     => [44434241] Highter
                 .......
    ABCDDCBA => [44434241]
                [41424343] Lower
    由高往低增长, 没有边界检查,因此存在溢出,表现形式为放入预定高内存区的内容的大小由于超过预设空间,因此把低内存的一些数据给覆盖了。而由于程序调用子函数时会把函数返回后的下一步指令的地址值压入堆栈,函数完成调用后将该值弹回eip,相对函数的内部变量的存放地值来说,这个值位于内存的低区域,因此就存在溢出的可嗯给,尽管不同的版本的编译器,使得栈中存放的函数返回调用地址相对函数内部变量的位置不一样,但溢出依然是没问题的。
/*
* 谁动了我的eip: DEMO 2
* 输出结果 segment falt 41414141
*/
int main() {
    char buffer[8];
    if (argc > 1) {
        strcpy(buffer, argv[1]);
    }
}

$./demo2 `perl -e 'print "A"x16'`
Segment falt 41414141
  进程收到SIGEVL, 我们的main函数中的strcpy操作将main调用返回后的下一步指令地址更改成为0x41414141, 而此地址指向的位置的指令不可运行,所以程序中止,那么如果指向的地址可以运行呢?答案是那么将不会中止。
** 关于shellcode **
  1, shellcode, 机器码的16进制表示表示,例如\x98\x98\90\x00\x98....
  2, 我个人认为熟练掌握溢出的最高境界是能够随心所欲地变化shellcode,突破各种程序内的软/硬限制
  3, 我们怎样编写shellcode,方法很多,如借助各种工具nasm,gcc...当然也可以对照汇编翻译表,将mov x, x语句自己翻译为shellcode, 我个人习惯用gcc 的 x/400xb $addr,诸位可以写一个 shell脚本,将结果重定向转到一个文件内。
  4, shellcode 中的基本模式,我们可以分析execve("/bin/bash"),容易看出。当内存中已经存在字符串"/bin/bash",设str = "/bin/bash"然后使得栈中存在下列结构体
    |   .........      | Higer mem
    |   str's addr     | => addr2
    |    NULL          |
    | other arg's addr |
    |    NULL          |
    |   .........      | Lower mem
同时令eax == addr2,在执行int 80系统调用指令. 
** 改变eip **
  1, 我们懂得了怎样改变eip和写出一个shellcode,剩下的事情就是工作了,但是问题在这里,怎样让我们的shellcode工作,常见方法一:放在环境变量中,方法二,放在我们导致eip变化的那精心构造的buffer中,针对本地一处,这两者都可以工作,而针对远程溢出,在下只懂得后者。