NSSCTF{}
Png
对于一个PNG文件来说,其文件头总是由位固定的字节来描述的,
.PNG
89 50 4E 47 0D 0A 1A 0A
标准的PNG文件结构
`.PNG` `IHDR` `IDAT` `IEND`
strings 扫描
strings 1.png
实例结果
C:\Users\86182\Downloads>strings 1.png
IHDR
IDATx
?{ S
?{ S
?,W%
777}
(NuS
?[!S
1Kf&
&RKN
UL~b
_sIMX
'O$^R
(NuS
o<}y
3I X
$$?k
1k#xU
WxbXK<
oUyM~A
3f-c
qFQ:)
OLm-9
(NUK<1
/Ev7}U
L<{5
_sIMX
W]fp
a-cb
!fILN
oUyM~A
/Ev7}U
y/>]
&#+P
od/d
I _3
I _3
I _3
I _3
IEND
没什么信息
010editor
或者其他十六进制查看/编辑器
搜索{ 或者}

复制为十六进制文本
7B 20 53 01 FB 2F 53 7D
7B 20 53 01 FB 2F 53 7D
7B 35 8F 7D
pngcheck
pnggroup/pngcheck: Development of pngcheck 4
下载源码进入根目录cmd
cmake .
cmake --build . --config Release
使用实例
pngcheck 1.png
#OK: 1.png (213x213, 24-bit RGB, non-interlaced, static, 97.7%).
pngcheck -v 1.png
#File: 1.png (3097 bytes)
# chunk IHDR at offset 0x0000c, length 13
# 213 x 213 image, 24-bit RGB, non-interlaced
# chunk IDAT at offset 0x00025, length 3040
# zlib: deflated, 32K window, superfast compression
# chunk IEND at offset 0x00c11, length 0
#No errors detected in 1.png (3 chunks, 97.7% compression).
LSB隐写
zsteg
搜索text {}相关

StegSolve
依次查看R G B为0时输出数据。

文本txt
词频分析
WebShell
工具
蚁剑 源码
加载器 启动器启动后需要初始化,选择源码根目录。
Fiddler打开的时候会修改SSL证书导致picgo图片上传失败。
BurpSuite
直接连接

提示需要POST wllm,暗示webshell连接密码为wllm
使用蚁剑连接

手动查找flag,发现在顶层目录下。

或者使用命令行搜索
find / -name "*flag*" 2>/dev/null

cat打印

Payload
<?php
highlight_file('index.php');
include("flag.php");
$id=$_POST['id'];
$json=json_decode($_GET['json'],true);
if ($id=="wllmNB"&&$json['x']=="wllm")
{echo $flag;}
?>
提示需要发送POST 和 GET,需要满足id为wllmNB 以及 json['x']=="wllm"
打开fiddler,选择composer组件
URL,默认http1.1
http://node7.anna.nssctf.cn:27699/index.php
添加GET参数
key: json
value: {"x":"wllm"}
#注意这里需要使用转义字符
value: %7B%22x%22%3A%22wllm%22%7D
设置 Request Headers
Content-Type: application/x-www-form-urlencoded
填写 Request Body ,在body选择form-urlencoded
id=wllmNB

最后的结果为
POST http://node7.anna.nssctf.cn:27699/index.php?json=%7B%22x%22%3A%22wllm%22%7D HTTP/1.1
Host:node7.anna.nssctf.cn:27699
User-Agent:Fiddler-Everywhere
Content-Type:application/x-www-form-urlencoded
Content-Length:9
id=wllmNB
执行得到flag

加密/解密
工具
SageMath
SageMathInstallation Guide
在线工具Sage Cell Server,没有一些基础的库比如说crypto相关,导致无法使用long2bytes,需要手动转换一下
#Ubunbu参考WSL安装方法
#添加环境变量
nano ~/.bashrc
#根据安装目录调整,添加到尾部即可。
#export PATH="/home/wdream/Tools/miniforge3/bin:$PATH"
source ~/.bashrc
conda --version

Ciphey
cyphey命令行工具,需要python3.8.8安装,推荐使用anaconda
安装具体细节参考用Anaconda安装ciphey的过程记录 - 知乎
ciphey -t 4O595954494Q32515046324757595N534R52415653334357474R4N575955544R4O5N4Q46434S4O59474253464Q5N444R4Q51334557524O5N4S424944473542554O595N44534O324R49565746515532464O49345649564O464R4R494543504N35
ciphey -f encrypted.txt

分析过程:
原密文
4O595954494Q32515046324757595N534R52415653334357474R4N575955544R4O5N4Q46434S4O59474253464Q5N444R4Q51334557524O5N4S424944473542554O595N44534O324R49565746515532464O49345649564O464R4R494543504N35
首先统计一下出现的字符集,0~9和NOQRS,字母数量特别少而且是相邻字符,疑似是偏移过的Base16
#!/usr/bin/env python3
import sys
from collections import Counter
def main():
if len(sys.argv) < 2:
print("用法: python count_chars.py <字符串>")
sys.exit(1)
input_str = sys.argv[1] # 获取第一个参数作为输入字符串
counts = Counter(input_str) # 统计字符出现次数
# 按字符排序输出
for char in sorted(counts):
print(f"'{char}': {counts[char]}")
if __name__ == "__main__":
main()

NOQRS对应偏移的ABDEF,如果是偏移的则offset = ‘N’ - ‘A’ = 78 - 65 = 13
caesar还原为
4B595954494D32515046324757595A534E52415653334357474E4A575955544E4B5A4D46434F4B59474253464D5A444E4D51334557524B5A4F424944473542554B595A44534B324E49565746515532464B49345649564B464E4E494543504A35
再使用Base16解密得到
KYYTIM2QPF2GWYZSNRAVS3CWGNJWYUTNKZMFCOKYGBSFMZDNMQ3EWRKZOBIDG5BUKYZDSK2NIVWFQU2FKI4VIVKFNNIECPJ5
分析字符集包括部分数字和大写字母,刚好符合Base32的字符集

尝试使用Base32解密得到
V143Pytkc2lAYlV3SlRmVXQ9X0dVdmd6KEYpP3t4V29+MElXSER9TUEkPA==
结尾有两个=,直接猜测是Base64,进行解密得到
W^7?+dsi@bUwJTfUt=_GUvgz(F)?{xWo~0IWHD}MA$<
可以看到这里有很多特殊符号,猜测是Base85或者Base91
如果用91进行解密会直接得到乱码,肯定不正确。
��W*!��H���9��N$� � �xptr�R�`Nl�
但是如果用85进行解密,会提示开头不能是W,这里是因为大部分解密工具选择的算法不对。需要选择RFC1924算法。Base85 Encoder And Decoder
最后得到flag为

Caesar
偏移量
BaseX
X表示的是编码字符数量
Base16
0123456789ABCDEF
没有小写字母,特点只有6种大写字母
Base32
ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
没有小写字母,只有部分数字,缺少数字0 1 8 9
Base58
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
没有0
Base62
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
Base64
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
结尾一般有= 比较好辨别,有大小写数字。
Base85
有多种算法Base85 Encoder And Decoder
包括 Adobe, Ascii, RFC1924, Z85 等
Ascii85 标准(! 到 u)
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstu
Z85(ZeroMQ 提出):使用完全可打印的 85 个字符,不包括容易混淆的字符(如 "、\ 等)
有很多特殊符号
Base91
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~"
包括数字,字母,符号,没有反斜杠\,单引号‘
对称加密
AES
非对称加密
RSA
RSA公开密钥密码体制的原理是:根据数论,寻求两个大素数比较简单,而将它们的乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
-
选择两个大素数 p 和 q
-
计算: \(n=p×q\) 这里 n 称为 模数,它决定了密钥长度(通常 1024~4096 位)
-
计算欧拉函数: \(ϕ(n)=(p−1)(q−1)\)
-
选择公钥指数 e,满足: \(1<e<ϕ(n),gcd(e,ϕ(n))=1\) gcd是最大公约数,公钥通常会给出。
-
计算私钥指数 d: \(d≡e^{−1}(mod \spaceϕ(n))\)
(mod ϕ(n))指的是同余,≡是约束关系而不是=,这里-1指的是模逆运算,等价于
\[\\d*e ≡1 \space (mod \spaceϕ(n)) \\=> \\(d*e) \space mod \spaceϕ(n)=1 \\=> d=inverse\_mod(e,\spaceϕ(n))\]
| 密钥类型 | 内容 |
|---|---|
| 公钥 (public key) | (n, e) |
| 私钥 (private key) | (n, d) |
加密与解密
- 加密(用公钥加密消息 m):
- 解密(用私钥还原消息 c):
这里 m 要转为整数表示(通常用 bytes_to_long 转换)。
Coppersmith
Coppersmith是指泄露了一部分信息的RSA
PR.<x> = Zmod(n)[] #x是多项式自变量,Zmod(n)表示 多项式的系数是模n的
f = (p_base + (i << 265)) + x * (mod << 6)
roots = f.monic().small_roots(X=2^453, beta=0.4, epsilon=0.05)
assert roots
p = f(roots[0])
X=2^453表示自变量x取值范围 $[0, 2^{453} - 1]$
beta和epsilon通常不动,epsilon越小表示探索深度越大,花费时间越多。
实例,求出flag
这里是公钥加密信息flag得到加密信息ct
p = getPrime(1024)
q = getPrime(1024)
n = p * q
e = 65537
hint1 = p >> 724
hint2 = q % (2 ** 265)
ct = pow(bytes_to_long(flag), e, n)
print(hint1)
print(hint2)
print(n)
print(ct)
#1514296530850131082973956029074258536069144071110652176122006763622293335057110441067910479
#40812438243894343296354573724131194431453023461572200856406939246297219541329623
#21815431662065695412834116602474344081782093119269423403335882867255834302242945742413692949886248581138784199165404321893594820375775454774521554409598568793217997859258282700084148322905405227238617443766062207618899209593375881728671746850745598576485323702483634599597393910908142659231071532803602701147251570567032402848145462183405098097523810358199597631612616833723150146418889589492395974359466777040500971885443881359700735149623177757865032984744576285054725506299888069904106805731600019058631951255795316571242969336763938805465676269140733371287244624066632153110685509892188900004952700111937292221969
#19073695285772829730103928222962723784199491145730661021332365516942301513989932980896145664842527253998170902799883262567366661277268801440634319694884564820420852947935710798269700777126717746701065483129644585829522353341718916661536894041337878440111845645200627940640539279744348235772441988748977191513786620459922039153862250137904894008551515928486867493608757307981955335488977402307933930592035163126858060189156114410872337004784951228340994743202032248681976932591575016798640429231399974090325134545852080425047146251781339862753527319093938929691759486362536986249207187765947926921267520150073408188188
getPrime是生成指定位数的素数,这里的p q都是1024位的素数。
p >> 724:泄露了 p 的高 1024-724 = 300 位也就是hint1
q % 2^265:泄露了 q 的低 265 位也就是hint2
解密脚本,需要在SageMath环境下运行
from Crypto.Util.number import long_to_bytes
h1, h2 = 1514296530850131082973956029074258536069144071110652176122006763622293335057110441067910479, 40812438243894343296354573724131194431453023461572200856406939246297219541329623
n = 21815431662065695412834116602474344081782093119269423403335882867255834302242945742413692949886248581138784199165404321893594820375775454774521554409598568793217997859258282700084148322905405227238617443766062207618899209593375881728671746850745598576485323702483634599597393910908142659231071532803602701147251570567032402848145462183405098097523810358199597631612616833723150146418889589492395974359466777040500971885443881359700735149623177757865032984744576285054725506299888069904106805731600019058631951255795316571242969336763938805465676269140733371287244624066632153110685509892188900004952700111937292221969
c = 19073695285772829730103928222962723784199491145730661021332365516942301513989932980896145664842527253998170902799883262567366661277268801440634319694884564820420852947935710798269700777126717746701065483129644585829522353341718916661536894041337878440111845645200627940640539279744348235772441988748977191513786620459922039153862250137904894008551515928486867493608757307981955335488977402307933930592035163126858060189156114410872337004784951228340994743202032248681976932591575016798640429231399974090325134545852080425047146251781339862753527319093938929691759486362536986249207187765947926921267520150073408188188
e = 65537
#h1是p的高300位(放在了低位),h2是q的低265位
# 1. 构造 p 的已知部分
mod = 1 << 265 #2^265
p_high = h1 << 724 #将高300位放到高位
#p = h1_724bit
#q = 759bit_h2
#n = p * q
#h2^-1 = inverse_mod(h2, 2^265)
#(n * h2^-1) % mod = (p % mod) * [(759bit_h2 * h2^-1) % mod] = (p % mod) * 1
p_low = (n * inverse_mod(h2, mod)) % mod #得到p的低265位
p_base = p_high + p_low #拼接p,中间有1024-300-265 = 459位缺失(265位~723位)
# 2. 使用 Coppersmith 算法查找 p 的中间缺失位
PR.<x> = Zmod(n)[] #x是多项式自变量,Zmod(n)表示 多项式的系数是模n的
for i in range(64):#2^6 = 64 爆破6位(穷举)
# 构建多项式: f(x) = (p_base + i*2^265) + x*2^329 (2^329 = mod * 64)
f = (p_base + (i << 265)) + x * (mod << 6)
#注意monic首一化
roots = f.monic().small_roots(X=2^453, beta=0.4, epsilon =0.05) # 453 = 459 - 6 爆破了6位
if roots:
p = int(f(roots[0]))#需要强转int
break
# 3. 解密 RSA
q = n // p
d = inverse_mod(e, (p - 1) * (q - 1))
print(long_to_bytes(pow(c, d, n)))
执行
sage rsa.py
flag{ef5e1582-8116-4f61-b458-f793dc03f2ff}
如果不手动爆破,即
#...
PR.<x> = Zmod(n)[]
f = p_base + x * mod
roots = f.monic().small_roots(X=2^459, beta=0.4, epsilon =0.04)
assert roots
p = int(f(roots[0]))
#...
epsilon收缩到0.04及以下均可。
Sage Cell Server 在线sage环境不能用long_to_bytes测试得到long56006392793429523039334991295440472601833419964063188654957241090275128167728784831578248304182847101本地转换为bytes
python -c "from Crypto.Util.number import long_to_bytes
print(long_to_bytes(56006392793429523039334991295440472601833419964063188654957241090275128167728784831578248304182847101))"
同样得到flag{ef5e1582-8116-4f61-b458-f793dc03f2ff}
Hastad 广播攻击
CRT中国剩余定理 China Remainder Theorem中国剩余定理 - OI Wiki
「物不知数」问题:有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?
解方程组

得到最小非负整数解$X = x + kN, N=Π^n_{i=1} n_i$
实例
# FLAG = bytes_to_long(pad(b"flag{??????}",64))
# def init_key():
# p, q = getPrime(512), getPrime(512)
# n = p*q
# e = 9
# while(GCD((p-1)*(q-1),e)!=1):
# p, q = getPrime(512), getPrime(512)
# n = p*q
# d = inverse(e,(p-1)*(q-1))
# return n,e,d
# n_list=list()
# c_list=list()
# for i in range(9):
# N,e,d=init_key()
# n_list.append(N)
# c=pow(FLAG,e,N)
# c_list.append(pow(FLAG,e,N))
# assert(pow(c,d,N)==FLAG)
# print("n_list:",n_list)
# print("c_list:",c_list)
p q都是512bit,n是1024bit,e = 9
后面的部分大致意思就是生成了9组模数n,一共用明文FLAG生成了9组密文c
所以已知c,e,n,记FLAG 为 m,记$N =Π^8_{i=0} n_i$
$$ \c_i = m^e\mod n_i \=> m^e ≡ c_i (\mod n_i)
\m = c^d_i \mod n_i \ \space=> m < n_i => m^9 < N $$ $m^e ≡ c_i (\mod n_i)$可以使用CRT中国剩余定理
crt(c_list, n_list)得到最小非负整数解M,M满足$M ≡ m^e (\mod N)$
但是m^e^ = m^9^ < N 所以m^e^ = M = crt(c_list, n_list)
再开e次根号得到m,也就是9次。
from Crypto.Util.number import long_to_bytes
n_list = [
]
c_list = [
]
e = 9
# CRT 合并
M = crt(c_list, n_list)
# 整数 9 次方根
m = M.nth_root(e)
print(long_to_bytes(int(m)))
Comments