吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7556|回复: 23
收起左侧

[Android 原创] 一道竞赛题的分析

  [复制链接]
Enigma_G 发表于 2017-4-28 23:31
本帖最后由 Enigma_G 于 2017-4-29 10:23 编辑

0x00 闲言碎语
发现自己的贴被繁华版主加了精,怪感动的,谢谢版主

上了一天的课怪累的。。。晚上开论坛发现有朋友问我一道PC的竞赛题,PC逆向自己接触的不多,然后看了下那个竞赛的第一道Android方向的题目,有一点声明一下,我这只是分享一些个人经验与技巧从分析apk的角度入手而不是解CTF题,这道题的算法其实不难 ,论坛里也有一篇精华是分析这个的
附上链接    http://www.52pojie.cn/thread-602744-1-1.html
还有安全客上的分析,这个比较简陋。随意看看就好,不适合新手  http://bobao.360.cn/ctf/learning/191.html

那么
嘻嘻,大家不嫌弃的话可以看看

那么进入正题
----------------
0x01 Java层分析

我的习惯是先去分析AndroidMainifest文件,因为这里可以得到很多东西,权限啦,注册了哪些Activity啦,reciver啦,等等之类的,还能看出它有没有加壳之类的,那么我们首先用jeb打开这个文件

1.png

[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.humen.crackme010" platformBuildVersionCode="23" platformBuildVersionName="6.0-2166767" xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
    <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme">
        <activity android:label="@string/app_name" android:name=".MainActivity"> 
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:label="@string/title_activity_window" android:name=".WindowActivity" />
    </application>
</manifest>


下面这两行代码有过开发经验的朋友应该知道,这是加载的第一个avtivity
[XML] 纯文本查看 复制代码
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />



那么我们跟进这个MainActivity查看



[Java] 纯文本查看 复制代码
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View$OnClickListener;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {
    static {
        System.loadLibrary("humen");  //加载so类库,还有一种加载方式是调用load()函数,至于加载的过程,我前段时间一直在看源码,有机会以后和大家分享分享
    }

    public MainActivity() {
        super();
    }

    public void checkPass(String arg6) {
        String v1 = CheckUtil.checkPass(arg6);
        if(arg6 == null || (arg6.equals(""))) {
            Toast.makeText(this.getApplicationContext(), this.getString(2131034114), 1).show();
        }
        else if(v1.equals("a")) {
            this.startActivity(new Intent(((Context)this), WindowActivity.class));
        }
        else {
            Toast.makeText(this.getApplicationContext(), this.getString(2131034115), 1).show();
        }
    }

    protected void onCreate(Bundle arg3) {
        super.onCreate(arg3);
        this.setContentView(2130903040);
        this.findViewById(2131165184).setOnClickListener(new View$OnClickListener() {
            public void onClick(View arg4) {
                MainActivity.this.checkPass(MainActivity.this.findViewById(2131165185).getText().toString().trim());//.trim()是去掉前后两端存在的空格
            }
        });
    }
}


那么思路其实很清晰,我们继续跟进CheckUtil.checkPass函数

[Java] 纯文本查看 复制代码
package com.humen.crackme010;

public class CheckUtil {
    public CheckUtil() {
        super();
    }

    public static native String checkPass(String arg0) {  //声明native函数
    }
}



那么Java层分析完毕了,接下来就是分析so文件了,正好前篇那篇文章提到过一些修改变量类型,可能还不是很详细,这里就详细给大家分享下我个人的经验


0x02 so文件分析

找到对应的Java_com_humen_crackme010_CheckUtil_checkPass()函数

那么明显这两个参数类型识别有问题

2.png

前面文章说过,第二个参数要么是jclass类型,要么是jobject类型,那么因为在java层声明的时候是static方法,所以这里应该是jclass类型,摁快捷键Y或者你右键也行,改为jclass,再就是第三个参数,就是我们java层传入的参数,不记得的话调回去看一看
是一个String类型参数,那么JNI对应的就是jstring类型,同样修改,呢么修复后代码如下:
[C++] 纯文本查看 复制代码
int __fastcall Java_com_humen_crackme010_CheckUtil_checkPass(_JNIEnv *a1, jclass a2, jstring a3)
{
  _JNIEnv *v3; // r7@1
  const char *v4; // r0@1
  const char *v5; // r4@1
  size_t v6; // r0@1
  signed int v7; // r2@1
  signed int v8; // r6@1
  int v9; // r3@3
  char v10; // r0@3
  signed int v11; // r1@3
  int i; // r3@3
  char v13; // r5@5
  signed int v14; // r5@6
  signed int v15; // r8@6
  signed int j; // r3@9
  const char *v17; // r1@13

  v3 = a1;
  v4 = (const char *)getStringUnicodeChars(a1, (int)a3, "utf-8");
  v5 = v4;
  v6 = strlen(v4);
  v7 = 0;
  v8 = v6;
  while ( 1 )
  {
    v7 += 2;
    if ( v7 >= v8 )
      break;
    v9 = (int)&v5[v7];
    v10 = v5[v7 - 1];
    *(_BYTE *)(v9 - 1) = v5[v7 - 2];
    v11 = 0;
    *(_BYTE *)(v9 - 2) = v10;
    for ( i = (int)(v5 + 4); ; *(_BYTE *)(i - 8) = v13 )
    {
      v11 += 4;
      i += 4;
      if ( v11 >= v8 )
        break;
      v13 = *(_BYTE *)(i - 4);
      *(_BYTE *)(i - 4) = *(_BYTE *)(i - 8);
    }
  }
  v14 = 0;
  v15 = strlen(v5);
  while ( v14 < v15 )
    _android_log_print(4, "humen", "%c", v5[v14++]);
  printf("%s", v5);
  for ( j = 0; v5[j] == (unsigned __int8)t[j]; ++j )
    ;
  if ( j <= v8 )
    v17 = "b";
  else
    v17 = "a";
  return _JNIEnv::NewStringUTF(v3, v17);
}


那么从解题的角度来看,已经可以开始分析算法了,但是前面说过我是从分析apk的角度,

这行代码从函数名字可以很容易看出他是转换Unicode码,但是这里参数类型让我觉得奇怪
[C++] 纯文本查看 复制代码
v4 = (const char *)getStringUnicodeChars(a1, (int)a3, "utf-8");


那么跟进看看:
很明显,这里IDA没有正确识别

3.png

注意这几行代码
[C++] 纯文本查看 复制代码
v6 = (*(int (__fastcall **)(_JNIEnv *, const char *))(*(_DWORD *)v4 + 24))(v4, "java/lang/String");


[C++] 纯文本查看 复制代码
v7 = (*(int (__fastcall **)(_JNIEnv *, int, const char *, const char *))(*(_DWORD *)v4 + 132))(
         v4,
         v6,
         "getBytes",
         "(Ljava/lang/String;)[B");


[C++] 纯文本查看 复制代码
v9 = _JNIEnv::CallObjectMethod(v4, v5, v7, v8);


那么看到CallObjectMethod这个函数了,这个函数是干吗的呢?还记得之前那篇文章讲过JNI.h这个文件么?去这里找到这个函数


下面是这个函数的三个重载


   
[C++] 纯文本查看 复制代码
 jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);[/size]    jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);


那么我们得到的第一个信息便是这个函数的返回值是一个jobject类型,但是这里返回的是int类型,呢么双击CallObjectMethod函数,我们跟进分析它哪识别错了

4.png
[C++] 纯文本查看 复制代码
int __fastcall _JNIEnv::CallObjectMethod(int a1, int a2, int a3, int a4)
{
  int varg_r3; // [sp+14h] [bp-4h]@1

  varg_r3 = a4;
  return (*(int (__cdecl **)(int, int))(*(_DWORD *)a1 + 140))(a1, a2);
}


可以发现全部识别错了



开始对照手动修改,修复完后

[C++] 纯文本查看 复制代码
jobject __fastcall _JNIEnv::CallObjectMethod(JNIEnv *a1, jobject a2, jmethodID a3, int a4)
{
  int varg_r3; // [sp+14h] [bp-4h]@1

  varg_r3 = a4;
  return (*a1)->CallObjectMethodV(a1, a2, a3, (int **__attribute__((__org_typedef(va_list))) )&varg_r3);
}


那么再回到之前的函数查看

5.png

那么再修复一下v4的类型为JNIEnv*,之前的你会发现前面多了下划线,那么这应该就是导致识别出错的地方

7.png

修改后成功参数识别,后面的函数也识别出来了,那么你会发现其实a1的参数类型也错了,同样修改,然后把函数修复一下,还记得么,右键force call type

8.png

是不是好看了很多,还没修复完,先不急,我们先看看几个重要的JNI函数

这个是一个找class的函数
[C++] 纯文本查看 复制代码
v6 = (*v4)->FindClass(v4, "java/lang/String");


看看JNI.h是怎样定义的

[C++] 纯文本查看 复制代码
jclass      (*FindClass)(JNIEnv*, const char*);


那么,这里没有识别错,看下一个

[C++] 纯文本查看 复制代码
v7 = (*v4)->GetMethodID(v4, v6, "getBytes", "(Ljava/lang/String;)[B");


JNI.h中的定义

[C++] 纯文本查看 复制代码
jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);


可以发现有几处识别出错了,对应着改

再看下一个

[C++] 纯文本查看 复制代码
v8 = _JNIEnv::NewStringUTF((_JNIEnv *)v4, v3);


会发现这里显示v4出错了,但其实v4类型就是JNIEnv*,那么很可能是函数定义处出错了,双击跟过去
[C++] 纯文本查看 复制代码
int __fastcall _JNIEnv::NewStringUTF(_JNIEnv *this, const char *a2)
{
  return (*(int (**)(void))(*(_DWORD *)this + 668))();
}


又是_JNIEnv的锅,这下会修改了,不怕。修复后

[C++] 纯文本查看 复制代码
jstring __fastcall _JNIEnv::NewStringUTF(JNIEnv *this, const char *a2)
{
  return (*this)->NewStringUTF(this, a2);
}


退回刚刚函数
9.png
那么识别出来了。

下面的修复,大同小异,就不再一一演示步骤了,截图再上传好累的,最终修复结果

[C++] 纯文本查看 复制代码
void *__fastcall getStringUnicodeChars(JNIEnv *a1, jobject a2, const char *a3)
{
  const char *v3; // r6@1
  JNIEnv *v4; // r4@1
  jobject v5; // r5@1
  jclass v6; // r0@1
  jmethodID v7; // r8@1
  jstring v8; // r6@1
  jobject v9; // r6@1
  signed int v10; // r5@1
  jbyte *v11; // r8@1
  void *v12; // r7@2

  v3 = a3;
  v4 = a1;
  v5 = a2;
  _android_log_print(4, "humen", "-------------------------------3");// 打印调试
  v6 = (*v4)->FindClass(v4, "java/lang/String");
  v7 = (*v4)->GetMethodID(v4, v6, "getBytes", "(Ljava/lang/String;)[B");
  v8 = _JNIEnv::NewStringUTF(v4, v3);
  _android_log_print(4, "humen", "-------------------------------4");// 打印调试
  v9 = _JNIEnv::CallObjectMethod(v4, v5, v7, (int)v8);
  v10 = (*v4)->GetArrayLength(v4, v9);
  v11 = (*v4)->GetByteArrayElements(v4, v9, 0);
  _android_log_print(4, "humen", "-------------------------------5");// 打印调试
  if ( v10 <= 0 )                               // 元素为空
  {
    v12 = 0;
  }
  else
  {
    v12 = malloc(v10 + 1);
    memcpy(v12, v11, v10);                      // 拷贝
    *((_BYTE *)v12 + v10) = 0;
  }
  _android_log_print(4, "humen", "-------------------------------6");
  (*v4)->ReleaseByteArrayElements(v4, v9, v11, 0);// 释放空间
  return v12;
}



那么可以继续往下分析函数的作用了

GetMethodID    ->    每一个函数都有对应的ID,呢么我们怎样得到呢?前面JNI那篇文章说了JNI注册时会将函数的签名传进去,也就是这里的  v6, "getBytes", "(Ljava/lang/String;)[B",v6就是java/lang/String


NewStringUTF ->字符串转化,没啥多讲

CallObjectMethod  -> 函数调用,这也是为啥它有那么多重载的原因,因为你调用的函数返回值有多种类型,这里就需要传入刚刚的MethodID了,比较要告诉它调用哪个函数,对吧

GetArrayLength ->获得数组长度,不多说

GetByteArrayElements -> 获得数组元素

ReleaseByteArrayElements - >最后释放数组空间

_android_log_print  ->打印调试用的方法

那么,这个getStringUnicodeChars函数算是修复完毕了



那么贴出,Java_com_humen_crackme010_CheckUtil_checkPass函数代码以及一点简单地解释


[C++] 纯文本查看 复制代码
jstring __fastcall Java_com_humen_crackme010_CheckUtil_checkPass(JNIEnv *env, jclass a2, jstring input)
{
  JNIEnv *v3; // r7@1
  const char *v4; // r0@1
  const char *v5; // r4@1
  size_t v6; // r0@1
  signed int v7; // r2@1
  signed int length; // r6@1
  int v9; // r3@3
  char v10; // r0@3
  signed int v11; // r1@3
  int i; // r3@3
  char v13; // r5@5
  signed int v14; // r5@6
  signed int v15; // r8@6
  signed int j; // r3@9
  const char *v17; // r1@13

  v3 = env;
  v4 = (const char *)getStringUnicodeChars(env, input, "utf-8");
  v5 = v4;
  v6 = strlen(v4);                              // 获取长度
  v7 = 0;
  length = v6;
  while ( 1 )
  {
    v7 += 2;
    if ( v7 >= length )
      break;
    v9 = (int)&v5[v7];
    v10 = v5[v7 - 1];
    *(_BYTE *)(v9 - 1) = v5[v7 - 2];
    v11 = 0;
    *(_BYTE *)(v9 - 2) = v10;
    for ( i = (int)(v5 + 4); ; *(_BYTE *)(i - 8) = v13 )
    {
      v11 += 4;
      i += 4;
      if ( v11 >= length )
        break;
      v13 = *(_BYTE *)(i - 4);
      *(_BYTE *)(i - 4) = *(_BYTE *)(i - 8);
    }
  }                                             // 转换
  v14 = 0;
  v15 = strlen(v5);                             // 获取长度
  while ( v14 < v15 )
    _android_log_print(4, "humen", "%c", v5[v14++]);// 打印
  printf("%s", v5);
  for ( j = 0; v5[j] == (unsigned __int8)t[j]; ++j )// S!@#@1FD23154A34
    ;
  if ( j <= length )
    v17 = "b";
  else
    v17 = "a";
  return _JNIEnv::NewStringUTF(v3, v17);
}


算法就不讲了,就是转换了


-------------------------

0x03 总结

这道题本身是不难的,但是可以学到IDA的小技巧,但是有一点,如果你分析汇编当我没说23333,这个应该是不会出现类型识别错误的问题233333
嘻嘻,只是个人分享的一点点小经验,又让师傅们笑话了

马上五一,祝大家五一快乐,以后有机会再接着分享





免费评分

参与人数 9威望 +2 吾爱币 +20 热心值 +9 收起 理由
siuhoapdou + 1 + 1 谢谢@Thanks!
qtfreet00 + 2 + 12 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
xuing + 1 + 1 感谢
ahmeijian + 1 + 1 热心回复!
施偶 + 1 + 1 谢谢@Thanks!
xiangyuruchu520 + 1 + 1 谢谢@Thanks!
myouter + 1 + 1 用心讨论,共获提升!
挥汗如雨 + 1 + 1 另一种思路,学到了。
夏雨微凉 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| Enigma_G 发表于 2017-4-29 06:32
myouter 发表于 2017-4-29 01:44
恕我直言,算法过程才是重点,尤其是这个反汇编结果并不是很清晰

我并没有说过算法不是重点啊,我只是说我不分析,人家分析过了,而且分析的很好,我干嘛做重复工作,开头我就已经声明我只是分享一下我分析apk的经验以及自己一些技巧而已,你要看算法分析我前面链接已给
myouter 发表于 2017-4-29 16:32
Enigma_G 发表于 2017-4-29 06:32
我并没有说过算法不是重点啊,我只是说我不分析,人家分析过了,而且分析的很好,我干嘛做重复工作,开头 ...

相对另一个帖子,算法的可读性略差。。也不是什么大问题。楼主修复的很仔细,对于没接触过开发的坛友还是很有帮助的。感谢分享。
niconico128 发表于 2017-4-29 01:18
myouter 发表于 2017-4-29 01:44
本帖最后由 myouter 于 2017-4-29 02:07 编辑

恕我直言,算法过程才是重点,尤其是这个反汇编结果并不是很清晰
 楼主| Enigma_G 发表于 2017-4-29 06:34
Enigma_G 发表于 2017-4-29 06:32
我并没有说过算法不是重点啊,我只是说我不分析,人家分析过了,而且分析的很好,我干嘛做重复工作,开头 ...

而且我觉得这反汇编结果已经非常清楚了,如果觉得看伪代码不清楚,可以试试分析汇编
夏雨微凉 发表于 2017-4-29 07:47
高手啊!         
夏雨微凉 发表于 2017-4-29 07:53
清晨起来,激励一下自己~~~
tony2526 发表于 2017-4-29 08:01
还是科班出身搞编程开发的比较有优势啊,厉害
挥汗如雨 发表于 2017-4-29 08:29
Enigma_G 发表于 2017-4-29 06:32
我并没有说过算法不是重点啊,我只是说我不分析,人家分析过了,而且分析的很好,我干嘛做重复工作,开头 ...

楼主说的很对,限于安卓基础不好所以我重点是算法,楼主正好给我补上了Android分析过程
挥汗如雨 发表于 2017-4-29 08:33
又看了一遍,变量类型转换讲的很好
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 16:38

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表