攻防世界Mobile - boomshakalaka-3、easy-app
题目 boomshakalaka-3(play the game, get the highest score?)
0x01 静态分析(java层分析 )
查看程序没有加固,使用jadx-gui分析java层代码,发现这是一个飞机大战的游戏:
分析界面UI,左下角是自定义TextView,可以不断定位入口点 com.example.plane.FirstTest
程序使用shareprefrence首先初始化数据,接着使用openai的库SurfaceView来加载界面,最后调用了native层的cocos2dcpp
其中onCreate是在本地文件中存储xml,我们打开目录查看本地xml,发现有两个xml
flag.xml
YmF6aW5nYWFhYQ==YmF6aW5nYWFhYQ==YmF6aW5nYWFhYQ==YmF6aW5nYWFhYQ==
Cocos2dxPrefsFile.xml
测试图片中有没有flag信息:
测试音频中有没有flag信息:
onCreateView则是绘制整个view的部分,至此java层告一段落~
0x02 so层分析
MainActivity加载了cocos2dcppso文件
搜索score分数关键字,找到updateScore函数
DATA的值为"DATA"
switch语句,当分数达到一定值时候就将相应的字符串拼接到origin_str其后,再调用setStringForKey方法
setStringForKey调用了setStringForKeyJNI方法
可见是将参数追加到xml文件后
0x03 解题步骤
分数为100时候写入内容为:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="DATA">MGN0ZntDMGNvUzJkX0FuRHJvMWdz99</string>
<boolean name="isHaveSaveFileXml" value="true" />
<int name="HighestScore" value="100" />
</map>
分数为4000写入的内容为:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="DATA">MGN0ZntDMGNvUzJkX0FuRHJv MWdz99Z ntDMGNvUzJkX0FuRHJv MWRzdz99Z ntDMGNvUzJkX0FuRHJvMWdz 99Z
ntDMGNvUzJkX0FuRHJvMWRfRzRVdz
99Z
ntDMGNvUzJkX0FuRHJvMW
</string>
<boolean name="isHaveSaveFileXml" value="true" />
<int name="HighestScore" value="41000" />
</map>
发现MGN0每次打开app时候会写入
ZntDMGNvUzJkX0FuRHJv每开一局后会写入一次
dz99每次游戏结束后会写入一次
而夹在中间的就是得分时写入的
截取每个分数添加的字符串MWRfRzBtRV9Zb1VfS24w
拼接好后为MGN0ZntDMGNvUzJkX0FuRHJvMWRfRzBtRV9Zb1VfS24wdz99
base64解得0ctf{C0coS2d_AnDro1d_G0mE_YoU_Kn0w?}
则flag为0ctf{C0coS2d_AnDro1d_G0mE_YoU_Kn0w?}
题目 easy-app(太湖杯easy-app)
0x01 行为分析,静态分析
分析apk未加固,查看java层代码,发现处理都写到so文件里面
我们通过check 定位到MainActivity_Check 方法中,
0x02 动态分析
双击这个so,即可查看so库中的函数
找到目标函数Java_com_example_myapplication_MainActivity_check(),继续双击这个函数,即可查看对应的汇编代码,
点击tab键对其进行反编译为伪代码
参数a1其实为_JNIEnv *env,我们导入jni.h文件,然后改变下它的数据类型
对获取v3(即输入的字符串开始调试)的地址处下断点
通过静态分析,可以得知我们要输入的字符串长度为38位,且前5位为"flag{",最后一位为“}”
flag{ //5
0123456789 //10
abcdefghijklmnopqrstuv //22
} //1
这道题的代码量很大,完全静态分析的话回很耗时间。
这里用ida动态调试的方式。
起了个大怪,重启debug一次,发现原来是输入的时候少数如了个"d",额…麻了
重新来下首先将接收到的字符串,然后将其前5个字符存放到栈地址上。
然后比较输入的前五个字符是否和"flag{"一致。
然后比较输入的最后一个字符是否和"}"一致。
然后再去判断输入的字符串的长度是否为38即0x26
接下来经过了中间的32位字符串经过高位替换的函数7008D55D50()处理
处理之前
0123456789abcdef 30313233343536373839616263646566
ghijklmnopqrstuv 6768696a6b6c6d6e6f70717273747576
处理之后
60616263646566676879717273747576-----> `abcdefghyqrstuv
3738393a3b3c3d3e6f30616263646566-----> 789:;<=>o0abcdef
然后再接下来就是个变异的tea算法处理
处理之后的字符串后面又经过了变异的base64的函数处理。(不小心直接run结束了,下次记得多打些断点。)
然后我们跟进去base64函数
通过动态调式可以得知这个base64函数并不是标准的base64算法,这次apk中将table表给替换成了"abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ+/",并且每加密三个明文得到4个密文,有将前3三个密文依次左移2个字符。
下面为
#模拟输入
flag{0123456789abcdefghijklmnopqrstuv}
#高位移换以及变异tea算法加密后的前三个密文
71 FA BD 0b1110001 0b11111010 0b10111101
#base64加密前获取偏移
01110001 11111010 10111101
011100 01 1111 1010 10 111101
011100 011111 101010 111101
0x1c 0x1f 0x2a 0x3d
#table表
abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ+/
#正常加密
hex(ord(str[0x1c]))
'0x23'
hex(ord(str[0x1f]))
'0x5e'
>>> hex(ord(str[0x2a]))
'0x47'
hex(ord(str[0x3d]))
'0x5a'
正常应该加密成这样
0x235e47 5a
123->231
但实际加密后是这样的
0x5e4723 5A
即这里最后一位是不变的,前三位发现都依次进行了循环右移2位
加密后的结果如下
5E 47 23 5A 68 67 62 2A 79 76 4E 4C 68 29 77 6C ^G#Zhgb*yvNLh)wl
77 43 63 6F 70 64 24 66 40 73 74 4B 6D 74 24 62 wCcopd$f@stKmt$b
77 7A 46 71 58 45 4A 2F 59 51 76 3D 00 00 00 00 wzFqXEJ/YQv=....
结果如果和"e)n*pNe%PQy!^oS(@HtkUu+Cd$#hmmK&ieytiWwYkIA="相等的话才会输出Congratulations,you found it!!!
所以这里需要我们进行逆向。
首先需要将"e)n*pNe%PQy!^oS(@HtkUu+Cd$#hmmK&ieytiWwYkIA="以4字节为单位,将每个单位的前三个字符依次右移2位。然后将其再进行base64解密,在解密的时候需要替换table为
abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ+/
然后将base64解密后的结果再依次进行变异的tea算法进行解密。
tea算法解密后还需要将结果以16字节为单位进行高位移换操作,便是最终的flag了。
0x03 编写脚本
第一步
#coding:utf8
base64_out="e)n*pNe%PQy!^oS(@HtkUu+Cd$#hmmK&ieytiWwYkIA="
print(len(base64_out))#44
base64_ok=""
temp=""
for i in range(len(base64_out)):
if i%4==0:
temp=base64_out[i:i+4]
#print(temp)
base64_ok+=temp[2]
base64_ok+=temp[0]
base64_ok+=temp[1]
base64_ok+=temp[3]
else:
print("")
print(base64_ok)#ne)*epN%yPQ!S^o(t@Hk+UuC#d$hKmm&yietwiWYAkI=
第二步 base64解密
import base64
import string
import binascii
base64_ok="ne)*epN%yPQ!S^o(t@Hk+UuC#d$hKmm&yietwiWYAkI="
outtab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
intab = "abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
teaout=(base64.b64decode(base64_ok.translate(base64_ok.maketrans(intab,outtab))))#b'4H\xe1\x10\xfc^c=\x1a\xd9\xf3\xa2M\xba\xca\xfb\x85&p7G\xb8\xc3 `\x81\x13X\x8e\xbc\x90\xab'
print(teaout)
print(type(teaout))
tea_out=binascii.hexlify(teaout).decode('utf-8')#https://www.delftstack.com/zh/howto/python/python-convert-hex-to-byte/
print(tea_out)#3448e110fc5e633d1ad9f3a24dbacafb8526703747b8c320608113588ebc90ab
第三步 tea解密
#include<stdio.h>
#include <string.h>
void decrypt(unsigned int *v, unsigned int *k)
{
unsigned int v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i;
unsigned int delta = 0x9e3779b9;
unsigned int k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++)
{
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
}
v[0] = v0;
v[1] = v1;
}
int main(){
int i=0;
unsigned int v[2] = {0, 0}, k[4] = {66, 55, 44, 33};
char tea_out[64]="\x34\x48\xe1\x10\xfc\x5e\x63\x3d\x1a\xd9\xf3\xa2\x4d\xba\xca\xfb\x85\x26\x70\x37\x47\xb8\xc3\x20\x60\x81\x13\x58\x8e\xbc\x90\xab";
for(i=0;i<strlen(tea_out);i+=8){
unsigned int *v=(unsigned int *)&tea_out[i];
//printf("%x %x\n", v[0], v[1]);
decrypt(v, k);
printf("%x %x\n", v[0], v[1]);
}
return 0;
}
/*
36346065 38673534
65353537 62303531
61333232 34363160
63316239 35356761
即6560343634356738373535653135306232323361603136343962316361673535
*/
第四步 进行高位替换
tea="6560343634356738373535653135306232323361603136343962316361673535"
tea1=tea[::2]#从0都结束 每step2进行取值 https://blog.csdn.net/Evan123mg/article/details/49232089
tea2=tea[1::2]#从1都结束 每step2进行取值
print(tea1)#6633336333363336 3336633336366633
print(tea2)#50464578755515022231016492131755
temp=tea1[16:]+tea1[:16] #3336633336366633 6633336333363336 高16位和低16位换位置
print(temp)
flag=""
for i in range(len(temp)):
flag+=temp[i]+tea2[i]
print(flag)#3530346664353738376535656165303262623331303166343932316331373565
print(bytes.fromhex(flag))#b'504fd5787e5eae02bb3101f4921c175e'
第五步 拼接测试
然后再拼接上面的"flag{“和”}"就是正确的输入
flag{504fd5787e5eae02bb3101f4921c175e}