首页
社区
课程
招聘
[原创]bugku CTF安卓逆向LoopAndLoop(阿里CTF)
2024-2-9 23:49 9031

[原创]bugku CTF安卓逆向LoopAndLoop(阿里CTF)

2024-2-9 23:49
9031

bugku LoopAndLoop(阿里CTF)

资料来源

来自bugku的LoopAndLoop(阿里CTF)

工具

  • JADX 用于解包和分析APK
  • IDA 7.5 SP3 逆向分析so

简单看一下

拿到压缩包解压,看到文件时APK格式,直接拖到夜神模拟器看看。

简单的测试了下如下

反编译,提取算法

将APK拖到JADX,找到MainActivity,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class MainActivity extends AppCompatActivity {
    public native int chec(int i, int i2);
 
    public native String stringFromJNI2(int i);
 
    /* JADX INFO: Access modifiers changed from: protected */
    @Override // android.support.v7.app.AppCompatActivity, android.support.v4.app.FragmentActivity, android.support.v4.app.BaseFragmentActivityDonut, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        final TextView tv1 = (TextView) findViewById(R.id.textView2);
        final TextView tv2 = (TextView) findViewById(R.id.textView3);
        final EditText ed = (EditText) findViewById(R.id.editText);
        button.setOnClickListener(new View.OnClickListener() { // from class: net.bluelotus.tomorrow.easyandroid.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View v) {
                String in_str = ed.getText().toString();
                try {
                    int in_int = Integer.parseInt(in_str);
                    if (MainActivity.this.check(in_int, 99) == 1835996258) {
                        tv1.setText("The flag is:");
                        tv2.setText("alictf{" + MainActivity.this.stringFromJNI2(in_int) + "}");
                        return;
                    }
                    tv1.setText("Not Right!");
                } catch (NumberFormatException e) {
                    tv1.setText("Not a Valid Integer number");
                }
            }
        });
    }
 
    @Override // android.app.Activity
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }
 
    @Override // android.app.Activity
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
 
    public String messageMe(String text) {
        return "LoopOk" + text;
    }
 
    public int check(int input, int s) {
        return chec(input, s);
    }
 
    public int check1(int input, int s) {
        int t = input;
        for (int i = 1; i < 100; i++) {
            t += i;
        }
        return chec(t, s);
    }
 
    public int check2(int input, int s) {
        int t = input;
        if (s % 2 == 0) {
            for (int i = 1; i < 1000; i++) {
                t += i;
            }
            return chec(t, s);
        }
        for (int i2 = 1; i2 < 1000; i2++) {
            t -= i2;
        }
        return chec(t, s);
    }
 
    public int check3(int input, int s) {
        int t = input;
        for (int i = 1; i < 10000; i++) {
            t += i;
        }
        return chec(t, s);
    }
 
    static {
        System.loadLibrary("lhm");
    }
}

安卓层面主要的逻辑

1
2
3
4
5
6
7
int in_int = Integer.parseInt(in_str);
if (MainActivity.this.check(in_int, 99) == 1835996258) {
    tv1.setText("The flag is:");
    tv2.setText("alictf{" + MainActivity.this.stringFromJNI2(in_int) + "}");
    return;
}
tv1.setText("Not Right!");

以上代码逻辑

  • 将输入的字符串转int
  • 将int传入check函数,得到的int必须等于1835996258

双击check函数,一层层引用可以找到最终调用的是

1
public native int chec(int i, int i2);

提取so文件,提取算法

JADX保存文件到本地,直接将so文件拖入32位IDA7.5版本,其支持arm F5的版本也行。

查看Exports 页面,下面时部分导出函数

1
2
3
4
Name    Address Ordinal
Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec   00000E8C   
Java_net_bluelotus_tomorrow_easyandroid_MainActivity_stringFromJNI2 00000F18   
....

双击函数Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec,直接跳转到汇编代码页面,F5就可得到逻辑代码。简单的修改了下第一个参数的数据类型,代码逻辑看着更清晰了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __fastcall Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec(JNIEnv *jenv, int a2, int t, int s)
{
  jclass v5; // r7
  int result; // r0
  int v10[9]; // [sp+1Ch] [bp-24h] BYREF
 
  v5 = (*jenv)->FindClass(jenv, "net/bluelotus/tomorrow/easyandroid/MainActivity");
  v10[0] = _JNIEnv::GetMethodID(jenv, v5, "check1", "(II)I");
  v10[1] = _JNIEnv::GetMethodID(jenv, v5, "check2", "(II)I");
  v10[2] = _JNIEnv::GetMethodID(jenv, v5, "check3", "(II)I");
  if ( s - 1 <= 0 )
    result = t;
  else
    result = _JNIEnv::CallIntMethod(jenv, a2, v10[2 * s % 3], t, s - 1);//
                                                // 99 * 2 % 3 = 0
                                                // 98 * 2 % 3 = 1
                                                // 97 * 2 % 3 = 2
                                                // 96 * 2 % 3 = 0
  return result;
}

可以看到so文件的代码又去调用了UI层的逻辑。

还原代码

结合MainActivity中的代码和so中的代码,整理chec函数逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
int chec(int t, int s);
 
int check1(int input, int s)
{
    int t = input;
    for (int i = 1; i < 100; i++)
    {
        t += i;
    }
    return chec(t, s);
}
 
int check2(int input, int s)
{
    int t = input;
    if (s % 2 == 0)
    {
        for (int i = 1; i < 1000; i++)
        {
            t += i;
        }
        return chec(t, s);
    }
    for (int i2 = 1; i2 < 1000; i2++)
    {
        t -= i2;
    }
    return chec(t, s);
}
 
int check3(int input, int s)
{
    int t = input;
    for (int i = 1; i < 10000; i++)
    {
        t += i;
    }
    return chec(t, s);
}
 
typedef int (*pfn_check)(int, int);
 
pfn_check check_fns[3] = {
    check1, check2, check3};
 
int chec(int t, int s)
{
    if (s - 1 <= 0)
    {
        /* code */
        return t;
    }
    else
    {
        return check_fns[2 * s % 3](t, s - 1);
    }
}

发现规律

既然是个int值,从0开始穷举就可以了。但是这绝对不是唯一办法。

先看看前0-99的输出吧!

1
2
3
4
5
for ( i = 0; i < 100; i++)
   {
       /* code */
        printf("[%08d] chec(%d,99) = %d \n",i,i,chec(i,99));
   }

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[00000000] chec(0,99) = 1599503850
[00000001] chec(1,99) = 1599503851
[00000002] chec(2,99) = 1599503852
[00000003] chec(3,99) = 1599503853
[00000004] chec(4,99) = 1599503854
[00000005] chec(5,99) = 1599503855
[00000006] chec(6,99) = 1599503856
[00000007] chec(7,99) = 1599503857
[00000008] chec(8,99) = 1599503858
[00000009] chec(9,99) = 1599503859
[00000010] chec(10,99) = 1599503860
[00000011] chec(11,99) = 1599503861
[00000012] chec(12,99) = 1599503862
[00000013] chec(13,99) = 1599503863
[00000014] chec(14,99) = 1599503864
[00000015] chec(15,99) = 1599503865
[00000016] chec(16,99) = 1599503866
[00000017] chec(17,99) = 1599503867
[00000018] chec(18,99) = 1599503868
[00000019] chec(19,99) = 1599503869
[00000020] chec(20,99) = 1599503870
[00000021] chec(21,99) = 1599503871
[00000022] chec(22,99) = 1599503872
[00000023] chec(23,99) = 1599503873
[00000024] chec(24,99) = 1599503874
.....

2分法

可以看出是结果是线性的。首先我们会想到2分法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
unsigned int low, high, value, cur, t,v,i;
 
value = 1835996258;
low = 0;
high = 0xffffffff;
 
cur = low  + (high - low)/2;
printf("value = %08x \n",value);
i = 0;
while (1)
{
    v = chec(cur, 99) ;
    printf("[%08d] cur = %08x v = %08x low = %08x high = %08x \n", i, cur,v,low,high);
    i++;
    if (v > value)
    {
        high = cur;
        cur = low + (high - low) / 2;
        continue;
    }
 
    if (v < value)
    {
        low = cur;
        cur = low + (high - low) / 2;
        continue;
    }
 
    //printf("chec(low,99) = %d \n", chec(low, 99));
    //printf("chec(high,99) = %d \n", chec(high, 99));
    printf("value  = %d \n", value);
    printf("chec(%d,99) = %d \n", cur,chec(cur,99));
    flag(cur);
    break;
}

2分法计算如下

1
2
3
4
5
6
7
8
9
10
11
[00000023] cur = 0e1896ff v = 6d6f14e9 low = 0e1895ff high = 0e1897ff
[00000024] cur = 0e18967f v = 6d6f1469 low = 0e1895ff high = 0e1896ff
[00000025] cur = 0e18963f v = 6d6f1429 low = 0e1895ff high = 0e18967f
[00000026] cur = 0e18965f v = 6d6f1449 low = 0e18963f high = 0e18967f
[00000027] cur = 0e18966f v = 6d6f1459 low = 0e18965f high = 0e18967f
[00000028] cur = 0e189677 v = 6d6f1461 low = 0e18966f high = 0e18967f
[00000029] cur = 0e18967b v = 6d6f1465 low = 0e189677 high = 0e18967f
[00000030] cur = 0e189679 v = 6d6f1463 low = 0e189677 high = 0e18967b
[00000031] cur = 0e189678 v = 6d6f1462 low = 0e189677 high = 0e189679
value  = 1835996258
chec(236492408,99) = 1835996258

找规律

通过仔细观察也可以看出i增长1,计算结果也增长1。这样可以直接算出想要的i值。

1
2
i = 1835996258 - chec(0,99);
printf("%d \n",i);

计算结果。

1
236492408

计算flag

找到so中函数stringFromJNI2,方法同上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
jstring __fastcall Java_net_bluelotus_tomorrow_easyandroid_MainActivity_stringFromJNI2(JNIEnv *a1, int a2, int a3)
{
  int v3; // r1
  JNIEnv v4; // r2
  char v6; // [sp+8h] [bp-50h]
  char v7; // [sp+Ch] [bp-4Ch]
  char v8; // [sp+10h] [bp-48h]
  int v9; // [sp+24h] [bp-34h]
  char str[12]; // [sp+28h] [bp-30h] BYREF
 
  v8 = a3 / 1000 % 10;
  v9 = a3 / 10000 % 10;
  v6 = a3 % 10;
  str[0] = v8 + v6 * v9;
  v7 = a3 / 1000000 % 10;
  v3 = a3 / 100 % 10;
  str[1] = v9 * v7 + 10 * v3 + 3;
  str[2] = 10 * (v8 + v9);
  str[3] = v9 * v7;
  str[4] = 19 * (a3 / 100000 % 10) + 2;
  str[5] = v6 * v7 + 1;
  str[6] = v6 * v7;
  str[7] = 12 * v3;
  str[9] = 12 * v3 + 3;
  str[8] = str[2] + v8;
  v4 = *a1;
  str[10] = 2 * (v9 * v7 + 10 * v3 - 37);
  str[11] = 0;
  return v4->NewStringUTF(a1, str);
}

剔除无用参数得到c语言逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void  flag(int a3)
{
  int v3; // r1
  char v6; // [sp+8h] [bp-50h]
  char v7; // [sp+Ch] [bp-4Ch]
  char v8; // [sp+10h] [bp-48h]
  int v9; // [sp+24h] [bp-34h]
  char str[12]; // [sp+28h] [bp-30h] BYREF
 
  v8 = a3 / 1000 % 10;
  v9 = a3 / 10000 % 10;
  v6 = a3 % 10;
  str[0] = v8 + v6 * v9;
  v7 = a3 / 1000000 % 10;
  v3 = a3 / 100 % 10;
  str[1] = v9 * v7 + 10 * v3 + 3;
  str[2] = 10 * (v8 + v9);
  str[3] = v9 * v7;
  str[4] = 19 * (a3 / 100000 % 10) + 2;
  str[5] = v6 * v7 + 1;
  str[6] = v6 * v7;
  str[7] = 12 * v3;
  str[9] = 12 * v3 + 3;
  str[8] = str[2] + v8;
 
  str[10] = 2 * (v9 * v7 + 10 * v3 - 37);
  str[11] = 0;
  printf("flag:alictf{%s}", str);
}

最后得到flag如下

1
2
236492408
flag:alictf{Jan6N100p3r}


[培训]内核驱动高级班,冲击BAT一流互联网大厂工 作,每周日13:00-18:00直播授课

上传的附件:
收藏
点赞3
打赏
分享
最新回复 (1)
雪    币: 19773
活跃值: (29385)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2024-2-11 21:29
2
1
感谢分享
游客
登录 | 注册 方可回帖
返回