IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> x键盘app逆向分析流程(顺带梳理流程) -> 正文阅读

[移动开发]x键盘app逆向分析流程(顺带梳理流程)

作者:prism language-c

0x01 IOS分析 拿到的是IOS版本的报文随机先直接解压IPA包

解压提取出来的二进制文件放入IDA里面发现会反编译失败 拖进去直接会显示
在这里插入图片描述

说明ios是有壳的随机使用 frida-ios-dump开始砸壳 砸壳后开始反编译完成后搜索字符串
在这里插入图片描述

发现是在此函数中调用
在这里插入图片描述

在此函数中定位关键位置 两个接口 并发现setHttpbody位置 可以看出v58就是setHttpbody的内容 继续往上跟 v58来自v55 v55是v54编码后 v54来自v53 v53来自v52 v52来自v51来自InnoSecureMain类的innoSecureEncode方法返回值
v48就是传进去的参数(应该是明文数据)
在这里插入图片描述

继续分析InnoSecureMain类的innoSecureEncode方法直接定位关键函数
在这里插入图片描述

IDA反编译失败
在这里插入图片描述

汇编代码如下
在这里插入图片描述

除了Encode函数其实还有Decode函数
在这里插入图片描述

其实可以看这里有很多常量 可以google 或者github搜一下这些常量 但是本次搜索无结果
在这里插入图片描述
在这里插入图片描述

此函数的汇编更加长
在这里插入图片描述

可以发现现在需要一步步看汇编 因为不想看那么多汇编 太浪费时间 转而分析Android端

0x02 分析Android 下载X键盘Android端

直接拖进GDA发现dex有360的壳 一开始使用FRAT的frida版 发现脱不出来 随即使用
脱壳工具整理:https://blog.csdn.net/u010671061/article/details/122085944
https://github.com/lasting-yang/frida_dump

最后使用frida_dump成功脱壳 脱出来结果拷贝出来如下
在这里插入图片描述

塞回apk中使用apktool反编译 导入android studio搜索关键词 cloudpredict继续跟踪 同时配合GDA或者JADX来看代码
在这里插入图片描述

在这里继续跟踪当然也可以直接打断点调试 发现最后发送的请求会在这里
在这里插入图片描述

IMCoreService.c方法发现和IMCoreService.a方法对应native接口如下
在这里插入图片描述

定位so
在这里插入图片描述

把so拖进ida反编译

(一)首先看函数名 发现都是去符号的sub函数

(二)关键词encrypt decrypt等等 继续往上跟一无所获

在这里插入图片描述

(三)看initarray 把这里所有的sub函数都hook一遍发现没有一个会走的

在这里插入图片描述

(四)Hook registernative函数 看注册了哪些函数 但是一hook就崩溃 (可能是反hook不一定)

//frida -U --no-pause -f  com.zenmen.palmchat  -l .\hook_jnionload.js 


var RevealNativeMethods = function() {
    var pSize = Process.pointerSize;
    var env = Java.vm.getEnv();
    var RegisterNatives = 215, FindClassIndex = 6; // search "215" @ https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html
    var jclassAddress2NameMap = {};
    function getNativeAddress(idx) {
      return env.handle.readPointer().add(idx * pSize).readPointer();
    }
    // intercepting FindClass to populate Map<address, jclass>
    Interceptor.attach(getNativeAddress(FindClassIndex), {
      onEnter: function(args) {
        jclassAddress2NameMap[args[0]] = args[1].readCString();
      }
    });
    // RegisterNative(jClass*, .., JNINativeMethod *methods[nMethods], uint nMethods) // https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#977
    Interceptor.attach(getNativeAddress(RegisterNatives), {
      onEnter: function(args) {
        for (var i = 0, nMethods = parseInt(args[3]); i < nMethods; i++) {
          /*
            https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129
            typedef struct {
               const char* name;
               const char* signature;
               void* fnPtr;
            } JNINativeMethod;
          */
          var structSize = pSize * 3; // = sizeof(JNINativeMethod)
          var methodsPtr = ptr(args[2]);
          var signature = methodsPtr.add(i * structSize + pSize).readPointer();
          var fnPtr = methodsPtr.add(i * structSize + (pSize * 2)).readPointer(); // void* fnPtr
          var jClass = jclassAddress2NameMap[args[0]].split('/');
            var methodName = methodsPtr.add(i * structSize).readPointer().readCString();
                var str_name_so = "libPinyinCore.so";    //需要hook的so名
                var n_addr_so = Module.findBaseAddress(str_name_so); //加载到内存后 函数地址 = so地址 + 函数偏移
          console.log('\x1b[3' + '6;01' + 'm', JSON.stringify({
            module: DebugSymbol.fromAddress(fnPtr)['moduleName'], // https://www.frida.re/docs/javascript-api/#debugsymbol
            package: jClass.slice(0, -1).join('.'),
            class: jClass[jClass.length - 1],
            method: methodName, // methodsPtr.readPointer().readCString(), // char* name
            signature: signature.readCString(), // char* signature TODO Java bytecode signature parser { Z: 'boolean', B: 'byte', C: 'char', S: 'short', I: 'int', J: 'long', F: 'float', D: 'double', L: 'fully-qualified-class;', '[': 'array' } https://github.com/skylot/jadx/blob/master/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java
            address: (fnPtr-n_addr_so).toString(16)
          }), '\x1b[39;49;00m');
        }
      }
    });
  }
   
function hook_java() {
    Java.perform(RevealNativeMethods);
}


function main() {
    hook_java(); 
}


setImmediate(main);

(五)JNIOnload分析
在这里插入图片描述
在这里插入图片描述
到关键函数发现被处理过了 不得不说很大可能是有意而为之
在这里插入图片描述

继续点进去看
Coreencryptda
在这里插入图片描述

Coredecryptdat
在这里插入图片描述

都是反编译失败的十六进制数据现在就是难办了 找不到注册的函数

0x03 无其他方法下定位关键位置

native输入输出参数分析 这里有点猫腻 进去是222字节 出来也是222字节 首先排除块加密 很大概率就是字节处理
说到字节处理那经常用到的不就是异或吗 直接搜异或的相关指令

在这里插入图片描述

边搜边使用frida进行hook 打调用栈 看看会不会调用 前面反编译失败的十六进制位置 这个时候就是看指令对不对头 这个看起来不对劲就hook一下 但是我第一次hook就成功了(瞎猫碰上死耗子)

在这里插入图片描述
在这里插入图片描述

hook sub_24D6C 调用栈如下:(这里其实也可以直接看函数参数 也能发现有报文内容)

RegisterNatives called from:\n0x55a48d8b libPinyinCore.so!0x40d8b\n0x55ae9c09 libPinyinCore.so!0xe1c09\n0xeb6fc2df libart.so!art_quick_generic_jni_trampoline+0x2e\n0xeb6f77d7 libart.so!art_quick_invoke_stub_internal+0x46\n0xebb1450d libart.so!art_quick_invoke_static_stub+0x118\n0xeb750897 libart.so!_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+0xaa\n0xeb8649c3 libart.so!_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_11ShadowFrameEtPNS_6JValueE+0x102\n0xeb85c6e3 libart.so!_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+0x2ee\n0xeb704fd5 libart.so!_ZN3art11interpreter20ExecuteSwitchImplCppILb0ELb0EEEvPNS0_17SwitchImplContextE+0x761c\n0xeb6fce77 libart.so!ExecuteSwitchImplAsm+0x6\n0xeb8556b3 libart.so!_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEbb.llvm.94699277460487538+0x186\n0xeb85bf0d libart.so!_ZN3art11interpreter33ArtInterpreterToInterpreterBridgeEPNS_6ThreadERKNS_20CodeItemDataAccessorEPNS_11ShadowFrameEPNS_6JValueE+0x94\n0xeb85c6cb libart.so!_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+0x2d6\n0xeb704fd5 libart.so!_ZN3art11interpreter20ExecuteSwitchImplCppILb0ELb0EEEvPNS0_17SwitchImplContextE+0x761c\n0xeb6fce77 libart.so!ExecuteSwitchImplAsm+0x6\n0xeb8556b3 libart.so!_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEbb.llvm.94699277460487538+0x186\n

按G跳转 0xe1c09
在这里插入图片描述

往上拉 这不刚好就是刚刚反汇编失败的
在这里插入图片描述

(关于定位解密response body位置 也是直接搜异或因为定位到了 加密 解密指令应该也差不多 也可以分析一下加密流程也能得出结果)
或者直接搜加密的 ORR.W指令 刚好紧跟着就是response body解密
在这里插入图片描述

0x04 汇编分析 定位到位置 现在就是分析解密逻辑了

在这里插入图片描述

我们发现v13只有定义没有赋值 结合汇编来看可以看出 R1是v13 指令 LDRB.W R0,[R8,R1] 表示从R8 偏移 R1加载到R0 那么 R1从哪里来的
最近一次使用R1 是在 mov R1,R9 也就是函数sub_1D1C34的第二个参数 那也有可能R1是在这里面被改变了
在这里插入图片描述

hook sub1D1C34这个函数发现返回值就是传进去的参数 因此可以说明这个函数只是改变R1值作用 其他并没有实际作用 只是为了混淆我们观察得对象 接下来我们直接hook内存地址 不hook函数 直接hook 0x24E10这个地址 第一个参数就是R0 第二个参数就是我们想要得

   var R1_addr  = libPinyinCore.add(0x24E10+1);
     console.log("R1_addr:",R1_addr);//thumb指令集 0x000011F8 0xe6f851f9
     Interceptor.attach(R1_addr,{
             onEnter: function(args){
                 console.log(args[0],"---------------------hook2 on enter",args[1]," r4:",args[4]);
             },onLeave: function(retval){
                 console.log(args[0],"---------------------hook2 on leave",args[1]);
             }
     })

hook出来的结果为 可以发现第二个参数R1 就是1-12循环往复 到此解密逻辑分析完毕(关于为什么第一个参数是author:v-max的第一位 因为00024E10这个内存地址的指令还没有执行完 所以值暂时没有改变 EORS R0, R4 指令执行完后会把结果放到R0 而我们hook的是在onEnter阶段 这也进一步验证了frida 内存劫持以及插桩的原理)

0x61 ---------------------hook2 on enter 0x0  r4: 0xc
0x75 ---------------------hook2 on enter 0x1  r4: 0xc
0x74 ---------------------hook2 on enter 0x2  r4: 0xc
0x68 ---------------------hook2 on enter 0x3  r4: 0xc
0x6f ---------------------hook2 on enter 0x4  r4: 0xc
0x72 ---------------------hook2 on enter 0x5  r4: 0xc
0x3a ---------------------hook2 on enter 0x6  r4: 0xc
0x76 ---------------------hook2 on enter 0x7  r4: 0xc
0x2d ---------------------hook2 on enter 0x8  r4: 0xc
0x6d ---------------------hook2 on enter 0x9  r4: 0xc
0x61 ---------------------hook2 on enter 0xa  r4: 0xc
0x78 ---------------------hook2 on enter 0xb  r4: 0xc
0x61 ---------------------hook2 on enter 0x0  r4: 0xc
0x75 ---------------------hook2 on enter 0x1  r4: 0xc
0x74 ---------------------hook2 on enter 0x2  r4: 0xc
0x68 ---------------------hook2 on enter 0x3  r4: 0xc
0x6f ---------------------hook2 on enter 0x4  r4: 0xc
0x72 ---------------------hook2 on enter 0x5  r4: 0xc
0x3a ---------------------hook2 on enter 0x6  r4: 0xc
0x76 ---------------------hook2 on enter 0x7  r4: 0xc
0x2d ---------------------hook2 on enter 0x8  r4: 0xc
0x6d ---------------------hook2 on enter 0x9  r4: 0xc
0x61 ---------------------hook2 on enter 0xa  r4: 0xc
0x78 ---------------------hook2 on enter 0xb  r4: 0xc
0x61 ---------------------hook2 on enter 0x0  r4: 0xc
0x75 ---------------------hook2 on enter 0x1  r4: 0xc
0x74 ---------------------hook2 on enter 0x2  r4: 0xc
0x68 ---------------------hook2 on enter 0x3  r4: 0xc
0x6f ---------------------hook2 on enter 0x4  r4: 0xc
0x72 ---------------------hook2 on enter 0x5  r4: 0xc
0x3a ---------------------hook2 on enter 0x6  r4: 0xc
0x76 ---------------------hook2 on enter 0x7  r4: 0xc

解密代码编写完成后 发现也能解android端的另外一个接口和ios端的两个接口 至此分析完成

#include <iostream>
  
#include <string.h>  
#include <stdlib.h>  


using namespace std;


static const int B64_LINE_LENGTH = 76;
static const int LINE_GROUPS = 19;
static const char CRLF_CR = '\r';
static const char CRLF_LF = '\n';
static const char PADDING_CHAR = '=';

/**
 * Base64 index table.
 */
static const unsigned char data_bin2ascii[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static const unsigned char data_ascii2bin[128] = {
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xE0, 0xF0, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xF2, 0xFF, 0x3F,
        0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
        0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF,
        0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
        0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
        0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
        0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
        0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
        0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
        0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
};


static unsigned char conv_ascii2bin(unsigned char a, const unsigned char* table) {
    return table[a];
}

size_t decodeB64(const unsigned char* in, unsigned char* out, size_t len) {
    size_t outLength = 0;
    int groupIndex = 0;
    unsigned long l;
    const unsigned char* table = data_ascii2bin;
    unsigned char group[4];
    unsigned char temp;
    for (int i = 0; i < len; i++) {
        temp = *(in++);
        if (temp == PADDING_CHAR) {
            break;
        }
        if (temp == CRLF_CR || temp == CRLF_LF) {
            // 遇到换行,继续
            continue;
        }
        group[groupIndex++] = conv_ascii2bin(temp, table);
        if (groupIndex == 4) {
            l = ((((unsigned long)group[0]) << 18L) |
                (((unsigned long)group[1]) << 12L) |
                (((unsigned long)group[2]) << 6L) | (((unsigned long)group[3])));
            *(out++) = (unsigned char)(l >> 16L) & 0xff;
            *(out++) = (unsigned char)(l >> 8L) & 0xff;
            *(out++) = (unsigned char)(l) & 0xff;
            outLength += 3;
            groupIndex = 0;
        }
    }
    if (groupIndex > 0) {
        l = ((((unsigned long)group[0]) << 18L) |
            (((unsigned long)group[1]) << 12L) |
            (((unsigned long)group[2]) << 6L) | (((unsigned long)group[3])));
        *(out++) = (unsigned char)(l >> 16L) & 0xff;
        if (groupIndex == 3) {
            // 代表末尾填充了一个 =
            *(out++) = (unsigned char)(l >> 8L) & 0xff;
        }
        outLength += groupIndex - 1;
    }
    // 最末尾加一个字符串结束符
    *out = '\0';
    return outLength;
}

int main()
{
const char *base = "AWOAo2PpgQgja8pJ6CChA0HpYArrAQKhgMAAZQDgikAJ44sp6mjIq4tJYcnDqypJ6MJjwGPiSEACaUDiYuAiYWNnisALI4vJ6ihISykiqoDrAUCBRsJjQ0HpYcmCaw==";
int outBufLen = 2000;
unsigned char *input = new unsigned char[strlen(base)];
memset(input,0,strlen(base));
    int outLength = decodeB64((unsigned char *)base, input, strlen(base));


    uint8_t str[] = {0x61,0x75,0x74,0x68,0x6F,0x72,0x3A,0x76,0x2D,0x6D,0x61,0x78};
    uint8_t* result = new uint8_t[strlen((char *)input)];

    for (int i = 0; i < outLength; i++)
    {
        result[i] = ((input[i] >> 5) | (input[i] << 3)) ;
        result[i] = result[i] ^ str[i % 12];
    }

    for (int j = 0; j < outLength; j++) {
        printf("%c", result[j]); //0x%02x

    }

return 0;
}
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-09-21 00:40:23  更:2022-09-21 00:43:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 5:54:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码