1625-5 王子昂 总结《2018年1月23日》 【连续第480天总结】
A. 结营赛xkey、若隐若现
B.
xkey
在被DecodeMe绕的晕头转向以后无奈换了一个题目做,简单明了地把onclick摆在脸上、调用JNI方法真是让我感动的不行
一安装就因为模拟器的API版本过低,报错了╮(╯_╰)╭只好Patch掉MiniApi再重打包
上来能看到反调AntiDebug和签名校验SignCheck两个方法
直接把exit()函数给Patch掉就完了,本来想PatchAntiDebug的,但是崩了╮(╯_╰)╭后来去看so发现这两个方法里面还有修改key的代码,反正IDA附加上可以直接绕过,所以干脆不搞了
SignCheck里修改app_signature_for_sign的部分
AntiDebug里修改aes_key的部分
为什么我会一下就想到呢……因为我也一直想这么做来着(望天
对付反调试最简单的方法就是直接Patch,那么就偷偷在反调试的方法中藏一点对关键代码的变换嘛!这样直接Patch就会被坑咯
虽然实际逆起来感觉还是挺明显的(:з」∠)交叉引用一下就发现了
这种偷偷摸摸改变量的坑上过一次当以后就会习惯性查看下交叉引用了
这里的赋值太复杂了不想看,反正就是对两个key进行了一些操作嘛。主要关注跟输入有关的方法:FuzzMath
它里面又对app_signature_for_sign里做了一些什么这样那样的操作,我大概扫了一下好像有调用方法获得APK的签名、md5之类的东西~
跟输入有关的只有这一段
do
{ *((_BYTE *)v11 + i) = aes_key[i] ^ *(_BYTE *)(v6 + i) ^ *((_BYTE *)&v10 + i); ++i; }
while ( i != 16 );
发现是循环将输入和aes_key和另一个跟签名有关的字符串异或了
动态调试在这里下断,观察寄存器中的地址,依次对应查找,找了了两个异或的值
key2 = [51, 49, 55, 52, 49, 98, 101, 54, 49, 98, 56, 54, 48, 48, 53, 100]
aes_key = “f0d856b6aa926010”
最后解出来再把输入跟这两个对应值异或就行了,长度都是16,很简单
继续往后分析,发现把异或结果送入了math_hash这个函数
这个函数中虽然看起来有点复杂,不过只进行了一个变换
v23是新开辟的内存空间,长度为16
操作代码如下
乍一看这个伪代码理解起来有点困难,不过反正就这么些东西
调试一下,注意观察v13和v14的变化值,跟着走一个循环以后就能发现规律了
最后与一个硬编码数组比较,Dump下来
看起来比较像哈希,数据混杂的太严重了
由于check方法为4位一组,因此可以接受爆破(穷举空间为97^4)
值得一提的是x86的so反编译出来会有很多浮点数运算,这个时候可以看一看ARM的so。赛前出题聊天的时候保证学长提了一句,x86汇编由于在移动平台上需要转换,所以可能会出现一些乱七八糟很神奇的代码。而ARM上就比较直接。这直接决定我逆的很顺利~感谢保证学长~
python3代码如下
import string
key = "f0d856b6aa926010"
key2 = [51, 49, 55, 52, 49, 98, 101, 54, 49, 98, 56, 54, 48, 48, 53, 100]
n = [29, 634, 1144, 3288, 125, 406, 1591, 2652, 49, 776, 1565, 3784, 94, 638, 1874, 2860, 825765939]
key31 = [1,0,0,0]
key32 = [2,6,0,0]
key33 = [3,7,11,0]
key34 = [4,8,12,16]
key3 = [key31,key32,key33,key34]
dic = string.digits + string.ascii_letters + string.punctuation
def fir(x, i):
return x^ord(key[i])^key2[i]
def sec(input, i):
p = 0
for j in range(4):
p += input[j]*key3[i][j]
# print(p)
return p
def main1(i):
for i4 in dic:
q4 = fir(ord(i4), i + 3)
for i3 in dic:
q3 = fir(ord(i3), i + 2)
for i2 in dic:
q2 = fir(ord(i2), i + 1)
for i1 in dic:
q1 = fir(ord(i1), i+0)
if(sec((q1,q2,q3,q4), 0)==n[i+0]):
if(sec((q1,q2,q3,q4), 1)==n[i+1]):
if (sec((q1, q2, q3, q4), 2) == n[i+2]):
if (sec((q1, q2, q3, q4), 3) == n[i+3]):
return (i1, i2, i3, i4)
#HappyNY3ar8yXKey
for i in range(4):
print("".join(main1(i*4)))
比赛的时候没空细想,当时在调试上浪费了太多时间~而且由于API的问题把APK进行了Patch,导致签名不同。而aes_key和key2都跟签名有关,幸亏key31是[1, 0, 0, 0],即n[0] == input[0] ^ aes_key[0] ^ key2[0],为一一对应关系,可以很轻松地校验,让我能够确认是keydump错了。不然我真是死不瞑目OTZ
爆破出来4组的时间大概在十分钟左右,比赛中还算可以接受
结束后交流发现这就是个矩阵乘法啊~
拿纸笔写一下该矩阵的逆矩阵,然后再乘结果矩阵即可
当然,我这么懒不可能从床上爬起来去拿纸笔的
python中有numpy这个库可以进行矩阵相关运算
# 因为数组录入的时候是转置状态,所以.T取得一个转置,.I取得一个逆矩阵,.A取得矩阵的二维数组表示
ori = numpy.mat(key3).T.I
result = numpy.mat([n[x*4:x*4+4] for x in range(4)])
flag = (result*ori).A
for i in range(16):
print(chr(fir(int(flag[i//4][i%4]+0.5), i)), end='')
这里+0.5是因为python对float转int的方式是去尾法,然后直接输出又他喵的把无限循环小数给直接近似显示了
a = flag[0][1]
print(a)
print(type(a))
print(int(a))
print()
b = numpy.float64(96.0)
print(b)
print(type(b))
print(int(b))
96.0
<class 'numpy.float64'>
9596.0
<class 'numpy.float64'>
96
我真是哔了狗了,最后用numpy.asscalar()才得到了实际值:95.99999999
(╯‵□′)╯︵┻━┻
反正最后这样矩阵一转就能得到flag啦~
若隐若现
说实话,对Patch的考察我个人觉得还是挺有新意的。不过除了Patch以外就没啥东西了所以略显单薄~
安装发现没有输入的地方,只有一个按钮。
于是进行反编译,在onCreate中发现
((Button)v1).setVisibility(4);
((EditText)v3).setVisibility(4);
很明显,这是设置按钮和输入框可用性的代码。将其Patch成1,或者删去都可以
之后调用了JNI中的plus2方法,并把”flag{“, input, “}”三个参数传入
于是跟入so查看
if ( strlen(a1) == 8 )
{
if ( strlen(v4) == 5 )
{
if ( strlen(v5) == 1 )
这里对长度进行了校验,以及v4和v5实际上是倒了,可以通过Patch或调整寄存器的方法来通过
因为我做这题的时候刚怼完xkey,距离比赛结束只有40分钟了,所以没空再Patch_(:з」∠)_赶紧动态附加上把这里过了
前面进行了一堆字符串连接、decode后再复制等等操作,然而并没有什么卵用,跟Input没关系,直接不看
注意最后input直接跟v17字符串的第(31*i)&0x3f个字符比较,直接下断然后把v17指向的字符串dump下来再手动提取对应字符即可
for i in range(8):
print(s[(31*(i+1))&0x3f], end='')
冬令营结束啦,学到了很多东西~
有酒有肉多兄弟,大家改日再会~
C. 明日计划
返乡(。