TokyoWesterns CTF 2019 printf
题目简述 & 题目漏洞
题目给了libc,libc版本是2.29:
1 | ubuntu@ubuntu:~/Documents/pwn/2019/twctf/printf$ strings libc.so.6 | grep "GNU C" |
开的保护情况:
1 | ubuntu@ubuntu:~/Documents/pwn/2019/twctf/printf$ checksec printf |
题目自己实现了一个prinf函数,我们暂且命名为my_printf。
在my_printf函数中,程序会对输入的格式化字符串进行逐字符处理,F5根据伪C代码可以看到程序对于一些特殊格式的判断,但是程序滤掉了%n,我们无法通过常规的格式化字符串漏洞进行地址写。但是函数有一个alloca操作,alloca函数是从栈里面动态分配内存,在程序返回时该内存会自动释放掉。对应汇编里是抬高了栈顶,抬高的大小是rax,rax开始赋值为[rbp+var_150],这个地方在反编译后看的比较清楚,这个变量是一个计数器,当格式化字符串的字符不是“%”时,它就会加1。这样就可以通过控制格式化字符串来控制rsp的值。
1 | .text:0000000000001C5D mov rax, [rbp+var_150] |
另外在my_printf函数最后会调用puts函数将输入的字符串输出,再输出时puts的参数是[rbp-0xd8],它会在遍历格式化字符串时将其存储在[rbp-0xd8]中,那么其实这段空间的地址在最开始有一个赋值操作,[rbp-0xd8] = rax,这两段代码其实正好是连着的,rax在0x1C88时被赋值为rsp。所以我们可以通过格式化字符串长度来控制rax从而控制rsp至目标地址,将格式化字符串写入这个地址。
1 | .text:0000000000001C97 mov [rbp+var_D8], rax ; //here |
利用过程
开始要求输入name,长度为0x100,可以用来泄露libc基址、栈地址、程序基址和canary。
1 | ##leak addr |
再利用第二次输入来达到在指定地址写one_gadget的目的。这里利用的是exit函数里的rop,在libc_base+0x1e66c8处写了one_gadget,而这个地址在libc_base+4736f中被赋值给了rbx,然后在libc_base+0x47398处call [ebx]从而触发one_gadget。
1 | .text:0000000000047368 cmp byte ptr [rsp+48h+var_3C], 0 |
调试看的比较清楚,在程序偏移0x1c85处下断点,rax为0x8037420。
单步执行,程序栈顶抬高,栈顶变为0x7ffff7fc76a0,这个地址是libc_base(0x00007ffff7de1000)偏移0x1e66a0处。
然后rax被赋值为libc_base+0x1e66a0。最后在调用puts函数处下断点,libc_base+0x1e66c8处被写入了one_gadget。
最后在libc_base+0x47398调用处下断点,程序call [rbx]成功执行one_gadget。
完整的exp我就不贴了,比赛时这道题看了半天也不会做,参考的是这篇wp。
De1CTF 2019 Unprintable(待补充)
题目描述 && 题目漏洞
libc版本是2.23,题目没有PIE,没有canary:
1 | [*] '/home/ubuntu/Documents/2019/De1CTF/Unprintable/unprintable' |
题目逻辑很简单,输出一个临时变量v3的地址,可以泄露栈地址,然后就关闭了stdout,最后有一个格式化字符串漏洞。
1 | int __cdecl __noreturn main(int argc, const char **argv, const char **envp) |
参考
https://www.anquanke.com/post/id/183859
https://www.xctf.org.cn/library/details/79378efa88bff52451b2f822abe562d29ae7aade/
HCTF 2018 the_end
题目简述 && 题目漏洞
逻辑比较简单,在开始会输出sleep函数的地址,从而泄露libc。
关闭了stdout和stderr,最后有5次写的机会,在每次循环中,先输入一个地址,然后可以在指定地址写一个字节,5次就是可以写5个字节。最后调用exit函数。
这里有两种解法:
- 第一种利用的是exit函数中的一个gadget。
- 第二种解法是程序在调用exit函数退出时会触发_IO_flush_all_lockp,刷新所有的文件指针,即遍历_IO_list_all链中的每一项会遍历IO_list_all,然后调用虚表里的某些函数,这个类似于我们在house of orange里让libc触发abort从而调用_IO_flush_all_lockp是一样的。
第一种解法
先通过sleep函数泄露libc:
1 | ##leak libc_base |
程序在调用exit函数时会调用ld.so里的_dl_fini函数:
跟进_dl_fini函数_dl_fini+126处函数有一个操作
1 | 0x7ffff7de7b3e <_dl_fini+126>: call QWORD PTR [rip+0x216404] # 0x7ffff7ffdf48 <_rtld_global+3848> |
这个地址0x7ffff7ffdf48与libc的偏移是0x5f0f48,可以将其覆写为one_gadget,但因为我们只能写5个字节,在未覆写之前,该处存储的值是:
1 | gdb-peda$ x /8gx 0x7ffff7ffdf48 |
所以可以覆写低5个字节,然后触发one_gadget。
最后在调试的时候注意因为题目关了输出,所以本地只有发送一个“exec /bin/sh 1>&0”才会get shell,而且还是在开了socat的情况下成功get shell。
完整exp如下:
1 | from pwn import * |
第二种解法
0x00 CTF 2017 left(待补充)
参考
https://github.com/agadient/CTF/tree/master/tokyo
https://www.cnblogs.com/hac425/p/9959748.html
https://github.com/SPRITZ-Research-Group/ctf-writeups/tree/master/0x00ctf-2017/pwn/left-250