首页
社区
课程
招聘
[原创]安卓日记_ida动调绕过登录验证
发表于: 2022-10-21 09:14 8044

[原创]安卓日记_ida动调绕过登录验证

2022-10-21 09:14
8044

前言

业余时间写的,纯兴趣更新!

环境

为了方便,使用的雷电模拟器 ,apk还是上一篇中的黑宝宝.apk

apk分析

上一篇中直接分析so文件,如何确定验证代码在so库中呢。

 

我们将apk安装之后,随便输入账号和密码,程序会弹出"登录失败",但是在jeb,AK或者其它反编译搜索 这个弹出来的关键字符串却搜不到,所以考虑这个字符串可能就存在so库中。

 

把apk拖入jeb中

 

 

android:debuggable="true",可以调试它,如果为flase,需要动态就需要将它改成true。

 

在jeb中查看Mainactivity这个入口类,把他反编译成java代码

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
package demo2.jni.com.myapplication;
 
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View$OnClickListener;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
 
public class MainActivity extends AppCompatActivity {
    EditText User_Name;
    EditText User_Pass;
    Context ct;
    myJNI mj;
 
    public MainActivity() {
        super();
        this.mj = new myJNI();
        this.ct = ((Context)this);
    }
 
    protected void onCreate(Bundle arg3) {
        super.onCreate(arg3);
        this.setContentView(0x7F04001B);
        this.User_Name = this.findViewById(0x7F0B0058);
        this.User_Pass = this.findViewById(0x7F0B0059);
        this.findViewById(0x7F0B005A).setOnClickListener(new View$OnClickListener() {
            public void onClick(View arg6) {
                Toast.makeText(MainActivity.this, MainActivity.this.mj.check(MainActivity.this.ct, MainActivity.this.User_Name.getText().toString().trim(), MainActivity.this.User_Pass.getText().toString().trim()), 0).show();
            }
        });
    }
}

 

可以看到它调用了 Native方法check

1
MainActivity.this.mj.check(MainActivity.this.ct, MainActivity.this.User_Name.getText().toString().trim(), MainActivity.this.User_Pass.getText().toString().trim()

简化看下

1
check(MainActivity.this.ct, User_Name,User_Pass)

双击MainActivity中的myJNI()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package demo2.jni.com.myapplication;
 
public class myJNI {
    static {
        System.loadLibrary("JniTest");
    }
 
    public myJNI() {
        super();
    }
 
    public native String check(Object arg1, String arg2, String arg3) {
    }
}

 

通过看代码,基本上就断定了程序的逻辑判断代码在so库中

 

接下来用ida去分析so库

 

首先解包

1
2
3
4
5
6
7
8
9
10
11
12
java -jar apktool.jar d 黑宝宝.apk
I: Using Apktool 2.6.1 on 黑宝宝.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: C:\Users\yang\AppData\Local\apktool\framework\1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...

因为用的模拟器所以还是分析x86的so即可

 

将其拖入ida,对check函数进行反编译并导入jni.h,安装第7天的增强代码可读性的方式来一次即可。参数 a4 a5其实就是传入的username和password

 

对伪C代码进行重命名的操作,代码如下(不同ida版本发编译出来的结果可能会有些不一致)

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
int __cdecl Java_demo2_jni_com_myapplication_myJNI_check(_JNIEnv *env, int a2, int a3, int username, int password)
{
  const char *v5; // edi
  _JNIEnv *v6; // ebp
  void *v7; // eax
  _BYTE *v8; // esi
  bool v9; // zf
  signed int v10; // ecx
  const char *v11; // edi
  bool v12; // zf
  _BYTE *User; // esi
  signed int v14; // ecx
  _BYTE *Pass; // esi
  signed int v16; // ecx
  const char *v17; // edi
  int result; // eax
  int v19; // [esp+Ch] [ebp-30h]
  int user; // [esp+18h] [ebp-24h]
  int pass; // [esp+1Ch] [ebp-20h]
 
  v5 = "308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e0"
       "60355040a0c07416e64726f6964310b3009060355040613025553301e170d3138303332313033303431385a170d3438303331333033303431"
       "385a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b30090603550406130"
       "2555330819f300d06092a864886f70d010101050003818d00308189028181008270f53e2cf8c7d7ed200863deb85a054defde773be0b848ee"
       "792839d9a81da098dd9b74bbb9679c19ea30b63fe3bb74aabb270a5c9b3359ebe3fdf278b82fe576a6677f0d77f0eb5b088d0711b15d03cad"
       "ae08b3b980f28055d0cde4bbc4a0b4b208b0f30f170b6ea77a8620269fa1d375442653663e1dd41293aa1c4910e350203010001300d06092a"
       "864886f70d010105050003818100044b9ab7e85346a147926c2d1c6c30e8ffcce174f88acb9763cb776fb1f4dd62183c9524346738ff1aea1"
       "6c5fa218c68da76d05a2422aee12fc23563b5e28925c3d96dff855a584fc1ec462aa768277bd25739085d52fe3fedfd396e38180c13fbb289"
       "786e524535933dd8a99ed3154880544f3e41f044acc43ceefbbce3af59";
  v6 = env;
  v7 = (void *)getSignature(env, a2, a3);
  v8 = (_BYTE *)v6->functions->GetStringUTFChars(&v6->functions, v7, 0);
  user = (int)v6->functions->GetStringUTFChars(&v6->functions, (jstring)username, 0);
  pass = (int)v6->functions->GetStringUTFChars(&v6->functions, (jstring)password, 0);
  __android_log_print(4, "JNI_LOG", &aJniS, v8);// .rodata:000008C4 aJniS           db 'JNI获取到的签名是%s',0
  v10 = 963;
  do
  {
    if ( !v10 )
      break;
    v9 = *v8++ == *v5++;
    --v10;
  }
  while ( v9 );
  if ( !v9 )
  {
    __android_log_print(4, "JNI_LOG", &asc_8F3, v19);// .rodata:000008F3 asc_8F3         db '签名不一致 退出程序',0
    exit(0);
  }
  v11 = "koudai";
  __android_log_print(4, "JNI_LOG", &asc_8DF, v19);// .rodata:000008DF asc_8DF         db '签名一致',0
  User = (_BYTE *)user;
  v14 = 7;
  do
  {
    if ( !v14 )
      break;
    v12 = *User++ == *v11++;
    --v14;
  }
  while ( v12 );
  if ( !v12 )
    goto LABEL_19;
  Pass = (_BYTE *)pass;
  v16 = 6;
  v17 = "black";
  do
  {
    if ( !v16 )
      break;
    v12 = *Pass++ == *v17++;
    --v16;
  }
  while ( v12 );
  if ( v12 )
    result = (int)env->functions->NewStringUTF((JNIEnv *)env, &asc_916);// .rodata:00000916 asc_916         db '登陆成功',0
  else
LABEL_19:
    result = (int)env->functions->NewStringUTF((JNIEnv *)env, &asc_923);// .rodata:00000923 asc_923         db '登陆失败',0
  return result;
}

简单分析下这个so库中check函数的逻辑

1
2
3
4
5
6
首先是检查签名
如果签名没问题就往下面走,否则程序结束。
然后
判断用户名是否为koudai
判断密码是否为black
如果都正确就返回登录成功,否则返回失败

目的是在调试的时候即使签名有问题,让它也继续向下执行,用户名和密码及时输错也可以返回登陆成功。

绕过思路

1.修改内存

 

2.修改指令

绕过

上传并运行ida_server

1
2
3
4
5
6
7
8
9
10
D:\softwareinstall\IDA 7.0\dbgsrv>adb push android_x86_server /data/local/tmp/android_x86_server
12534 KB/s (920668 bytes in 0.071s)
 
D:\softwareinstall\IDA 7.0\dbgsrv>adb shell
 
aosp:/ # cd /data/local/tmp/
aosp:/data/local/tmp # chmod 777 android_x86_server
aosp:/data/local/tmp # ./android_x86_server
IDA Android x86 32-bit remote debug server(ST) v1.22. Hex-Rays (c) 2004-2017
Listening on 0.0.0.0:23946...

 

进行端口转发

1
>adb forward tcp:23946 tcp:23946

手机运行apk

 

新起一个ida窗口

 

 

 

点击ok,报错了

 

 

额,忘记模拟器是x86的了

 

 

 

点击ok,然后选中apk对应的进程(如果进程太多的话 就可以使用ctal+f进行搜索)

 

 

然后长这个样子

 

看下模块窗口(里面都是些so文件)

 

在模块窗口中搜索JniTest找到libJniTest.so库

 

 

双击点这个so,可查看so库中的函数列表

 

继续点击这个check函数,在调试窗口中会出现对应的汇编代码。

 

点tab键对其进行反编译却报错

1
Please position the cursor within a function

 

这是因为ida在动态调试的时候,没有解析函数段导致的。(看调试窗口中的地址是红色的)

 

点击代码,鼠标右键,选择create function

 

然后再次tab键即可完成反编译了。

 

 

但是点过后失败

1
2
libJniTest.so:A5D5384D: The function has undefined instruction/data at the specified address.
Your request has been put in the autoanalysis queue.

 

选中代码一直到retn,然后右键发现找不到create function了,但是可以用快捷键p代替

1
2
参考
https://www.cnblogs.com/studyskill/p/7714485.html

然后再用tab键就可以反编译了

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
int __cdecl Java_demo2_jni_com_myapplication_myJNI_check(_JNIEnv *env, int a2, int a3, int username, int password)
{
  const char *v5; // edi
  _JNIEnv *ENV; // ebp
  int v7; // eax
  _BYTE *v8; // esi
  bool v9; // zf
  signed int v10; // ecx
  const char *v11; // edi
  bool v12; // zf
  _BYTE *USER; // esi
  signed int v14; // ecx
  _BYTE *PASS; // esi
  signed int v16; // ecx
  const char *v17; // edi
  int result; // eax
  int user; // [esp+18h] [ebp-24h]
  int pass; // [esp+1Ch] [ebp-20h]
 
  v5 = "308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e0"
       "60355040a0c07416e64726f6964310b3009060355040613025553301e170d3138303332313033303431385a170d3438303331333033303431"
       "385a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b30090603550406130"
       "2555330819f300d06092a864886f70d010101050003818d00308189028181008270f53e2cf8c7d7ed200863deb85a054defde773be0b848ee"
       "792839d9a81da098dd9b74bbb9679c19ea30b63fe3bb74aabb270a5c9b3359ebe3fdf278b82fe576a6677f0d77f0eb5b088d0711b15d03cad"
       "ae08b3b980f28055d0cde4bbc4a0b4b208b0f30f170b6ea77a8620269fa1d375442653663e1dd41293aa1c4910e350203010001300d06092a"
       "864886f70d010105050003818100044b9ab7e85346a147926c2d1c6c30e8ffcce174f88acb9763cb776fb1f4dd62183c9524346738ff1aea1"
       "6c5fa218c68da76d05a2422aee12fc23563b5e28925c3d96dff855a584fc1ec462aa768277bd25739085d52fe3fedfd396e38180c13fbb289"
       "786e524535933dd8a99ed3154880544f3e41f044acc43ceefbbce3af59";
  ENV = env;
  v7 = ((int (__cdecl *)(_JNIEnv *, int, int))getSignature)(env, a2, a3);
  v8 = (_BYTE *)ENV->functions->GetStringUTFChars(&ENV->functions, (jstring)v7, 0);
  user = (int)ENV->functions->GetStringUTFChars(&ENV->functions, (jstring)username, 0);
  pass = (int)ENV->functions->GetStringUTFChars(&ENV->functions, (jstring)password, 0);
  ((void (__cdecl *)(signed int, const char *, const char *, _BYTE *))unk_A5D533C0)(
    4,
    "JNI_LOG",
    "JNI获取到的签名是%s",
    v8);                                        // libJniTest.so:A5D538C4 aJniS db 'JNI获取到的签名是%s',0
  v10 = 963;
  do
  {
    if ( !v10 )
      break;
    v9 = *v8++ == *v5++;
    --v10;
  }
  while ( v9 );
  if ( !v9 )
  {
    ((void (__cdecl *)(signed int, const char *, const char *))unk_A5D533C0)(
      4,
      "JNI_LOG",
      "签名不一致 退出程序");                            // libJniTest.so:A5D538F3 asc_A5D538F3 db '签名不一致 退出程序',0
    ((void (__cdecl *)(_DWORD))unk_A5D533D0)(0);
    JUMPOUT(unk_A5D5384D);
  }
  v11 = "koudai";
  ((void (__cdecl *)(signed int, const char *, const char *))unk_A5D533C0)(4, "JNI_LOG", "签名一致");// libJniTest.so:A5D538DF asc_A5D538DF db '签名一致',0
  USER = (_BYTE *)user;
  v14 = 7;
  do
  {
    if ( !v14 )
      break;
    v12 = *USER++ == *v11++;
    --v14;
  }
  while ( v12 );
  if ( !v12 )
    goto LABEL_19;
  PASS = (_BYTE *)pass;
  v16 = 6;
  v17 = "black";
  do
  {
    if ( !v16 )
      break;
    v12 = *PASS++ == *v17++;
    --v16;
  }
  while ( v12 );
  if ( v12 )
    result = (int)env->functions->NewStringUTF((JNIEnv *)env, "登陆成功");// libJniTest.so:A5D53916 asc_A5D53916 db '登陆成功',0
  else
LABEL_19:
    result = (int)env->functions->NewStringUTF((JNIEnv *)env, "登陆失败");// libJniTest.so:A5D53923 asc_A5D53923 db '登陆失败',0
  return result;
}


上面这一步其实和静态分析步骤完全一样。

 

然后按空格,转成图形模式,看的逻辑会更清晰些

 

程序里面有三处关键跳转

 

分别用来判断

1
2
3
签名是否一致
用户名是否一致
密码是否一致

 

这里因为是用的是原始的apk,

 

所以签名那部分肯定是一致的

 

我们给账号密码部分进行比较的时候下断点。

 

鼠标选中某一行,然后按f2即可下断点

 

点f9 运行程序,模拟器中输入用户名和密码 点击登录

 

可以看到成功断点

 

 

用户名在rsp+0x18

 

 

用户名也有

 

密码同理

 

目的,随便输入让程序登录成功

 

f8走到jnz关键跳转处

 

 

把ZF改成1

 

修改后

 

同样的,密码也是同样的操作

 

 

 

最后发现成功

 

另一种就是把我们输入的账号密码通过在内存中修改成正确的 koudai black

 

同样可以成功。

 

比如校验用户这里

 

我们输入的内容所在的内存地址最后传到了esi寄存器中

 

 

看下

 

 

然后在内存窗口可看到"ymt"

 

 

我们给它改成正确的"koudai"(右键点击edit 编辑完之后 右键点击应用修改)

1
6B 6F 75 64 61 69 00

 

然后就可以了,密码也是同样的操作。

 

最后也是可以能实现随便输入然后弹出登录成功。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2022-10-21 18:24 被陌上恋静编辑 ,原因: 标题打错了
收藏
免费 1
支持
分享
最新回复 (4)
雪    币: 310
活跃值: (960)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
本人菜鸡一枚,欢迎大佬们陆续加入到QQ群 801022487 一起交流学习
2022-10-21 09:16
0
雪    币: 2379
活跃值: (1794)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
学习了,感谢分享!
2022-10-21 09:28
0
雪    币: 2334
活跃值: (10386)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
大佬能录制个视频讲解吗,不熟悉ida。。
2022-10-24 17:58
0
雪    币: 0
活跃值: (10)
能力值: (RANK:0 )
在线值:
发帖
回帖
粉丝
5
大佬求个联系方式
2023-4-28 14:21
0
游客
登录 | 注册 方可回帖
返回
//