声明:本篇的主要作者 Fish(CCSR小组重要成员之一)

一道中等难度的Reverse题,由蓝鲸安全平台提供。

题目:标准的用户名–密码类型。地址:https://pan.baidu.com/s/12FfpVJ81hxaM71fk1qV0zA

知识点:IDA动态调试

目录

  1. 题目解析
    1. 函数sub_400C9A
    2. 函数sub_400C41
    3. 函数sub_400CDD
    4. 函数sub_4008F7
    5. 函数sub_400977
    6. 函数sub_400876
  2. 总结

题目解析

直接运行程序,要求输入用户名和密码:

使用IDA反编译该程序的main函数,结果如下图:

  • 第一部分为默认自动输出;
  • 第二、四部分为干扰程序执行的sleep函数,可以利用IDA动态调试,在循环开始前下断点,然后在循环结束处set IP的方式跳过;
  • 第三部分获得输入的Username和Password;
  • 第五部分为破解程序的主要关注点。

函数sub_400C9A

该函数通过for循环获得Username的长度,并将长度赋值给i(如果长度大于等于50,则i=50),执行sub_400C41函数。

函数sub_400C41

该函数传入的参数为之前输入的Username的长度,如果不符合4个if判别式的要求,则会输出invalid username or password并退出。

可以通过编写C程序来判断什么数满足该条件(测试了0~99):

python脚本:可知Username的长度必须为8或12。

1
2
3
for i in range(100):
if (4 * (i >> 2) == i and 4 * (i >> 4) != i >> 2 and (i>>3) and not (i>>4)):
print i

此外,也可以直接分析该判别式:

  • 第四个条件可知a1长度不能超过4位,在1000~1111之间;
  • 第一个条件可知a1后两位为00,a1位双数,且是4的倍数;
  • 第二个条件可知a1的前两位不能全为0,a1起码大于3;
  • 第三个条件可知a1的最高位为1;
  • 所以,a1只能为1000或1100,也即Username的长度必须为8或12。

函数sub_400CDD

通过动态调试的方法,可以看出:

  • 当Username为8位时,v4对应其高4位,v3对应其低4位,v2为0;
  • 当Username为12位时,v4对应其高4位,v3对应其中4位,v2为其低4位;
  • 其原因是已经按照每4位进行了赋值。

然后通过列方程的方式解出v4、v3、v2的值:

  • v4-v3+v2=1550207830
  • v3+3*(v2+v4)=12465522610
  • v2*v3=3651346623716053780

②-3*①即可得到v3的值,带入③得到v2的值,代回①得到v4:

  • V4=1635017059,即0x61746163,对应ascii码为atac

  • V3=1953724780,即0x7473796C,对应ascii码为tsyl

  • V2=1868915551,即0x6F65635F,对应ascii码为oec_

  • 因为是小端序,所以Username为catalyst_ceo。

函数sub_4008F7

这个函数比较简单:如果Username出现了既非_,又非小写英文字母的字符,则报错并停止运行。实际上,如果上个函数已经求出了Username,这个函数就起不到限制效果。

函数sub_400977

这个函数比较长,但后面部分只是简单的重复,所以这里只分析前面部分:

  • 通过计算for循环的限制条件,可知password每位的ascii码必须位于(47,57](64,90](96,122],查ascii码表可知password为数字或大小写字母;

  • Srand()函数生成随机数种子,通过分析可知,随机种子是根据Username生成的;

  • Rand()函数生成一个随机数,但由于之前srand()函数已指定了随机数种子,所以每次执行时,生成的随机数是一样的,可以通过IDA动态调试的方法得到十个随机数的值,从而算出v2~v11(v2到v11从高到低各对应password的4位),得到password;

  • 右上角的RAX保存的即是rand()得到的随机数0x684749,与后面比较时用到的0x55EB052A相加,就得到了password的前四个字母0x56534C73,也即VSLs(因为小端序,所以password开头四个字母实际为sLSV)。

  • 后面部分原理与此相同(相加结果如果溢出,移除最高位即可),最终得到password为:sLSVpQ4vK3cGWyW86AiZhggwLHBjmx9CRspVGggj

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#username
userin = [1635017059,1953724780,1868915551]
username = ''
for i in userin:
username+=i.to_bytes(4,'little').decode('utf-8')
print(username)

#password
passin = [1441465642,251096121,3424529764,
3350644469,647240698,638382323,
282381039,3328632868,4236854684,
605226810]

randp = [0x684749,0x673CE537,0x7B4505E7,
0x70A0B262,0x33D5253C,0x515A7675,
0x596D7D5D,0x7CD29049,0x59E72DB6,
0x4654600D]
password = ''
for i in range(len(randp)):
s = passin[i] +randp[i]
s %= 0x100000000
password += s.to_bytes(4,'little').decode('utf-8')
print(password)

函数sub_400876

该函数用于输出最后的flag。输入正确的Username和password后,即可得到flag。

总结

这道题整体分析下来并不难,只是看着复杂些而已,在调试过程中需要绕过一些sleep()函数,其他的就是需要耐心调试了。

小组成员Fish,第一次写writeup,大家多多鼓励。