发布于 

攻防世界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 353567616560343634356738373535653135306232323361603136343962316361673535
*/

第四步 进行高位替换

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}

https://bbs.kanxue.com/thread-274653.htm