当前位置: 代码迷 >> 综合 >> ISCC2019——mobile01 之用android studio动态调试SO文件
  详细解决方案

ISCC2019——mobile01 之用android studio动态调试SO文件

热度:46   发布时间:2023-12-25 04:14:23.0

参考:https://www.zhaoj.in/read-5629.html 内含该APK下载链接 感谢作者

  1. 安卓逆向第一步 拖进JEB
    翻到MainActivity函数
package com.iscc.crackme;import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View$OnClickListener;
import android.view.View;
import android.widget.Toast;public class MainActivity extends AppCompatActivity {
    static {
    System.loadLibrary("native-lib");}public MainActivity() {
    super();}static boolean access$000(MainActivity arg1, String arg2) {
    return arg1.checkFirst(arg2);}private boolean checkFirst(String arg5) {
    if(arg5.length() != 16) {
    return 0;}int v0 = 0;while(true) {
    if(v0 >= arg5.length()) {
    return 1;}if(arg5.charAt(v0) <= 56) {
    if(arg5.charAt(v0) < 49) {
    }else {
    ++v0;continue;}}return 0;}return 0;}public native boolean checkSecond(String arg1) {
    }protected void onCreate(Bundle arg4) {
    super.onCreate(arg4);this.setContentView(0x7F09001C);this.findViewById(0x7F070022).setOnClickListener(new View$OnClickListener(this.findViewById(0x7F070038)) {
    public void onClick(View arg5) {
    String v0 = this.val$editText.getText().toString().trim();if(!MainActivity.this.checkFirst(v0) || !MainActivity.this.checkSecond(v0)) {
    Toast.makeText(MainActivity.this, "注册失败!", 0).show();}else {
    Toast.makeText(MainActivity.this, "注册成功!", 0).show();}}});}
}
  1. 我们发现checkFirst函数简单的做了一个长度检查(16)和限制1~8的数字
  2. 但注册成功需要checkSecond函数也成功 我们注意到这里有一句public native boolean checkSecond(String arg1){}
    好,我们先来学习两个概念
  • 什么是native?
    答:Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能。可以将native方法比作Java程序同C程序的接口。
  • 什么是so文件?
    so(shared object)
    这个so文件,有时候是直接被调用的,有时候是会参与到编译中的,也就是说,一个.so文件可能会被多个应用程序用到,因此取名叫share object。
  1. 让我们来找找SO文件
    在这里插入图片描述
    右键将其导出 我喜欢用IDA来反编译~
  2. 拖进IDA 函数很多 搜check 这三个是函数的主体 一个个看吧
    在这里插入图片描述
  3. 先看我们之前调用的checkSecond
int __cdecl Java_com_iscc_crackme_MainActivity_checkSecond(int a1, int a2, int a3)
{
    bool v3; // zf@5signed int v5; // [sp-4h] [bp-80h]@1int v6; // [sp+0h] [bp-7Ch]@1int v7; // [sp+4h] [bp-78h]@1unsigned __int8 v8; // [sp+10h] [bp-6Ch]@5char v9; // [sp+11h] [bp-6Bh]@2char v10; // [sp+12h] [bp-6Ah]@1char v11; // [sp+13h] [bp-69h]@1int v12; // [sp+14h] [bp-68h]@1int v13; // [sp+18h] [bp-64h]@1int v14; // [sp+1Ch] [bp-60h]@1int *v15; // [sp+20h] [bp-5Ch]@1signed int v16; // [sp+24h] [bp-58h]@1char v17; // [sp+2Bh] [bp-51h]@1unsigned __int8 v18; // [sp+37h] [bp-45h]@3int v19; // [sp+38h] [bp-44h]@2int v20; // [sp+48h] [bp-34h]@1int v21; // [sp+58h] [bp-24h]@1int v22; // [sp+68h] [bp-14h]@1v5 = 35617;v22 = *MK_FP(__GS__, 20);//MK_FP是一个宏。功能是做段基址加上偏移地址的运算,也就是取实际地址v16 = 208460;*(int *)((char *)&dword_32E54 + (_DWORD)&v6 - 208460) = a3;*(int *)((char *)&dword_32E50 + (_DWORD)&v6 - 208460) = a1;v15 = &v21;v14 = a3;v13 = a2;v12 = a1;jstring2str(&v21);v17 = 0;*(int *)((char *)&dword_32E50 + (_DWORD)&v6 - 208460) = (int)v15;std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::basic_string(&v20, v7);v11 = checkfirst(&v20);//从上一句看v20是一个字符串 通过checkfirst函数对这个字符串做个检查 注意到是小写的firstv10 = 0;if ( v11 & 1 )//如果checkfirst通过继续checkAgain{
    *(int *)((char *)&dword_32E50 + (_DWORD)&v6 - 208460) = (int)&v21;std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::basic_string(&v19, v7);v17 = 1;v9 = checkAgain((int)&v19);//跟进去v10 = v9;}v18 = v10 & 1;if ( v17 & 1 )std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::~basic_string(&v19);std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::~basic_string(&v20);std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::~basic_string(&v21);v3 = *MK_FP(__GS__, 20) == v22;v8 = v18;if ( !v3 )JUMPOUT(*(_DWORD *)algn_8D04);return v8;
}
int __cdecl checkfirst(int a1)
{
    int v2; // [sp+4h] [bp-94h]@7int v3; // [sp+10h] [bp-88h]@4signed int i; // [sp+1Ch] [bp-7Ch]@1char v5; // [sp+23h] [bp-75h]@10for ( i = 1; i < 8; ++i ){
    if ( *(_BYTE *)a1 & 1 )//第一字节是奇数v3 = *(_DWORD *)(a1 + 8);//移动到下一个字节else//否则会偏移1位 很麻烦 所以我们只要让上面这个条件成立也是可以的v3 = a1 + 1;if ( *(_BYTE *)a1 & 1 )v2 = *(_DWORD *)(a1 + 8);elsev2 = a1 + 1;//很难理解v2 v3的操作 但有一点可以肯定 经过这个if else之后两者的值是一样的//然后我们注意到下面做了一个字符的比较要求后一个大于前一个 否则返回false //比较了7次 为 12345678if ( *(_BYTE *)(v3 + i) <= (signed int)*(_BYTE *)(v2 + i - 1) ){
    v5 = 0;return v5 & 1;//即return 0;}}v5 = 1;return v5 & 1;//即return 1;
}
  1. 大致逻辑有了 检查字符串前8(也有可能是后8,代码逻辑看不出)为12345678 之后会在checkAgain函数中再次做一个复杂的检查 静态调试继续深入十分费力 有什么好的办法呢? 就是动态调试了
  2. 逻辑:构造一个12345678+长度为8(每位1-8)的字符串让checkAgain检查如果通过就是答案 8^8=1.7kw次运算 合理!
  3. 我们用android studio做这件事情 配置详情可直接参考原文章
    MainActivity.java代码如下
package com.iscc.crackme;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
    static{
    System.loadLibrary("native-lib");}@Overrideprotected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Toast.makeText(this,"123",Toast.LENGTH_SHORT).show();int cnt = 0;int flag = 0;for(int i=1;i<=8;++i){
    if(flag == 1) break;for(int j=1;j<=8;++j){
    for(int q=1;q<=8;++q){
    for(int k=1;k<=8;++k){
    for(int x=1;x<=8;++x){
    for(int y=1;y<=8;++y){
    for(int z=1;z<=8;++z){
    for(int m=1;m<=8;++m){
    final String paramString = "12345678" + i + j + q + k + x + y + z + m;if(checkSecond(paramString)){
    System.out.println(paramString+":"+checkSecond(paramString));flag = 1;}cnt += 1;if(cnt%100000==0){
    //用来观测进行到哪里了System.out.println(paramString);}}}}}}}}}}public native boolean checkSecond(String paramString);
}

可以在log日志中找到答案

  相关解决方案