主题:安卓app中的关键登录逻辑分析
目标:des算法分析,.so文件分析
样本:某恋v5.0.1 app
代码:main函数自实现,其它函数提取自app中的安卓无关代码
作者:by GKLBB
参考:Bu弃 https://www.chinapyg.com/forum.php?mod=viewthread&tid=119242&highlight=DES
无名Android逆向 系列视频

资源:

链接:https://pan.baidu.com/s/14FzEJt0uegp9XQhYr5iQoA
提取码:rrc0

test文件里是加密器,依据分析app逻辑用java写出

1.依据线索定位关键函数
抓包找到线索
在dex中搜索线索

com.a.a.a.b. s
switch
紧跟着加密代码
JSONObject v1_1 = new JSONObject(); //创建一个构建JSON字符串的对象
v1_1.put("xxxx", xxxx); //往里面加入key/value形式的键值对
v1_1.put("xxxx", xxxx);
v0 = com.a.a.a.f.a.a(v1_1.toString()).getBytes(); //com.a.a.a.f.a.a(v1_1.toString()) 就是具体的加密逻辑了

com.a.a.a.f.a.a
Jni.getInstance().encryptString(arg1);

com.Jni.encryptString
v4.append(this.getEncryptString(v2, true)); //调用this.getEncryptString(v2, true)加盐
DESencryption.getEncString(v4.toString(), this.getEncryptString("a", true).substring(0, 8));//getEncryptString就是libjni.so中的一个方法,这里有个细节容易被忽略,v2是输入v2字符串格式化json后hex转换后的结果,如果直接传入“a”其结果是a+盐

最终我们找到了两个关键函数加密用的getEncString,加盐用的getEncryptString

2.分析关键函数
分析getEncryptString
用ida生成的c伪码
int __fastcall Java_com_jni_Jni_getEncryptString(_JNIEnv *a1, JNINativeInterface *a2, int a3, int a4)
{
_JNIEnv *v4; // r5@1
int v5; // r4@1
const char *v6; // r7@1
size_t v7; // r4@1
_JNIEnv *v8; // r0@2
char *v9; // r1@2
jstring (__cdecl *v10)(JNIEnv *, const char *); // r3@2
int result; // r0@6
char *s; // [sp+0h] [bp-828h]@1
int v13; // [sp+4h] [bp-824h]@1
char dest; // [sp+Ch] [bp-81Ch]@3
int v15; // [sp+80Ch] [bp-1Ch]@1

v4 = a1;
v5 = a3;
v13 = a4;
v15 = _stack_chk_guard;
g_env = a1;
s = (char *)initAddStr();//// 初始化一个字符串,即就是盐本身。
v6 = (const char *)jstringTostring(v4, v5);// 调用JNI的方法,把Java中的String变成C中的char *,即就是输入的v5转char×
v7 = strlen(s);// 求出初始化字符串的长度
if ( strlen(v6) + v7 <= 0x7FF )// 转成C的inputStr的长度和s的长度<0x7ff(2047)如果小于则拼上s.否则不拼
{
memset(&dest, 0, 0x7FFu);// 往dest这个地址填充0x7FF个0
strcat(&dest, v6);// 这里是把v6的值,也就是inputStr转成Char的值赋给dest
if ( v13 )// 第2个传参也就是a4 inputBool为true的时候,就在后面跟上初始值s。否则不跟。也就是说要想加入盐必需加入后的长度不能超过2047且为true时。
strcat(&dest, s);//加盐!!!!!!!!!!!!!!!!!!!!!!!1111
v8 = v4;// v8 = JNIEnv
v9 = &dest;
v10 = v4->functions->NewStringUTF;// v10 = NewStringUTF:把C的char* 转换成Java中的String
}
else//无盐直接转换string回去
{
v8 = v4;
v9 = (char *)v6;
v10 = v4->functions->NewStringUTF;//// v10 = NewStringUTF:把C的char* 转换成Java中的String
}
result = ((int (__fastcall *)(_JNIEnv *, char *))v10)(v8, v9);// 反转结果为string,调用NewStringUTF方法,把v10转换成String
if ( v15 != _stack_chk_guard )
_stack_chk_fail(result);
return result;
}

//总结一下,就是如果加盐后的长度不能超过2047且为true时,加盐,否则不加直接返回

分析initAddStr
用ida生成的c伪码
int initAddStr()
{
int v0; // r0@2
int v1; // r2@2

if ( !isInit ) //这里是如果初始化一次了,就不需要再执行了。也就是这里只会执行一次。
{
v0 = initInflect((int)jniStr); //反射到java层,参数就是“/key=i im lianai”
key = jstringTostring((int)g_env, v0, v1); //调用方法,把java的String变成C语言中的char*
isInit = 1;
}
return key;
}
//总结一下,这段是包含/key=i im lianai中间代码,跳转到initInflect

分析initInflect
用ida生成的c伪码
int __fastcall initInflect(int a1)
{
_JNIEnv *env; // r5@1
int v2; // r0@1
bool v3; // zf@1
int v4; // r7@1
const struct JNINativeInterface *v5; // r0@1
int (__fastcall *v6)(int *, const char *); // r3@3
const char *v7; // r1@3
int v8; // r0@2
const struct JNINativeInterface *v9; // r3@4
int v10; // r4@4
int v12; // [sp+Ch] [bp-1Ch]@1

v12 = a1;//保存入参
env = (_JNIEnv *)g_env;

//找出类ID
v2 = (*(int (__fastcall **)(_DWORD *, const char *))(*g_env + 24))(g_env, "com/Reflect");//clzID
v4 = v2=clzID;
v3 = v2 == 0;
v5 = env->functions;
if ( v3 )//clzID存在,自行下面代码段
{
v6 = (int (__fastcall *)(int *, const char *))env->functions;->NewStringUTF;
v7 = "jclass";
return v6((int *)env, v7);
}

//找出方法ID
v8 = ((int (__fastcall *)(_JNIEnv *, int, const char *, const char *))env->functions;->GetStaticMethodID)(
env,
clzID,
"func",
"(ILjava/lang/String;)Ljava/lang/String;");
v9 = env->functions;
v10 = v8=MethodID;
if ( !v8 )
{
v6 = (int (__fastcall *)(int *, const char *))env->functions;->NewStringUTF;
v7 = "method";
return v6((int *)env, v7);
}

//调用类.方法
((void (__fastcall *)(_JNIEnv *, int))env->functions;->NewStringUTF)(env, v12);//v12就是本函数入参/key=i im lianai,把v12转string,但是这里没有接收者,是因为伪代码不可信
return _JNIEnv::CallStaticObjectMethod(env, clzID, MethodID, 10);
}
//总结一下,,这段是真正的反射中间代码,调用java层的com.Reflect类中的func方法,并返回结果。

分析com.Reflect类!!!!!!!!!!
public static String func(int arg2, String arg3) {//这个就是IDA中调用的方法
return Reflect.encode(String.valueOf(arg3) + " alien"); //这里调用了此类中的encode方法,传入的是我们传递过来的参数“/key=i im lianai” 加上此类提供的一个静态字符串" alien"<注意前面有个空格>,综合起来也就是“/key=i im lianai alien”!!!!!!!!!!!!!!!!1
//总结一下,/key=i im lianai后加入alien,生成最终盐key=i im lianai alien返回

3.解密加密了的字符串

des是对称加密算法,因此密文:已知,算法:DES/CBC/PKCS5Padding, 密码:a2F6B657,IV:010230405060708,就可以解出明文,注意解出来的值还要转换HEX字符串然后每两位一组当作HEX字符串转换为accii,这是因为加密时ascii->hex->byte 解码就要byte->hex->ascii

附录:

分析前要清楚r0 r1 r2 r3四个寄存器的保存的是什么,保存的是少于4个的入参,超过4个用堆保存。
汇编无非就是操作寄存器并通过寄存器操作内存的过程。
分析指令关键看调用代码bl blx 跳转指令B
在所arm指令前先说说smali代码,smali是给dalvik vm使用的代码,而dvm类似于jvm,所以smali类似于在dalvik上跑的伪汇编代码

比如
java代码 a a1=b(123);
java代码之所以属于高级语言,容易读取,是因为到处用到了人可以识别的命名,并且把能合并参数的合并 能省略类型、包名的省略,再加上封装 所以得到了易读取的java

转换成smali代码(#为注解)
const-string v0,"123"#v0=123
invoke-virtual {v0} L包名/包名/包名/类名;->b(Ljava/lang/String;)Ljava/lang/String;
move-result-object v0
我们并没有看见a1这个名字是给人看的,我们看见的都是v0等等的虚拟寄存器,为什么叫虚拟寄存器因为只有虚拟机才能识别

转换成arm汇编(;为注解)
mov r0 123;r0=123
bl b();跳转到b并将r0传入
mov r1 r0 ;将返回值保存在r1中
在汇编代码里都是隐性的传入参数 隐性的返回,用到更多的寄存器和内存堆栈和地址,可读性更差

最终完整分析Java_com_jni_Jni_getEncryptString函数
.text:000010A4 ; =============== S U B R O U T I N E =======================================
.text:000010A4
.text:000010A4 ; r0 env
.text:000010A4 ; r1 obj
.text:000010A4 ; r2 input
.text:000010A4 ; r3 bool
.text:000010A4 ; r4 - r7 为结局变量声明,就是说后面要用临时用到这些个寄存器
.text:000010A4
.text:000010A4 ; int __fastcall Java_com_jni_Jni_getEncryptString(_JNIEnv *a1, JNINativeInterface *a2, int a3, int a4)
.text:000010A4 EXPORT Java_com_jni_Jni_getEncryptString
.text:000010A4 Java_com_jni_Jni_getEncryptString
.text:000010A4
.text:000010A4 s = -0x828
.text:000010A4 var_824 = -0x824
.text:000010A4 dest = -0x81C
.text:000010A4
.text:000010A4 PUSH {R4-R7,LR}
.text:000010A6 LDR R6, =(__stack_chk_guard_ptr - 0x10B0) ; 栈保护代码忽略
.text:000010A8 LDR R4, =0xFFFFF7EC
.text:000010AA MOVS R5, R0 ; 保存env
.text:000010AC ADD R6, PC ; __stack_chk_guard_ptr
.text:000010AE LDR R6, [R6] ; __stack_chk_guard
.text:000010B0 ADD SP, R4
.text:000010B2 MOVS R4, R2 ; 保存input
.text:000010B4 LDR R2, =0x80C ; 80c
.text:000010B6 STR R3, [SP,#0x828+var_824] ; 存bool
.text:000010B8 LDR R3, [R6]
.text:000010BA ADD R2, SP ; sp+80c
.text:000010BC STR R3, [R2] ; sp+80c=栈保护
.text:000010BE LDR R3, =(g_env_ptr - 0x10C4)
.text:000010C0 ADD R3, PC ; g_env_ptr
.text:000010C2 LDR R3, [R3] ; g_env
.text:000010C4 STR R0, [R3] ; g_env = env 把env放进[r3]目的不知道
.text:000010C6 BL initAddStr ; 调用iniAddStr
.text:000010C6 ; nop
.text:000010CA MOVS R1, R4 ; 取出input
.text:000010CC STR R0, [SP,#0x828+s] ; 保存上一个函数的返回值salt到栈s,s=salt
.text:000010CE MOVS R0, R5 ; 取出env
.text:000010D0 BL jstringTostring ; r0 env
.text:000010D0 ; r1 =r4=r2=input
.text:000010D0 ; jstringTostring(env,input)
.text:000010D4 MOVS R7, R0 ; 保存*input
.text:000010D6 LDR R0, [SP,#0x828+s] ; s
.text:000010D8 BLX strlen ; r0 salt
.text:000010D8 ; 求盐长
.text:000010DC MOVS R4, R0 ; 存盐长
.text:000010DE MOVS R0, R7 ; s
.text:000010E0 BLX strlen ; r0 *input
.text:000010E0 ; 求入长
.text:000010E0 ; 将7ff存到堆为什么不存在寄存器里目的是方便后面调用
.text:000010E4 LDR R2, =0x7FF ; n
.text:000010E6 ADDS R0, R0, R4 ; 盐长加入长
.text:000010E8 MOVS R4, R6
.text:000010EA MOVS R6, #0x29C ; 偏移量668,即就是NewStringUTF
.text:000010EE CMP R0, R2 ; 总长比0x7ff
.text:000010F0 BLS loc_10FC ; <=则跳转
.text:000010F2 LDR R3, [R5]
.text:000010F4 MOVS R0, R5 ; env
.text:000010F6 MOVS R1, R7 ; *input
.text:000010F8 LDR R3, [R3,R6]
.text:000010FA B loc_1122 ; 跳转到NewStringUTF
.text:000010FC ; ---------------------------------------------------------------------------
.text:000010FC
.text:000010FC loc_10FC ; CODE XREF: Java_com_jni_Jni_getEncryptString+4Cj
.text:000010FC MOVS R1, #0 ; c
.text:000010FE ADD R0, SP, #0x828+dest ; s
.text:00001100 BLX memset ; r0 &dest
.text:00001100 ; r1 0
.text:00001100 ; r2 0x7ff
.text:00001100 ; 上一个函数的返回值不用所以直接r0=&dest
.text:00001104 ADD R0, SP, #0x828+dest ; dest
.text:00001106 MOVS R1, R7 ; src
.text:00001108 BLX strcat ; r0 &dest
.text:00001108 ; r1 *input
.text:0000110C LDR R3, [SP,#0x828+var_824] ; 取bool
.text:0000110E CMP R3, #0
.text:00001110 BEQ loc_111A ; 真则跳转
.text:00001112 ADD R0, SP, #0x828+dest ; dest
.text:00001114 LDR R1, [SP,#0x828+s] ; src
.text:00001116 BLX strcat ; 加入盐
.text:0000111A
.text:0000111A loc_111A ; CODE XREF: Java_com_jni_Jni_getEncryptString+6Cj
.text:0000111A LDR R3, [R5]
.text:0000111C MOVS R0, R5 ; env
.text:0000111E ADD R1, SP, #0x828+dest
.text:00001120 LDR R3, [R3,R6]
.text:00001122
.text:00001122 loc_1122 ; CODE XREF: Java_com_jni_Jni_getEncryptString+56j
.text:00001122 BLX R3 ; 跳转到NewStringUTF
.text:00001124 LDR R3, =0x80C
.text:00001126 ADD R3, SP
.text:00001128 LDR R2, [R3]
.text:0000112A LDR R3, [R4]
.text:0000112C CMP R2, R3 ; v15 = _stack_chk_guard
.text:0000112E BEQ loc_1134
.text:00001130 BLX __stack_chk_fail
.text:00001134 ; ---------------------------------------------------------------------------
.text:00001134
.text:00001134 loc_1134 ; CODE XREF: Java_com_jni_Jni_getEncryptString+8Aj
.text:00001134 LDR R3, =0x814
.text:00001136 ADD SP, R3
.text:00001138 POP {R4-R7,PC}
.text:00001138 ; End of function Java_com_jni_Jni_getEncryptString
.text:00001138
.text:00001138 ; ---------------------------------------------------------------------------
.text:0000113A ALIGN 4
.text:0000113C off_113C DCD __stack_chk_guard_ptr - 0x10B0
.text:0000113C ; DATA XREF: Java_com_jni_Jni_getEncryptString+2r
.text:00001140 dword_1140 DCD 0xFFFFF7EC ; DATA XREF: Java_com_jni_Jni_getEncryptString+4r
.text:00001144 dword_1144 DCD 0x80C ; DATA XREF: Java_com_jni_Jni_getEncryptString+10r
.text:00001144 ; Java_com_jni_Jni_getEncryptString+80r
.text:00001148 off_1148 DCD g_env_ptr - 0x10C4 ; DATA XREF: Java_com_jni_Jni_getEncryptString+1Ar
.text:0000114C ; size_t n
.text:0000114C n DCD 0x7FF ; DATA XREF: Java_com_jni_Jni_getEncryptString+40r
.text:00001150 dword_1150 DCD 0x814 ; DATA XREF: Java_com_jni_Jni_getEncryptString:loc_1134r
.text:00001154 CODE32

安卓逆向的初步研究--从某恋app入手的更多相关文章

  1. 20145307陈俊达_安卓逆向分析_Xposed的hook技术研究

    20145307陈俊达_安卓逆向分析_Xposed的hook技术研究 引言 其实这份我早就想写了,xposed这个东西我在安卓SDK 4.4.4的时候就在玩了,root后安装架构,起初是为了实现一些屌 ...

  2. 20145307陈俊达_安卓逆向分析_dex2jar&jd-gui的使用

    20145307陈俊达_安卓逆向分析_dex2jar&jd-gui的使用 引言 这次免考选择了四个项目,难度也是从简到难,最开始先写一个工具的使用 想要开发安卓首先要会编写代码,但是想要逆向分 ...

  3. iOS多线程的初步研究(六)

    iOS多线程的初步研究(六) iOS平台提供更高级的并发(异步)调用接口,让你可以集中精力去设计需完成的任务代码,避免去写与程序逻辑无关的线程生成.运行等管理代码.当然实质上是这些接口隐含生成线程和管 ...

  4. iOS多线程的初步研究3

    iOS多线程的初步研究(三) 弄清楚NSRunLoop确实需要花时间,这个类的概念和模式似乎是Apple的平台独有(iOS+MacOSX),很难彻底搞懂(iOS没开源,呜呜). 官网的解释是说run ...

  5. iOS多线程的初步研究1

    iOS多线程的初步研究(一) 对于多线程的开发,iOS系统提供了多种不同的接口,先谈谈iOS多线程最基础方面的使用.产生线程的方式姑且分两类,一类是显式调用,另一类是隐式调用. 一.显示调用的类为NS ...

  6. iOS多线程的初步研究

    iOS多线程的初步研究(四) 理解run loop后,才能彻底理解NSTimer的实现原理,也就是说NSTimer实际上依赖run loop实现的. 先看看NSTimer的两个常用方法: + (NST ...

  7. CTF的一道安卓逆向

    前几天打CTF时遇到的一道安卓逆向,这里简单的写一下思路 首先用jadx打开apk文件,找到simplecheck处(文件名是simplecheck),可以看到基本逻辑就是通过函数a对输入的内容进行判 ...

  8. 安卓逆向(一)--Smali基础

    安卓逆向(一)--Smali基础 标签(空格分隔): 安卓逆向 APK的组成 文件夹 作用 asset文件夹 资源目录1:asset和res都是资源目录但有所区别,见下面说明 lib文件夹 so库存放 ...

  9. 20145307陈俊达_安卓逆向分析_APKtools分析smail

    20145307陈俊达_安卓逆向分析_APKtools分析smail 引言 真刺激呢!到了第二篇博客了,难度开始加大,之前是简单的dex2jar和有图形界面的jd-gui,现在来隆重介绍强大的反汇编工 ...

  10. 安卓逆向之基于Xposed-ZjDroid脱壳

    http://bbs.pediy.com/thread-218798.htm     前言 之前介绍了普通常见的反编译模式 但对于使用了 360加固 棒棒 爱加密 等等的加固应用就没办法了. 你会发现 ...

随机推荐

  1. Windows 10 消费者\商业版本 20H2(2021年2月更新)附百x云链接

    Windows 10 消费者版本 20H2(2021年2月更新)x64 64位系统 中文简体 包含: 专业版 家庭版  教育版 专业教育版 专业工作站版 文件名:cn_windows_10_consu ...

  2. Win11系统更新错误0xc1900101的问题

    win11系统是非常火热的电脑操作系统,很多电脑基地的用户都体验过Win11的新功能,但是有一位用户在更新win11系统时遇到了更新错误0xc1900101的情况,这可能是因为系统出现了一些问题,大家 ...

  3. Mysql查询语句执行流程?更新语句执行流程?

    查询语句执行流程 查询语句的执行流程如下:权限校验.查询缓存.分析器.优化器.权限校验.执行器.引擎. 举个例子,查询语句如下: select * from user where id > 1 ...

  4. 大模型的JSON之殇:从脆弱的API调用到稳健的未来

    大家好,今天我们来聊一个让许多AI开发者感到不安的话题:大模型工具调用中的JSON格式. 当我们尝试让大模型(LLM)调用外部工具,尤其是像OpenAI的API那样,通常需要模型生成一个严格的JSON ...

  5. (译) 理解 Elixir 中的宏 Macro, 第三部分:深入理解 AST

    Elixir Macros 系列文章译文 [1] (译) Understanding Elixir Macros, Part 1 Basics [2] (译) Understanding Elixir ...

  6. 深入浅析BIO、NIO、AIO

    https://www.cnblogs.com/henuliulei/p/15143649.html BIO.NIO.AIO Java的I/O演进之路I/O模型 :就是用什么样的通道或者说是通信模式和 ...

  7. ZYNQ linux上使用 USB CDC ACM

    一.USB CDC ACM介绍 USB CDC ACM 是 USB 通信设备类(CDC)中的一个子类,它提供了一种通过 USB 接口实现虚拟串行通信的标准方法. 1. 基本概念 CDC ACM 是 U ...

  8. redis实现分布式锁的方式,他们有什么区别

    SETNX 使用方法:SETNX(SET if Not eXists)是 Redis 中一个经典的用于设置分布式锁的命令.它的作用是当指定的键不存在时,才会设置该键的值,如果键已经存在则不做任何操作. ...

  9. 从 URL 到页面:浏览器加载过程的详细解析

    当你在浏览器中输入一个 URL 并按下回车键,直到页面内容显示在屏幕上,这个过程中发生了许多步骤.以下是一个详细的分解,解释从输入 URL 到看到内容之间的整个过程: 1. 用户输入 URL 用户在浏 ...

  10. rider 设置多个启动项目

    要设置多个启动项目,您可以按照以下步骤操作: 1. 打开 Rider 并导航到 "Run" 菜单.2. 选择 "Edit Configurations" 以打开 ...