题目分析


首先查壳后发现没有壳,直接使用IDA打开,但是使用F5反编译的时候,出现栈帧报错,使用ALT+K修复一下,然后直接F5得到主函数结构如下

main

分析主流程:

  • 输入字符
  • 判断字符长度
  • 经过sub_401160函数处理
  • 经过sub_401000函数处理
  • 经过sub_4010E0函数处理
  • 和最终的byte_402104数组比对,如果不一致就exit,否则最后输出Congratulation。

逆向分析


输入的字符串经过一系列变化后,与byte_402104处的代码进行比较,如果一致则成功,提取byte_402104数组的值,长度大小为44位。

1
2
3
4
5
6
start_address  = 0x00402104
finall_str = []
print "start"
for i in range(start_address,start_address+44):
finall_str.append(Byte(i))
print finall_str

接下来我们来看对字符串进行的变换操作,依次经过sub_401160、sub_401100、sub_4011E0三个函数,在sub_4010E0函数中,对v3输入字符串的操作依赖于v7故我们先分析sub_401000函数

sub_401000函数


输入,输出,长度

sub_401000(&v7, (int)&v9, strlen(&v9))

经过以上代码后,在a1处得到的是01 02 03 …FF。而在*v6所指向的位置是flag{this_is_not_the_flag_hahaha}的循环直到256个字符。

sub_401000(&v7, (int)&v9, strlen(&v9))

算法分析(KSA):

  • 两组字符串,每组是256个字符串,分别命名为a[],b[],下标值v3初始为0
  • 接下来的操作循环256次
  • 依次取a[],b[]中对应位置的字符,下标v3,三者相加后取余数256后得到新的v3值,交换a[v3]和b[v3]的值
  • 得到最后的字符串

这里是进行swap()操作,最后在38行给a1赋值。这里每次的a1的值也就是传入的v7的值最后的结果是固定的,我们通过动态调试得到如下数据:

经过数据处理:

1
2
3
4
5
6
7
8
9
10
11
fi = open("new.txt",'r')
data = fi.readline()
res = []
while data:
data = data.split()
for i in range(1,17):
m = int(data[i],16)
res.append(m)
data = fi.readline()
print len(res)
fi.close()

得到v7的初始的值。

sub_4010E0函数分析


sub_4010E0((int)&v7, (int)v3, strlen(v3));

算法分析(PRGA):

  • result是传入的密钥流,v6的指向是密钥流中的每个字符,v4是每个字符和v4相加后取余的结果
  • 对于明文a2,循环其长度a3次,每次与某一指定字节,文中红线部分,做异或。

在这一步中关键点是红线部分的值,通过代码改写,可以发现红线部分的值是定值,那么我们再异或一次就可以得到初始的字符串了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
v3 = 0
v4 = 0
v5 = 0
value_or = []
while v5 <= 43:
v3 = ( v3 + 1 ) % 256
v6 = res[v3]
v4 = ( v6 + v4 ) % 256
res[v3] = res[v4]
res[v4] = v6
m = res[(v6 + res[v3]) % 256]
value_or.append(m)
v5 = v5 + 1
print value_or
base_flag = []
for i in range(0,len(value_or)):
base_flag.append(value_or[i] ^ finall_str[i])
print base_flag
base_str = ""
for i in base_flag:
base_str +=chr(i)
print base_str,len(base_str)

sub_401160函数


这是一个变换了密码表的base64加密,基本原则是3*8 = 4*6即将3个8字节的字符,变成4个由6字节编码的字符。

sub_401160(v11)

这里每次取三个字符,在do-while循环中通过或和移位操作将三个字符保存到一个数中。

这里的do-while循环中,每次将v3 右移,取6个字节(0x3f),作为Byte_402138数组的下标(下表中存储的就是源字符),得到加密字符。解密时我们要先提取出byte_402138数组,根据上文得到的字符串解出下标值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
start_address = 0x00402138
end_address = 0x00402179
source_str = []
for i in range(start_address,end_address):
source_str.append(Byte(i))
flag_index = []
for i in base_flag:
flag_index.append(source_str.index(i))
print flag_index,len(flag_index)
flag_letter = []
for i in range(0,len(flag_index)/4):
num = 0
for j in range(0,4):
loca = i*4 + j
print loca,i
num = num + (flag_index[loca] << 6*(3-j))
for j in range(0,3):
m = (num >> (2-j) * 8)&0xff
flag_letter.append(m)
print flag_letter

总结


1
2
3
4
5
flag = ""
for i in flag_letter:
flag += chr(i)
print flag
#flag{y0u_know_rc4_and_base64_ha$}

在题目中依次调用了变种的base64以及rc4算法,在动态调试的时候,将Option Header中的DLL Characteristics该为0,可以实现IDA和OD中的地址分布一样。