小菜鸟一枚 发表于 2022-4-30 15:42

学破解第195天,《攻防世界mobile练习区Android2.0》学习

本帖最后由 小菜鸟一枚 于 2022-4-30 16:16 编辑

前言:

  坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:(https://www.52pojie.cn/thread-1582287-1-1.html)

**立帖为证!--------记录学习的点点滴滴**

## 0x1 正向分析
  1.将apk拖进jadx工具反编译,可以看到调用这个函数JNI.getResult,返回值为0就提示wrong。

![https://s1.ax1x.com/2022/04/30/LzLJcd.png](https://s1.ax1x.com/2022/04/30/LzLJcd.png)

  2.因为getResult是JNI函数,所以需要看一看so文件,找到这个函数,反汇编代码如下:
```
bool __fastcall Java_com_example_test_ctf03_JNI_getResult(int a1, int a2, int a3)
{
_BOOL4 v3; // r4
const char *v4; // r8
char *v5; // r6
char *v6; // r4
char *v7; // r5
int i; // r0
int j; // r0

v3 = 0;
v4 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
if ( strlen(v4) == 15 )
{
    v5 = (char *)malloc(1u);
    v6 = (char *)malloc(1u);
    v7 = (char *)malloc(1u);
    Init(v5, v6, v7, v4, 15);
    if ( !First(v5) )
      goto LABEL_6;
    for ( i = 0; i != 4; ++i )
      v6 ^= v5;
    if ( !strcmp(v6, a5) )
    {
      for ( j = 0; j != 4; ++j )
      v7 ^= v6;
      v3 = strcmp(v7, "AFBo}") == 0;
    }
    else
    {
LABEL_6:
      v3 = 0;
    }
}
return v3;
}
```

  3. 有些乱,改一下函数的形参类型:JNIEnv *env, jobject obj, jstring str,再看看伪代码:
```
ibool __fastcall Java_com_example_test_ctf03_JNI_getResult(JNIEnv *env, jobject obj, jstring str)
{
_BOOL4 v3; // r4
const char *v4; // r8
char *v5; // r6
char *v6; // r4
char *v7; // r5
int i; // r0
int j; // r0

v3 = 0;
v4 = (*env)->GetStringUTFChars(env, str, 0);
if ( strlen(v4) == 15 )
{
    v5 = (char *)malloc(1u);
    v6 = (char *)malloc(1u);
    v7 = (char *)malloc(1u);
    Init(v5, v6, v7, v4, 15);
    if ( !First(v5) )
      goto LABEL_6;
    for ( i = 0; i != 4; ++i )
      v6 ^= v5;
    if ( !strcmp(v6, " 5-\x16a") )
    {
      for ( j = 0; j != 4; ++j )
      v7 ^= v6;
      v3 = strcmp(v7, "AFBo}") == 0;
    }
    else
    {
LABEL_6:
      v3 = 0;
    }
}
return v3;
```

  4.先看GetStringUTFChars将我们输入的java类型的str变成c语言形式的字符串,然后判断长度是否等于15,否则返回0,接着v5,v6,v7分配了一个字符的大小,执行Init函数,进去看一看这个函数是干啥的:
```
int __fastcall Init(int result, char *a2, char *a3, const char *a4, int a5)
{
int v5; // r5
int v6; // r10
int v7; // r6

if ( a5 < 1 )
{
    v6 = 0;
}
else
{
    v5 = 0;
    v6 = 0;
    do
    {
      v7 = v5 % 3;
      if ( v5 % 3 == 2 )
      {
      a3 = a4;
      }
      else if ( v7 == 1 )
      {
      a2 = a4;
      }
      else if ( !v7 )
      {
      ++v6;
      *(_BYTE *)(result + v5 / 3u) = a4;
      }
      ++v5;
    }
    while ( a5 != v5 );
}
*(_BYTE *)(result + v6) = 0;
a2 = 0;
a3 = 0;
return result;
}
```

  5.代入去想(注意这里要把result看成*a1),15位字符串,do while循环15次,模3赋值a1,a2,a3,也就是将我输入的字符串按如下方式处理:
```
a1就是str,str,str,str,str
a2就是str,str,str,str,str
a3就是str,str,str,str,str

也就是对应getResult函数中的v5,v6,v7
```

  6.接着看第一个if ( !First(v5) ),这里是将v5进行了4次循环处理,将自身乘以2然后异或0x80,v5通过前面的分析,可以知道一共有5位,这里只对前4位进行了处理,处理后的v5必须等于"LN^dl"。
```
bool __fastcall First(char *a1)
{
int i; // r1

for ( i = 0; i != 4; ++i )
    a1 = (2 * a1) ^ 0x80;
return strcmp(a1, "LN^dl") == 0;
}
```

  7.再回来,又看到一个4次的for循环,这一次处理的是v6,将自身与v5的每一位进行异或,那串字符串反编译有点问题,看data进行比较。
```
    for ( i = 0; i != 4; ++i )
      v6 ^= v5;
    if ( !strcmp(v6, " 5-\x16a") )
      
      .rodata:00002888 unk_2888      DCB 0x20                ; DATA XREF: Second(char *,char *)+18↑o
      .rodata:00002888                                       ; Second(char *,char *)+1C↑o ...
      .rodata:00002889               DCB 0x35 ; 5
      .rodata:0000288A               DCB 0x2D ; -
      .rodata:0000288B               DCB 0x16
      .rodata:0000288C               DCB 0x61 ; a
      .rodata:0000288D               DCB    0
```

  8.最后又是一组循环,v7的处理和v6一样,也是将自身与v6异或,同样只处理前4位,然后和"AFBo}"比较。
```
for ( j = 0; j != 4; ++j )
      v7 ^= v6;
      v3 = strcmp(v7, "AFBo}") == 0;
```

## 0x2 逆向还原
  1.刚刚通过正向分析理清了程序的流程,现在就来逆向还原一下字符串,先把v5弄出来,按数学的等式交换法则反过来即可:
```
void getV5(char *v5)
{
    int i;
    char str = "LN^dl";
    ;
    for (i = 0; i < 4; i++)
    {
      v5 = (str ^ 0x80) / 2;
    }
    v5 = str;
    v5 = 0;
}
```

  2.把v6弄出来,还是一样的方法,就是v6这里数据定义的时候不能直接定义成字符串,得换成16进制数组,因为含有不可见字符:
```
void getV6(char *v6)
{
    int i;
    char str = "LN^dl";
    char str2 = {0x20,0x35,0x2d,0x16,0x61,0};
   
    for (i = 0; i < 4; i++)
    {
      v6 = str2^str;
    }
    v6 = str2;
    v6 = 0;
}
```

  3.v7和v6的处理方式都一样,直接CTRL+C、V:
```
void getV7(char *v7)
{
    int i;
    char str2 = {0x20,0x35,0x2d,0x16,0x61,0};
    char str3 = "AFBo}";
   
    for (i = 0; i < 4; i++)
    {
      v7 = str3^str2;
    }
    v7 = str3;
    v7 = 0;
}
```

  4.最后需要将v5,v6,v7组合成原始的15位字符串,通过前面的分析,可以知道还原需要按照v5,v6,v7的顺序按位拼接,得到最终的代码:
```
#include <stdio.h>

void getV5(char *v5)
{
    int i;
    char str = "LN^dl";
    ;
    for (i = 0; i < 4; i++)
    {
      v5 = (str ^ 0x80) / 2;
    }
    v5 = str;
    v5 = 0;
}

void getV6(char *v6)
{
    int i;
    char str = "LN^dl";
    char str2 = {0x20, 0x35, 0x2d, 0x16, 0x61, 0};

    for (i = 0; i < 4; i++)
    {
      v6 = str2 ^ str;
    }
    v6 = str2;
    v6 = 0;
}

void getV7(char *v7)
{
    int i;
    char str2 = {0x20, 0x35, 0x2d, 0x16, 0x61, 0};
    char str3 = "AFBo}";

    for (i = 0; i < 4; i++)
    {
      v7 = str3 ^ str2;
    }
    v7 = str3;
    v7 = 0;
}

int main()
{
    char v5;
    char v6;
    char v7;
    char flag;

    getV5(v5);
    getV6(v6);
    getV7(v7);

    int j= 0;
    for (int i = 0; i < 5; i++)
    {
      flag = v5;
      flag = v6;
      flag = v7;

      j=j+3;
    }

    printf("%s", flag);

    return 1;
}
```

  5.代码太臃肿,优化一下,因为每组字符串都是5个,循环次数都是4,所以可以进行合并,将每一个处理过程放在循环里,最后拼上未处理的字符串,得到优化后的代码:
```
#include <stdio.h>

int main()
{
    char str = "LN^dl";
    char str2 = {0x20, 0x35, 0x2d, 0x16, 0x61, 0};
    char str3 = "AFBo}";
    char flag;

    int i = 0;
    int j = 0;
    for (i = 0; i < 4; i++)
    {
      flag = (str ^ 0x80) / 2;
      flag = str2 ^ str;
      flag = str3 ^ str2;

      j = j + 3;
    }

    flag = str;
    flag =str2;
    flag = str3;
      flag = '\0';

    printf("%s", flag);

    return 1;
}
```

  6.运行程序,得到flag:
![https://s1.ax1x.com/2022/04/30/OSGgMT.png](https://s1.ax1x.com/2022/04/30/OSGgMT.png)

## 0x3 总结
  1.先建立正向的逻辑关系,执行流程,再逆向反推出输入。

  2.活用等式交换,a1 = (a2 \* 2) ^ 80,可以变成a2 \* 2 = a1 ^ 80,然后就可以反过来把a2当做未知数求解。

  3.分析过程中一定要细心,例如我踩的一些坑:刚开始把流程弄错了,折腾老半天,最后优化后循环的时候,应该是4次,我直接5次.....,

夜步城 发表于 2022-8-7 21:11

evill 发表于 2022-5-1 21:53
这个等式没能理解,^是指数的意思吧,这样变换是不是有问题?

就是说其中已知的是a1,反过来求a2,其中^是按位异或。异或有个特性就是二次异或可以得原始数据,如 a^b=c,a^c=b,b^c=a,就是这样的,可以弄两个数字试一试

Nuwand4 发表于 2023-3-28 19:08

改一下函数的形参类型:v4 = (*env)->GetStringUTFChars(env, str, 0);, 这边是怎么看出来的
我的ida也是这样一串莫名其妙的v4 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);

hackerbob 发表于 2022-4-30 15:50

菜鸟已成大神,在下佩服佩服

jimilar 发表于 2022-4-30 18:35

不年轻了,但是也想看看能不能跟上前进的步伐

glorymusic 发表于 2022-4-30 19:44

学习了,感谢楼主分享~!~!~!~

0mengzi0 发表于 2022-4-30 22:29

Mark一下

13729181580 发表于 2022-4-30 22:42

感谢分享

deepgo 发表于 2022-4-30 22:46


感谢楼主分享

X1O1 发表于 2022-4-30 23:44

感谢楼主的分享~

Ckopoer 发表于 2022-5-1 08:37

帮顶,学习一下

xtkj 发表于 2022-5-1 09:02

学习了。。。
页: [1] 2 3 4
查看完整版本: 学破解第195天,《攻防世界mobile练习区Android2.0》学习