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

  • 1 -- 简介
    • 1, EIP
    2 -- 关于栈
    • 1, 栈的定义 2, 不同的栈的结构变化
    3 -- 关于shellcode
    • 1, shellcode的定义 2, 怎样写出一个基本的shellcode 3, 怎样写出一个特殊的shellcode
    4 -- 改变EIP
    • 1, 直接法 2, 间接法
    5 -- 参考文献

写在前面的前面
  我是一个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'`
Segmentation fault
  进程收到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中,针对本地一处,这两者都可以工作。
  2, 下面是一个范例程序及exploit程序
/*
* 谁动了我的eip: DEMO 3
* 有漏洞的程序
*/
int main() {
    char buffer[64];
    if (argc > 2) {
        strcpy(buffer, argv[1]);
    }
}
  针对上面的漏洞程序,我们可以清楚地构造出调用时栈的情况,由于现代编译器的不同,程序在调用是,所分配的栈的空间将会不一样,除了 >=   
60的共同点外,其他很难判断,所以为了针对性地的,我们可以做两件事情,
    1, 自己构建一个环境,一样版本的操作系统,一样版本的gcc
    2, 通过输入参数判断这个临界值
  由于1对于很多人来说是费力不讨好的事情,所以我们很自然地选择了方法2,虽然在一些复杂的程序中,这种方法完全是一种沙箱操作,但一切尽在掌握之中.
  $./DEMO3 `perl -e 'print "A"x100'`
  Segmentation fault
  $./DEMO3 `perl -e 'print "A"x60'`
  ..
  $./DEMO3 `perl -e 'print "A"x80'`
  Segmentation fault
  $./DEMO3 `perl -e 'print "A"x70'`
  ..
  $./DEMO3 `perl -e 'print "A"x76'` #实际上填充了77位最后一位为NULL [0x00]
  Segmentation fault
  $./DEMO3 `perl -e 'print "A"x72'`
  ..
  由此我们可以断定,72是程序分配的空间,如果要直接改变程序返回的eip,我们应该添加 >80个长度的字符进去,并且第76-80放的是我们要让其指向我们的shellcode地址.而shellcode放在哪里我们能精确地找到它呢,1,放在环境变量里,在execve中有这样一段非常重要的代码......由此可见我们的shellcode地址为 0xc0000000-4-[2+strlen(program_name)+strlen(shellcode)]
2, 放在我们塞进缓冲区中,这样做一个好处,方法一很容易因为某个原因,如之前已经存放有其他环境变量等因素改变,而我们难以捉摸,放在我们赛进去的字符串里,通过分析,这个地址始终在一个范围之内,于是我们的定位又相对容易,加上0x90(nop)指令,我们可以清晰地定位我们的程序,而相关的buffer长度不够都是我们高级shellcode的内容
  关于解决字符串的使用问题
    jmp A 
   B: 
    pop %eax ; %eax now has the address of the string 
    . ; continue as usual 
    . 
    . 

   A: 
    call B 
    .string \"hello\"