[2020 GeekChallange] Re 刘壮的BaseXX
0x00 逆向分析
把文件丢进IDA64
可以看到套了两层循环,如果执行了break即输出Correct。
切换到汇编界面分析:
通过分析汇编代码结构可以猜测出程序大致分为加密、比较两部分。
0x01 加密部分
观察汇编代码部分,可以看到循环里调用了两个函数:
sub_140001420和sub_1400010E0
反编译看看。
sub_140001420
通过分析代码,不难看出它将我们输入的字符串,用for循环,一个一个字符进行加密操作。
关键的加密操作即图中红框位置。
先不管开头的 ‘i ^’,我们先分析
((*(_BYTE *)(a1 + i) >> 4) | (16 * *(_BYTE *)(a1 + i)))
这里要特别注意管道符|后的**不是幂乘,而是一个乘号和一个指针符号。
乍一看这似乎是一步很难逆向的运算,但是经过仔细分析会发现,管道符左边是右移4位,右边是*16,也相当于左移四位。
注意这个函数的输入参数a1是__int64数组指针
即我们所操作的这个数长度是8位的,那么当我们把这个数左移4位以后,右边就空出了4个0,左边的4位被挤出去了。右移则是把右边4位挤出去了。
那么此时这两个数按位或,由于互相的位置都空出来了4位,其中不存在进位,那就相当于十进制两个数相加。
举个例子:
a = 'W'
print(bin(ord(a)))
print(bin((ord(a) << 4) % 0b100000000))
print(bin(ord(a) >> 4))
print(bin((ord(a) << 4) % 0b100000000|(ord(a) >> 4))) #%0b100000000只是为了保持8位
#输出结果:
# 0b01010111
# 0b01110000
# 0b00000101
# 0b01110101
#我偷偷在前面补了几个0,方便观察
用二进制表示可能不太直观,因为它是4位一操作,4个二进制位等于1个16进制位,那么用16进制表示就很直观了:
a = 'W'
print(hex(ord(a)))
print(hex((ord(a) << 4) % 0x100))
print(hex(ord(a) >> 4))
print(hex((ord(a) << 4) % 0x100|(ord(a) >> 4))) #同上#0x100也只是为了保持变量位数
#输出结果:
# 0x57
# 0x70
# 0x05
# 0x75
可以很容易地发现,它就是交换了16进制位的前后两位(即交换二进制位的前四位和后四位)。
那么解密就只需要把每一个字符的前四位和后四位交换即可。
sub_1400010E0
打开就发现满屏幕的代码,一看就很不想去分析。(我也懒得分析)
但是在这里能看到有一个一直用到的一个字符串
是不是跟base64的字母表长度一模一样?
这边我直接猜测换表了。
0x02 比较部分
sub_140001490
可以看到main函数这里如果返回了0就错了,如果正确应该返回1。
可以看到循环外输出0,要正确就不能跳出循环。
看到循环内,有一个简单地按位比较,因此这个比较部分就是简单地按位字符比较。
跳转到密文:
就是由之前的表组成的。
题目也告诉我们是某Base加密,大胆猜测是base64换表。
拉进脚本里跑一下:
import base64
import string
str1 = "maj7TmztjquUN8Xm-hKplvaYfEAxrUnIc51qxlKOwCKN4XsdzBmjOd_-"#密文
string1 = "zyxwvutsrqponmlkjihgfedcbaABCDEFGHIJKLMNOPQRSTUVWXYZ9876543210-_"#改过的表
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"#原表
print (base64.b64decode(str1.translate(str.maketrans(string1,string2))))
#输出结果:b'5\x946\xb4\xd0\x06@\x91n\x9f\\M\xf9)\n8FrQ\xe6\x82"\xe3"_\x8f\t\x08\xe9(\r\xc9\'\xe7\x11\xd6\x01\xb3P\xa1o\xfe'
把二进制结果转换成数字数组:
[53, 148, 54, 180, 208, 6, 64, 145, 110, 159, 92, 77, 249, 41, 10, 56, 70, 114, 81, 230, 130, 34, 227, 34, 95, 143, 9, 8, 233, 40, 13, 201, 39, 231, 17, 214, 1, 179, 80, 161, 111, 254]
到这一步,还没有验证到底是否真的是Base64换表怎么办?
我们可以先试试输入SYC(固定flag的开头)进去看看是不是这样。
在第一次加密之后下断点
双击V6看其中的值
可以看到前三个字符为:5(\x35) \x94 6(\x36)
和我们之前换表的出的字符串前三位一模一样。
到这里基本就已经解出来了,放脚本:
import base64
import string
str1 = "maj7TmztjquUN8Xm-hKplvaYfEAxrUnIc51qxlKOwCKN4XsdzBmjOd_-"#密文
string1 = "zyxwvutsrqponmlkjihgfedcbaABCDEFGHIJKLMNOPQRSTUVWXYZ9876543210-_"#改过的表
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"#原表
result = base64.b64decode(str1.translate(str.maketrans(string1,string2)))
b = []
for i in result:
b.append(int(i))
print(b)
for i in range(len(b)):
b[i] = i ^ b[i]
print(chr((b[i]>>4) + b[i]%16*16) ,end = "")
得到flag:SYC{M0dified_B@se64_is_Sti11_S1mpl3_Right}