第二届强网杯部分writeup

第二届强网杯全国网络安全挑战赛

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&param2[]=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?}