第二届强网杯全国网络安全挑战赛
0x00签到:
操作内容:
FLAG值:
flag{welcome_to_qwb}
0x00 2、Welcome:
操作内容:
FALG值:
QWB{W3lc0me}
Streamgame三连
Streamgame1:
- 下载下来是一个包含加密脚本和加密后key的压缩包
- 首先观察下加密脚本
from flag import flag
assert flag.startswith("flag{")
assert flag.endswith("}")
assert len(flag)==25
def lfsr(R,mask):
output = (R << 1) & 0xffffff
i=(R&mask)&0xffffff
lastbit=0
while i!=0:
lastbit^=(i&1)
i=i>>1
output^=lastbit
return (output,lastbit)
R=int(flag[5:-1],2)
mask = 0b1010011000100011100
f=open("key","ab")
for i in range(12):
tmp=0
for j in range(8):
(R,out)=lfsr(R,mask)
tmp=(tmp << 1)^out
f.write(chr(tmp))
f.close()
分析
- 这个脚本无法单独运行,题目没有给出flag.py
废话,但是我们可以从前四行代码分析出flag的特点
assert flag.startswith("flag{")
assert flag.endswith("}")
assert len(flag)==25
这三个断言表达式提供了如下信息:flag的形式是flag{….},长度是25位。再往下看
R=int(flag[5:-1],2)
R的值是将flag的5至倒数第一位转换成的二进制值。注意,int(x,2)中x为二进制数字符串,如’101010’。推断出flag的形式为flag{19位二进制}
- 算法内容大致浏览后没发现突破口,于是写了个脚本爆破,2^19在可接受范围内
#原加密算法
def lfsr(R,mask):
output = (R << 1) & 0xffffff
i=(R&mask)&0xffffff
lastbit=0
while i!=0:
lastbit^=(i&1)
i=i>>1
output^=lastbit
return (output,lastbit)
#打开key获取加密后的数据
f=open("key","rb")
data = bytearray()
data+=f.read()
print(data)
f.close()
flag = 0
#爆破
while flag <= 0b1111111111111111111 :
print('try:',flag)
byte=bytearray()
flag1 = bin(flag)
R=int(flag1[2:],2)
mask = 0b1010011000100011100
for i in range(12):
tmp=0
for j in range(8):
(R,out)=lfsr(R,mask)
tmp=(tmp << 1)^out
byte+=(tmp).to_bytes(1,byteorder='big')
# 将key中数据与当前循环加密后的数据进行比对,匹配则输出flag
if byte == data:
print('success:'+'flag{'+bin(flag[2:])+'}')
break
flag = flag+1
- 时间:15min
Streamgame2:
- 与第一题一模一样的套路,先上加密源码:
from flag import flag
assert flag.startswith("flag{")
assert flag.endswith("}")
assert len(flag)==27
def lfsr(R,mask):
output = (R << 1) & 0xffffff
i=(R&mask)&0xffffff
lastbit=0
while i!=0:
lastbit^=(i&1)
i=i>>1
output^=lastbit
return (output,lastbit)
R=int(flag[5:-1],2)
mask=0x100002
f=open("key","ab")
for i in range(12):
tmp=0
for j in range(8):
(R,out)=lfsr(R,mask)
tmp=(tmp << 1)^out
f.write(chr(tmp))
f.close()
分析:
- 与上题的区别在于增加了中间二进制位的长度,算法是没改变的,2^19时间为15min,2^21预计在1小时左右,依然是可接受的,二话不说开爆
def lfsr(R,mask):
output = (R << 1) & 0xffffff
i=(R&mask)&0xffffff
lastbit=0
while i!=0:
lastbit^=(i&1)
i=i>>1
output^=lastbit
return (output,lastbit)
f=open("key","rb")
data = bytearray()
data+=f.read()
print(data)
f.close()
flag = 0
flag=0b111111111111111111111
while flag >= 0 :
print('try:',flag)
byte=bytearray()
flag1 = bin(flag)
R=int(flag1[2:],2)
mask=0x100002
for i in range(12):
tmp=0
for j in range(8):
(R,out)=lfsr(R,mask)
tmp=(tmp << 1)^out
byte+=(tmp).to_bytes(1,byteorder='big')
if byte == data:
print('success:'+'flag{'+bin(flag[2:])+'}')
break
flag-=1
一套脚本跑两题…我好咸鱼….
– 时间:30min
##Streamgame4:
– 相比前两题,大幅增加了key的计算复杂度key的大小从几十b暴涨到1M,先上源码
from flag import flag
assert flag.startswith("flag{")
assert flag.endswith("}")
assert len(flag)==27
def nlfsr(R,mask):
output = (R << 1) & 0xffffff
i=(R&mask)&0xffffff
lastbit=0
changesign=True
while i!=0:
if changesign:
lastbit &= (i & 1)
changesign=False
else:
lastbit^=(i&1)
i=i>>1
output^=lastbit
return (output,lastbit)
R=int(flag[5:-1],2)
mask=0b110110011011001101110
f=open("key","ab")
for i in range(1024*1024):
tmp=0
for j in range(8):
(R,out)=nlfsr(R,mask)
tmp=(tmp << 1)^out
f.write(chr(tmp))
f.close()
分析
- 移位加密函数nlfsr增加了状态切换
def nlfsr(R,mask):
output = (R << 1) & 0xffffff
i=(R&mask)&0xffffff
lastbit=0
changesign=True
while i!=0:
if changesign:
lastbit &= (i & 1)
changesign=False
else:
lastbit^=(i&1)
i=i>>1
output^=lastbit
return (output,lastbit)
- 写入数据的次数大幅增加
for i in range(1024*1024):
tmp=0
for j in range(8):
(R,out)=nlfsr(R,mask)
tmp=(tmp << 1)^out
f.write(chr(tmp))
- 一开始本想如法炮制直接开爆,但相比起前两题的文件写入次数(8),本题加密的写入次数为1024*1024,爆破一个数字的时间约1分钟
那还爆个P啊,但不会逆向算法只能干拉啊
- 加密算法没复杂多少,只是个简单的状态切换。但文件写入却复杂了几个数量级。会不会key有重复的部分?使用010Editor查看下文件hex果然有收获
- 猜想正确,果然是有很多重复的部分。将重复的部分提取出来,修改下爆破脚本继续不是美滋滋?
- 提取后大小减少了不少,通过修改加密脚本写入部分可以减少比对的次数来减少复杂度。二话不说开爆,速度为每秒10个。掐指一算,两天不够
那还爆个P啊,但不会逆向算法只能干拉啊
- 既然全文比对会复杂度爆炸,那么局部比对呢?取前64个字节的数据,容量有2^67,而需要爆破的内容量只有2^21,虽然有碰撞的风险,但在比赛时这种用精度换取速度的方式是可行的(2^21/2^64=1/2^43…..
还不是因为撸不出逆算法) - 将key前64位提取出来
修改爆破脚本
def nlfsr(R,mask):
output = (R << 1) & 0xffffff
i=(R&mask)&0xffffff
lastbit=0
changesign=True
while i!=0:
if changesign:
lastbit &= (i & 1)
changesign=False
else:
lastbit^=(i&1)
i=i>>1
output^=lastbit
return (output,lastbit)
f=open("key","rb")
data = bytearray()
data+=f.read()
f.close()
flag = 0b111111111111111111111
while flag >= 0:
byte=bytearray()
print('try: ',flag)
flag1 = bin(flag)
R=int(flag1[2:],2)
mask=0b110110011011001101110
#修改循环加密次数
for i in range(64):
tmp=0
for j in range(8):
(R,out)=nlfsr(R,mask)
tmp=(tmp << 1)^out
byte+=(tmp).to_bytes(1,byteorder='big')
if byte == data:
print('success:'+'flag{'+bin(flag[2:])+'}')
input()
flag = flag-1
一套脚本跑三题…题好咸鱼….
– 修改后速度大为提升,约每秒200+次
– 时间:50min
0x06 Web签到:
操作内容:
1. 查看源代码
Md5绕过,所以要构造param1和param2,使用bp抓包修改参数。
利用md5函数的处理漏洞,0e开头都认为是==。
进入第二关
将两个参数改为数组即可,param1[]=1¶m2[]=2,
进入第三关
查看源码:
在网上找到二个md5值相同的hex串
Hex:4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa200a8284bf36e8e4b55b35f427593d849676da0d1555d8360fb5f07fea2
Hex:4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa202a8284bf36e8e4b55b35f427593d849676da0d1d55d8360fb5f07fea2
使用url编码
发送即可得到flag
FLAG值:
QWB{s1gns1gns1gnaftermd5}
0x08 Three hint:
操作内容:
注册后登陆进去发现
可以查看和我同年龄的人,age字段肯定可以注入,类型为二次注入。
但是age字段只能为number,所以采用hex编码
猜测字段,发现有4个字段
爆数据库
得出数据库名为qwb.
爆表名
查看字段flag
得到flag.
FLAG值:
QWB{M0b4iDalao0rz0rz}
0x09. Simplecheck
操作内容:
simplecheck是一道android逆向题,对其进行反编译后查看主函数,发现是一个一元二次方程的求解,主要代码如下
private static int[] a = {0, 146527998, 205327308, 94243885, 138810487, 408218567, 77866117, 71548549, 563255818, 559010506, 449018203, 576200653, 307283021, 467607947, 314806739, 341420795, 341420795, 469998524, 417733494, 342206934, 392460324, 382290309, 185532945, 364788505, 210058699, 198137551, 360748557, 440064477, 319861317, 676258995, 389214123, 829768461, 534844356, 427514172, 864054312};
private static int[] b = {13710, 46393, 49151, 36900, 59564, 35883, 3517, 52957, 1509, 61207, 63274, 27694, 20932, 37997, 22069, 8438, 33995, 53298, 16908, 30902, 64602, 64028, 29629, 26537, 12026, 31610, 48639, 19968, 45654, 51972, 64956, 45293, 64752, 37108};
private static int[] c = {38129, 57355, 22538, 47767, 8940, 4975, 27050, 56102, 21796, 41174, 63445, 53454, 28762, 59215, 16407, 64340, 37644, 59896, 41276, 25896, 27501, 38944, 37039, 38213, 61842, 43497, 9221, 9879, 14436, 60468, 19926, 47198, 8406, 64666};
private static int[] d = {0, -341994984, -370404060, -257581614, -494024809, -135267265, 54930974, -155841406, 540422378, -107286502, -128056922, 265261633, 275964257, 119059597, 202392013, 283676377, 126284124, -68971076, 261217574, 197555158, -12893337, -10293675, 93868075, 121661845, 167461231, 123220255, 221507, 258914772, 180963987, 107841171, 41609001, 276531381, 169983906, 276158562};
public static boolean m(String paramString) {
if (paramString.length() != b.length) {
return false;
}
int[] arrayOfInt = new int[a.length];
arrayOfInt[0] = 0;
byte[] paramBytes = paramString.getBytes();
int len = paramBytes.length;
int index = 0;
int arrayOfIntIndex = 1;
while (index < len) {
arrayOfInt[arrayOfIntIndex] = paramBytes[index]; //把1~34个字符赋值给arrOfInt,第0个字符是0
arrayOfIntIndex += 1;
index += 1;
}
index = 0;
for (; ; ) {
if (index >= c.length) {
break;
}
if ((a[index] != b[index] * arrayOfInt[index] * arrayOfInt[index] + c[index] * arrayOfInt[index] + d[index]) ||
(a[(index + 1)] != b[index] * arrayOfInt[(index + 1)] * arrayOfInt[(index + 1)] + c[index] * arrayOfInt[(index + 1)] + d[index])) {
break;
}
index += 1;
}
return true;
}
}
编写脚本,对 bx^2+cx+d=a 进行求解,只取正数解,再进行ASCII转换,就等于重新获得了字符串paramString,而这,也就是我们要的flag
FLAG值:
flag{MAth_i&_GOOd_DON7_90V_7hInK?}