西班牙三棵树 发表于 2021-7-21 10:58

Ph0ne1x-100的解题感想

最近在做XCTF新手区的题目,发现了这个Ph0ne1x-100这道题十分的有意思,这道题的难度不高,但是十分适合新手,它的解题思路多种多样,不仅可以动态分析,还可以静态分析,包括Hook,改源码等技巧。
经过最近的研究,我通过这道题也研究出五种解法,其中有一些方法的思想相同,只是角度和难度不同,希望可以给大家提供一些思路。


- 老规矩,安装上去看一下,有一个输入flag的窗口,登录显示Fail:



- 再扔到JEB中打开,这个APK有SO库,主要类就是MainActivity,BuildConfig里面Debug为false,MainActvity中加载了库phcm,接下来是两个类库中的关键函数encrypt和getFlag,MainActvity中有一个getSecret函数通过调用那两个native函数完成加密:



- 分析一下MainActivity中的getSecret函数,这个函数参与运算的外来参数只有一个传入参数,这就意味着如果我们在放入getsecurity之前获得了flag,不经过getsecurity也能直接比较,那么这道题的主要逻辑就是:

内置的字符串和函数通过setflag加工,生成加密密文。输入的flag通过encrypt进行加密生成密文,最后这两个密文是一致的,所以接下来我们就要先通过setflag进行加工,生成密文,再逆向encrpt加密生成flag。






- 接下来看so库的文件:

- getFlag如下,主要是getFlag没有输入,这就意味着这里面使用的是内置的参数进行的加密,看起来挺简单的,但是里面有一个unk_2784并没有被成功解析出来,但是v10又被用于接下来的计算,unk_2784内存处是一些无法理解的东西,这就导致了静态分析陷入了僵局:





- 静态分析失败,接下来转为动态,既然v10进行复制的是内存的一段,我们就在动态运行时截取下来getflag的返回值这一段进行读取,再放入encrypt进行逆向分析,解析出需要我们输入的Flag

动态分析1,借用maketext输出:

- 先找到MainActivity中调用getFlag的函数:



- 拷贝到下方输出Failed的位置,覆盖掉原有的Failed。



- 重新打包,运行,出现中间值:



- encrypt如下,主要就是按位减一:



- 编写程序按位加1得到源程序:

```c++
string ciphertext = "ek`fz@q2^x/t^fn0mF^6/^rb`qanqntfg^E`hq|";
for(int x=0; x < ciphertext.length(); x++){
    ciphertext = ciphertext + 1;
}
cout<<ciphertext;
```
输出:



动态分析二:

使用AndroidStudio进行分析:

- 创建一个remote,对APK拆包出来的APK中的smali文件夹导入到项目中,并设置成resourceroot



- 接下来在虚拟机上运行smali文件,并attach到进程:



- 将断点打在MainActivity的getFlag之后,传入getsecret函数之后,单步跳入查看传入getsecret函数的string值:






动态分析三:

使用Frida框架对So库getFlag()进行So层Hook操作:

Hook代码:

```python
import frida
import sys

jscode = """
Java.perform(function(){
    Interceptor.attach(Module.findExportByName("libphcm.so","Java_com_ph0en1x_android_1crackme_MainActivity_getFlag"),{
      onEnter: function(args) {
      },
      onLeave: function(retval){
            var String_java = Java.use('java.lang.String');
            var args_4 = Java.cast(retval, String_java);
            send("getFlag()输出信息为:"+args_4);
      }
    });
});
"""
def printMessage(message,data):
    if message['type'] == 'send':
      print(' {0}'.format(message['payload']))
    else:
      print(message)

process = frida.get_usb_device().attach('Android_crackme')
script = process.create_script(jscode)
script.on('message',printMessage)
script.load()
sys.stdin.read()
```

输出结果:



静态分析补充:

- 刚刚遇到了无法解决的unk_2748,经过动态分析我们知道了这个每次都是不变的,那么我们可以直接将这40个按int型抄下来,再转化成char返回给v10,代码如下:

```c++
#include<iostream>
using namespace std;
#include<stdlib.h>

int main(){
   
    int v2; // r0
    int v3; // r3
    char *v4; // ST04_4
    char v5; // ST08_1
    int v6; // r1
    int v8; //
    char v9;
    char v10;
    int v11 = {0x2E, 0x36, 0x42, 0x4C, 0x5F, 0xBF, 0xE0, 0x3A, 0xA8, 0xC3, 0x20, 0x63,
    0x89, 0xB7, 0xC0, 0x1C, 0x1D, 0x44, 0xC2, 0x28, 0x7F, 0xED, 0x02, 0x0E, 0x5D, 0x66, 0x8F, 0x98,
    0xB5, 0xB7, 0xD0, 0x16, 0x4D, 0x83, 0xF8, 0xFB, 0x01, 0x43, 0x47, 0x00};
    strcpy(v9,"Hello Ph0en1x");
    //memcpy(v10, &v11, 40);
    //memcpy(v10, &v9, 40);
   
    for(int x=0; x<=40;x++){
      v10 = v11;
    }
    //cout<<strlen(v10);
    v8 = strlen(v9);
    v2 = strlen(v10) - 1;

   while ( v2 > 0 )
    {
      v3 = ((unsigned __int8)v10++ + 1) & 0xFF;
      v4 = &v10;
      v5 = v3 - v10;
      v6 = v2-- % v8;
      v4 = (v9 ^ v5) - 1;
    }
    v10 = (v10 ^ 0x48) - 1;//至此获得了用于比较的字符数组v10,长度为40

    //接下来是反向处理
    string str(v10);
    //cout<<str;

    string ciphertext = "ek`fz@q2^x/t^fn0mF^6/^rb`qanqntfg^E`hq|";
    for(int x=0; x < ciphertext.length(); x++){
      ciphertext = ciphertext + 1;
    }
    cout<<ciphertext;

}
```

动态分析4:

使用Frida对Java层函数getSecret()函数进行Hook操作:

```python
import frida
import sys

jscode = """
if(Java.available){
    Java.perform(function(){
      var MainActivity = Java.use("com.ph0en1x.android_crackme.MainActivity");
      //找到getSecret函数,将输入参数str返回即可
      MainActivity.getSecret.implementation=function(str){
            send("getFlag()输出信息为:"+str);
            return str;   
      }
    });

}
"""
def printMessage(message,data):
    if message['type'] == 'send':
      print(' {0}'.format(message['payload']))
    else:
      print(message)

process = frida.get_usb_device().attach('Android_crackme')
script = process.create_script(jscode)
script.on('message',printMessage)
script.load()
sys.stdin.read()
```





**FLAG:**flag{Ar3_y0u_go1nG_70_scarborough_Fair}

西班牙三棵树 发表于 2021-7-21 11:03

Markdown粘贴的,格式全乱了。。。

朝太阳奔跑 发表于 2021-7-21 18:35

思路学习
页: [1]
查看完整版本: Ph0ne1x-100的解题感想