签到
- 打开题目直接获取到flag,真·签到
Welcome:
- 下载得到一个无后缀的文件,binwalk查看是bmp位图
- 拖进Stegsolve使用偏移工具得flag
调查问卷
- 填写问卷得flag,真·水题
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
总结
- 题目偏难且灵活,有两三道题有思路但无法完成,感觉就差临门一脚了。等官方WP
- 得PWN者得天下
- 身为Win逆向手居然靠Crypto输出...要好好反思自己的方向知识水平了
文章评论