yutao531315 发表于 2020-2-13 22:21

安卓逆向实战题目,ctf 过反调试,以及找到正确密码

前言:这个apk是在我报名的某个培训班上课的实战题目,感觉挺难的,我也搞了好久,写出来是为了能够增加自己的影响,下次遇到这种类似的有一个分析的方向。
下面我对整个静态分析做一个整理,主要是增强一下我的印象。
刚开始分析这个的时候,胡乱的动态调试,发现啥也找不到,一脸懵逼,最终还是要靠把静态分析啃熟悉了,才能动态调试。不然会给你绕晕,
所以静态分析是特别重要!
先看一下apk文件,。直接提示密码错误。

1.先对apk进行一个反编译。jadx-gui-0.9.0打开目标apk,查看安卓配置文件,androidMainifest 配置清单文件找到程序入口界面。


看到我指向的一个就是程序的入口界面。直接在class 文件里面找到对应的类目。贴出来代码 关键代码,设置了一个按钮监听,如果点击则直接调用

    public void click(View v) {///设置一个按钮监听事件
      if (this.editText.getText().toString().equals("")) {//获取输入框的内容,判断是否为空
            Toast.makeText(this, "Please Enter Your PassWord!", 0).show();//为空就弹出,Please Enter Your PassWord
      } else {
            NI.greywolf(this, this.editText.getText().toString());//如果,不为空,则调用ni.greywolf 方法,参数1 this 参数2 我们输入的密码
      }
    }

crlt +点击鼠标查看,这个方法的调用位置,
public class NI {
    public static native void greywolf(Context context, String str); //通过查看greywolf方法,是一个native修饰的,所以可以断定,这个方法在java层实现,并且是static 静态关键词修饰的。
}

在分析so的时候,一定要吧java层跟so层参数对应关系,对上去才能分析,不然都是白搭, 我也会尽量把我分析的过程写清楚,帮助大家搞懂,我也才能收获更多嘛。
{:1_927:}到这里java层就分析完毕了,下面就要进入so层分析,我是用的ida 7.0
《总结下,在java层,设置一个按钮监听事件,然后调用greywolf方法,判断输入的注册码是否正确。 》


来到ida ,输出函数界面,ctrl+f    搜索java_ 没有任何结果,那就断定这个apk是采用的JNIload 动态注册

接着在输出函数界面,搜索JNI_ONLoAd,



双击进入

ext:00014B58               PUSH    {R4-R6,LR}      ; JNIload 函数开始位置。
.text:00014B5A               SUB   SP, SP, #8
.text:00014B5C               ADD   R5, SP, #0x18+var_14 ; 以上,都是堆栈平衡,做准备工作,我们不分析这段代码
.text:00014B5E               MOV   R4, R0    此处调用了一个 j_j__Z2ADv这里面就是apk反调试逻辑,
.text:00014B60               BL      j_j__Z2ADv      ; 看到一个这段代码,我们进入分析。
直接F5,
看伪代码
为了,验证我们的猜想,我们直接进入 j_j__Z2ADv

v0 = j_getpid();                              // 获取一个进程pid
v1 = j_sprintf(&v40, "/proc/%d/status");
if ( !j_fork(v1) )    //这个就是程序自己fork一个子进程,然后自己附加,让我们ida 没办法附加。

j_std::basic_ifstream<char,std::char_traits<char>>::~basic_ifstream(&v62);
      if ( !v3 )
      {
      v16 = j_fopen(&v40, "r");
      do
      {
          if ( !j_fgets(&v38, 128, v16) )
            goto LABEL_3;
      }
      while ( j_strncmp(&v38, "TracerPid", 9) );
      v17 = j_atoi(&v39);
      j_fclose(v16);
      if ( !v17 )
      {
LABEL_3:
          j_sleep(1);
          continue;
      }
      }

这里是获取TracerPid 的值,也是一个反调试逻辑,这里如何过掉反调试,只需要 nop掉这个调用代码处,就过条这个反调试了。



我们来看看完整的汇编代码,


这里有一个关键就是注册函数
,程序逻辑我这里一遍

getenv
findclass
第三个是我们重点需要关注的代码。
注册函数逻辑。
.text:00014B92               MOVS    R0, #0x35C
.text:00014B96               LDR   R2,
.text:00014B98               LDR   R4,
.text:00014B9A               LDR   R0, =(_GLOBAL_OFFSET_TABLE_ - 0x14BA0)
.text:00014B9C               ADD   R0, PC          ; _GLOBAL_OFFSET_TABLE_
.text:00014B9E               LDR   R2, =(dword_58058 - 0x57678)
.text:00014BA0               ADDS    R2, R2, R0      ; dword_58058
.text:00014BA2               MOVS    R3, #1
.text:00014BA4               MOV   R0, R5
.text:00014BA6               BLX   R4


到这里,我们的jniload 函数最关键位置,注册逻辑,
学习过ndk开发的都应该知道, 注册函数一共有四个参数,分别对应的是
1 ENV
2CLASS
3注册方法结构体,
4   需要注册函数数量。
这个时候,这几个参数我们理清楚了,下面我们就应该知道,反汇编的参数传递规则
第一个就是,参数 对应的寄存器,按照 R0 R1R2 R3顺序
那这时候对应起来的就是
R0 = ENV 上面有个getenv 获取出来的
R1 =CLASS有个findclass
R2 =方法体 ADDS R2, R2, R0 ; dword_58058
R4 = 注册方法的个数    MOVS R3, #1
{这时候说点题外话,就是那要是有4个以上寄存器我们应该怎么办,就是把多余的参数存放在堆栈里面 然后调用。}、
这里面最关键的就是方法体了,我们点击


2

3

3
这时候我们就进入了,程序逻辑实现的位置。


F5 查看伪代码。
这时候也到关键位置了,理清楚程序的整个逻辑。
伪代码上。
int (*sub_146A4())()
{
int (*result)(); // r0

dword_58058 = j_wolf_de("6C7A5CA7B2DC4B9C");
dword_5805C = j_wolf_de("234458B0A1C1489300D2572AA3D004E057970FFF6FC1318CF5F6135E6D062813D2642446BD540E79927E12CD4199");
result = bc;
dword_58060 = bc;
return result;
}

看到里面有个bc 方法,我们点击进入


int __fastcall bc(JNIEnv *a1, jclass a2, int a3, String str)//这里就很关键了,参数分别对应,env ,class ,javc层一个参数this,第二个参数,就是我们输入的字符串,这时候我们就要盯着这个str看看,参数走向是如何走,很容易走偏,
{
String v4; // r6
int v5; // r4
JNIEnv *v6; // r5
int v7; // r0
int v8; // r1
int v10; // r0

v4 = str;
v5 = a3;
v6 = a1;
v7 = j_jstringTostring(a1, str);///第一个参数 ,env   第二个参数是我们输入的密码。转换一下

if ( j_dc(v6, v5, v7) )
    return j_jk(v6, v8, v5, v4); //走这里,跟着传入的参数走
v10 = j_wolf_de("5B694AADB2DC559E44B84637A2D61F");
return j_st(v6, v5, v10);

*******************************************
int __fastcall jk(int a1, int a2, int a3, int a4)
{
int v4; // r4
int v5; // r5
int v6; // r0
int result; // r0
int v8; // r0

v4 = a3;
v5 = a1;
v6 = j_jstringTostring(a1, a4);
result = j_dc(v5, v4, v6); ///走这里。
if ( result )
{
    v8 = j_wolf_de("486757B9B7D2538F089C402CA2CA12AF77D029B071D42787F6A22D502C193A1CCC6C2D49E629");
    result = j_st(v5, v4, v8);
}
return result;
}
到这里。

int __fastcall dc(int a1, int a2, int a3)
{
int v3; // r4
int v4; // r5
int v5; // r6
int v6; // r1
int result; // r0

v3 = a3;
v4 = a2;
v5 = a1;
v6 = j_dh(a1, a2);
result = 0;
if ( v6 )
    result = j_db(v5, v4, v3); //走这里...
return result;
}}

**************
int __fastcall db(int a1, int a2, int a3)
{
int v3; // r4
int v4; // r5
int v5; // r6
int v7; // r0

v3 = a3;
v4 = a2;
v5 = a1;
if ( j_dh(a1, a2) )
    return j_ds(v5, v4, v3); 走这里
v7 = j_getpid();
j_kill(v7, 9);
return 0;
}
//******************

int __fastcall ds(int a1, int a2, int a3)
{
int v3; // r4
int v4; // r0
std::__node_alloc *v5; // r5
std::__node_alloc *v6; // r6
unsigned int v7; // r2
signed int v8; // r4
int v9; // r0
int result; // r0
char v11; //
char v12; //
int v13; //
int v14; //
std::__node_alloc *v15; //
int v16; //
int v17; //
std::__node_alloc *v18; //
int v19; //

v3 = a3;
if ( j_dh(a1, a2) )
{
    v4 = j_wolf_de("636D55B2AA8609CB");
    sub_15184(&v16, v4, &v12);///整个程序就走到了这个位置! 这里返回出来的就是正确密码hello5.1
    sub_15184(&v13, v3, &v11);
    v5 = v18;
    v6 = v15;
    v7 = v17 - v18;
    v8 = 0;
    if ( v17 - v18 == v14 - v15 )
    {
      v8 = 1;
      if ( j_memcmp(v18, v15) )
      v8 = 0;
    }
    if ( v6 != &v13 && v6 )
    {
      j_std::__node_alloc::deallocate(v6, (v13 - v6), v7);
      v5 = v18;
    }
    if ( v5 != &v16 && v5 )
      j_std::__node_alloc::deallocate(v5, (v16 - v5), v7);
}
else
{
    v9 = j_getpid();
    j_kill(v9, 9);
    v8 = 0;
}
result = _stack_chk_guard - v19;
if ( _stack_chk_guard == v19 )
    result = v8;
return result;
}


程序终于给走完了,最终密码是 hello5.1   ,这个需要在动态调试的时候,在调用
sub_15184(&v16, v4, &v12);程序返回的是密码。
这个加密是采用rc4 加密算法 ,可以在wolf_de里面可以跟踪到
if ( j_RC4(v5, v8 >> 1, v2, v9, v7, &v11) )
      {
          v7 = 0;
          v4 = v7;
      }
但是咱们呢,也不纠结是怎么得到的,只要我们能够把正确密码得到就算成功,
好了教程就到这里了,写得不好大家多多包涵,毕竟我也是刚学习安卓逆向没得多久,这段时间肺炎闹得厉害,打游戏呢,也打懵逼了,发个帖子记忆一下
有什么错误,欢迎大家指正,

链接:https://share.weiyun.com/5cr3Nm3 密码:v9q94t

yutao531315 发表于 2020-2-17 21:54

neilwu 发表于 2020-2-17 21:51
有个偷鸡方法 如果你直接hook wolf_de这个方法(0x000169A0)可以直接拿到flag
args0 636D55B2AA8609CB
...

嗯 ,是的,这个方案可行,不过前提得会分析,找到关键点

neilwu 发表于 2020-2-17 21:51

有个偷鸡方法 如果你直接hook wolf_de这个方法(0x000169A0)可以直接拿到flag
args0 636D55B2AA8609CB
retval hello5.1

wale 发表于 2020-2-14 01:03

感谢楼主精彩分析

Solomon 发表于 2020-2-14 07:51

来支持一下楼主。。

tianaishi 发表于 2020-2-14 10:37


来支持一下楼主。。

Jackdlk 发表于 2020-2-14 14:32

来学习一下

neilwu 发表于 2020-2-15 22:55

支持一下 练练手

gypsylu 发表于 2020-2-16 11:50

感谢楼主的分享

shuangyeying 发表于 2020-2-22 00:37

哪个培训班呀,感觉想去学一波。
页: [1] 2
查看完整版本: 安卓逆向实战题目,ctf 过反调试,以及找到正确密码