记一次 APK 逆向动静调试 + so 动态链接库分析

0x00 前言:

好久没有做过安卓逆向了,最近重新系统地学习了安卓逆向技术。找到了一道较为典型的逆向分析题来练手,以锻炼动静态分析和动态链接库分析的基本能力。在这里记录基本的分析流程手法。
在这里插入图片描述


0x01 逆向分析:

一、使用 Genymotion 生成一个安卓虚拟机,把目标 Apk 拖入安装并运行:

Genymotion 是一款出色的跨平台的Android模拟器,具有容易安装和使用、运行速度快、自带 ROOT 的特点, 是Android开发、测试等相关人员的必备工具。

任意输入,返回flag错误。初步判断该软件类似于卡密软件,需要输入正确的卡密才能使用。


二、使用 Jeb 对目标 Apk 进行动静分析:

拖入 jeb 查看 Java 字节码如下:
在这里插入图片描述

tab 键反汇编如下:

在这里插入图片描述
静态分析可知:
主函数主要是获取输入框输入,判断输入字符串长度是否 > 10, 并且字符串收尾由 flag{ 和 } 组成。符合基本条件,就可以进入 check.check() 函数,对 flag{} 包裹的字符串进行检查,验证是否正确。
进入 check 函数:
在这里插入图片描述
可知该check函数采用 AES 对称加密对参数进行加密,然后base64编码后传入 Myjni.encode函数进行二次编码,编码后的结果与 NOYKxeJRlz65XGjgTODxUvJIBdnY8NQZNQgnoK5Mxckh3fhvJjNFWoBM8wVCdfOz 进行比较,比较相同则输入的flag正确。

其中 MyJni 是外部链接库,待会还得逆向分析 MyJni 所在的 .so 外部链接库。
在这里插入图片描述


基本程序逻辑已经理清楚,Ctrl + B 在check 函数中打入断点
在这里插入图片描述
开始 动态调试
在这里插入图片描述
按基本要求输入字符串,断点成功被卡住:
在这里插入图片描述
断点调试直到 0000000E invoke-static Myjni->getkey()String获取AES加密秘钥:Z29qZSUgYKMmYJ5fch9kZL==
在这里插入图片描述

运行到 00000096 invoke-static Base64->encodeToString([B, I)String, p0, v1 生成加密结果:uOkRlVa7zcEd9qTGJdreAw==

在这里插入图片描述
运行 0000009E invoke-static Myjni->encode(String)String, p0 进行二次编码,多次尝试发现,每次编码的结果都不同,而且编码后长度还变短了,有点诡异。以下是三次相同字符串(flag{xxxxxxxxxxxxxx})的二次编码结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


没什么头绪,开始分析 MyJni 所在的 libHello.so, 分析 encode 函数逻辑。导出 libHello.so
在这里插入图片描述
放入 IDA 中进行分析:
在这里插入图片描述
encode 函数的汇编代码如下:

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

jstring __fastcall Java_com_example_myapplication_Myjni_encode(JNIEnv *env, jclass jclass, jstring jflag)
{
  const char *v4; // r12
  size_t i; // rbx
  char v6; // al
  char v7; // dl
  int v8; // ecx
  __m128 v10; // [rsp+0h] [rbp-78h] BYREF
  __m128 v11; // [rsp+10h] [rbp-68h]
  __int128 v12; // [rsp+20h] [rbp-58h]
  __int128 v13; // [rsp+30h] [rbp-48h]
  char v14; // [rsp+40h] [rbp-38h]
  unsigned __int64 v15; // [rsp+50h] [rbp-28h]

  v15 = __readfsqword(0x28u);
  v4 = env->functions->GetStringUTFChars(env, jflag, 0LL);
  env->functions->ReleaseStringUTFChars(env, jflag, v4);
  if ( *v4 )
  {
    for ( i = 0LL; strlen(v4) > i; ++i )
    {
      v8 = v4[i];
      if ( (unsigned int)(v8 - 123) > 0xFFFFFFE5 )
      {
        v6 = 97;
        v7 = -97;
      }
      else
      {
        if ( (unsigned int)(v8 - 91) < 0xFFFFFFE6 )
          continue;
        v6 = 65;
        v7 = -65;
      }
      v4[i] = v6
            + v7
            + v8
            + 17
            - 26 * ((20165 * ((char)(v7 + v8) + 17) < 0) + ((unsigned int)(20165 * ((char)(v7 + v8) + 17)) >> 19));
    }
  }
  v13 = 0LL;
  v12 = 0LL;
  v11 = 0LL;
  v10 = 0LL;
  v14 = 0;
  v10 = _mm_movelh_ps((__m128)*((unsigned __int64 *)v4 + 1), (__m128)*((unsigned __int64 *)v4 + 7));
  v11 = _mm_movelh_ps((__m128)*((unsigned __int64 *)v4 + 3), (__m128)*((unsigned __int64 *)v4 + 6));
  v12 = *((_OWORD *)v4 + 2);
  *(_QWORD *)&v13 = *((_QWORD *)v4 + 2);
  *((_QWORD *)&v13 + 1) = *(_QWORD *)v4;
  return env->functions->NewStringUTF(env, &v10);
}

简单分析如下:

在这里插入图片描述

主要分两段处理逻辑:第一段字符串替换加密,第二段字符串重组。


①、替换加密算法分析与逆向:

在这里插入图片描述
转成 python 代码:

after_aes_base64_value = "" # aes加密后base64编码值
ans = "" # 替换后的结果
for i in range(len(after_aes_base64_value )):
    v8 = ord(after_aes_base64_value[i])
    if v8 > 96:
        v6 = 97
        v7 = -97
    else:
        if v8 < 65:
            ans.append(v8)
            continue
        v6 = 65
        v7 = -65
    ans.append(v6 + v7 + v8 + 17 - 26 * (20165 * ((v7 + v8 + 17) < 0) + (20165 * (v7 + v8 + 17)) >> 19))

这段代码对字符串中的字母进行了一个复杂的变换,而非字母字符保持不变。

对于字符串中的每个字符,获取字符的ASCII值(v8)。检查 v8 是否大于96(表示是小写字母)。如果v8小于65(不是大写字母或数字),直接将v8添加到ans。确定基值 v6(大写字母为65,小写字母为97)和负基值v7。使用公式计算加密值: v6 + v7 + v8 + 17 - 26 * (20165 * ((v7 + v8 + 17) < 0) + (20165 * (v7 + v8 + 17)) >> 19)

根据以上分析写出逆向解密算法

# 已知加密后的ans值
ans = []
# 逆向函数,计算原始字符的ASCII值
def reverse_char(encoded_char, base):
    v7 = -base
    v8_candidates = []
    for v8 in range(base, base + 26):
        # 根据加密公式计算对应的原始字符
        encoded_value = base + v7 + v8 + 17 - 26 * (20165 * ((v7 + v8 + 17) < 0) + (20165 * (v7 + v8 + 17) >> 19))
        if encoded_value == encoded_char:
            v8_candidates.append(v8) 
    return v8_candidates

# 存储逆向替换字符后的字符串
after_aes_base64_value  = []

# 对每个加密后的字符进行逆向计算
for char in ans:
    if 97 <= char <= 122:  # 小写字母
        v8_candidates = reverse_char(char, 97)
    elif 65 <= char <= 90:  # 大写字母
        v8_candidates = reverse_char(char, 65)
    else:
        # 非字母或数字字符直接添加
        after_aes_base64_value.append(chr(char))
        continue
    if v8_candidates:
        # 选择第一个候选值作为解密结果
        after_aes_base64_value.append(chr(v8_candidates[0]))

②、字符串重组分析与逆向

在这里插入图片描述

在UTF-8编码下,一个字符 ‘a’ 占1个字节(8位);在UTF-16编码下,‘a’ 占2个字节(16位)。分析上述代码可知,每次合并拿 64位,也就是 64 / 8 = 8 个字符, 8个字符为一组进行重组。重组后的字符串NOYKxeJRlz65XGjgTODxUvJIBdnY8NQZNQgnoK5Mxckh3fhvJjNFWoBM8wVCdfOz 长度为64,刚好可以分为8组,重组规则如下:
在这里插入图片描述
编写逆向重组代码

def reorder_string(s):
    # 定义序号顺序
    order = [1, 7, 3, 6, 4, 5, 2, 0]
    # 每组8个字符
    groups = [s[i:i+8] for i in range(0, len(s), 8)]
    reordered_groups = [None] * 8
    # 根据order将组放置到正确的位置
    for index, position in enumerate(order):
        reordered_groups[position] = groups[index]
    return ''.join(reordered_groups)

s = "NOYKxeJRlz65XGjgTODxUvJIBdnY8NQZNQgnoK5Mxckh3fhvJjNFWoBM8wVCdfOz"
result = reorder_string(s)
print(result)
# 8wVCdfOzNOYKxeJRJjNFWoBMTODxUvJINQgnoK5Mxckh3fhvBdnY8NQZlz65XGjg

综上分析,我们即可写出 Myjni.encode 编码对应的解码函数,解码后再 AES 解密,即可得到正确的flag。 刚才动态分析已经获取到了秘钥为:Z29qZSUgYKMmYJ5fch9kZL==,静态分析找到偏移量IV为:ZmxhZ2ZsYWdyZQ==,加密方式是:AES/CBC/PKCS5Padding

故完整解题代码如下:

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import base64

def decode(s):

    # 逆向重组
    order = [1, 7, 3, 6, 4, 5, 2, 0]
    groups = [s[i:i+8] for i in range(0, len(s), 8)]
    reordered_groups = [None] * 8
    for index, position in enumerate(order):
        reordered_groups[position] = groups[index]
    
    ascii_array = [ord(char) for char in ''.join(reordered_groups)]
    
    # 存储逆向替换字符后的字符串
    after_aes_base64_value  = []

    # 逆向替换算法
    for char in ascii_array:
        if 97 <= char <= 122:  # 小写字母
            v8_candidates = reverse_char(char, 97)
        elif 65 <= char <= 90:  # 大写字母
            v8_candidates = reverse_char(char, 65)
        else:
            # 非字母或数字字符直接添加
            after_aes_base64_value.append(chr(char))
            continue
        if v8_candidates:
            # 选择第一个候选值作为解密结果
            after_aes_base64_value.append(chr(v8_candidates[0]))
    return aes_decrypt(''.join(after_aes_base64_value))


# 逆向函数,计算原始字符的ASCII值
def reverse_char(encoded_char, base):
    v7 = -base
    v8_candidates = []
    for v8 in range(base, base + 26):
        # 根据加密公式计算对应的原始字符
        encoded_value = base + v7 + v8 + 17 - 26 * (20165 * ((v7 + v8 + 17) < 0) + (20165 * (v7 + v8 + 17) >> 19))
        if encoded_value == encoded_char:
            v8_candidates.append(v8) 
    return v8_candidates

# aes 解密
def aes_decrypt(encrypted_text):
    try:
        # 将密钥和IV进行编码
        key = 'Z29qZSUgYKMmYJ5fch9kZL=='.encode('utf-8')
        iv = 'ZmxhZ2ZsYWdyZQ=='.encode('utf-8')
        encrypted_text_bytes = base64.b64decode(encrypted_text)
        cipher = AES.new(key, AES.MODE_CBC, iv)
        decrypted_bytes = cipher.decrypt(encrypted_text_bytes)
        decrypted_text = unpad(decrypted_bytes, AES.block_size).decode('utf-8')
        return decrypted_text
    except (ValueError, KeyError) as e:
        return f"解密失败: {str(e)}"


s = "NOYKxeJRlz65XGjgTODxUvJIBdnY8NQZNQgnoK5Mxckh3fhvJjNFWoBM8wVCdfOz"
result = decode(s)
print(f"flag{{{result}}}")

在这里插入图片描述


0x02 总结:

通过 APK 逆向和 so 动态链接库分析,了解应用的内部逻辑和行为。静态分析帮助我们初步理解代码结构和关键点,而动态调试则允许我们在运行时获取更详细的信息。两者结合,可以有效地进行应用的逆向工程和安全分析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/747233.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

视频汇聚平台LntonCVS视频集中存储平台技术解决方案

安防视频监控技术是一种利用各种监控设备捕捉实时画面&#xff0c;并将其传输至监控中心或数据存储设备的技术。随着科技的不断进步&#xff0c;监控视频技术也在不断改进&#xff0c;应用领域也在不断扩展。 然而&#xff0c;尽管技术进步&#xff0c;当前视频监控技术仍然面临…

线性代数基础概念:向量空间

目录 线性代数基础概念&#xff1a;向量空间 1. 向量空间的定义 2. 向量空间的性质 3. 基底和维数 4. 子空间 5. 向量空间的例子 总结 线性代数基础概念&#xff1a;向量空间 向量空间是线性代数中最基本的概念之一&#xff0c;它为我们提供了一个抽象的框架&#xff0c…

WIN版-苹果和平精英画质帧率优化教程

一、视频教程&#xff1a; 想要视频的联系博主 二、图文教程&#xff1a; 前置说明&#xff1a;不按教程&#xff0c;会导致修改不成功&#xff0c;或者设备里面内容丢失。请务必按教程操作&#xff01;&#xff01; 准备工作&#xff08;这部分是在要改的设备上操作&#x…

JAVA每日作业day6.26

ok了家人们&#xff0c;今天我们学习了面向对象-多态&#xff0c;话不多说我们一起来看看吧 一.多态概述 面向对象的第三大特性&#xff1a;封装、继承、多态 我们拿一个生活中的例子来看 生活中&#xff0c;比如跑的动作&#xff0c;小猫、小狗和大象&#xff0c;跑起来是不一…

如何轻松获取 GitLab 指定分支特定路径下的文件夹内容

第一步&#xff1a; 获取 accessToken 及你的 项目 id &#xff1a; 获取 accessToken ,点击用户头像进入setting 按图示操作&#xff0c;第 3 步 填写你发起请求的域名。 获取项目 id , 简单粗暴方案 进入 你项目仓库页面后 直接 源码搜索 project_id&#xff0c; value 就…

基于 elementUI / elementUI plus,实现 主要色(主题色)的一件换色(换肤)

一、效果图 二、方法 改变elementUI 的主要色 --el-color-primary 为自己选择的颜色&#xff0c;核心代码如下&#xff1a; // 处理主题样式 export function handleThemeStyle(theme) {document.documentElement.style.setProperty(--el-color-primary, theme) } 三、全部代…

Fragment与ViewModel(MVVM架构)

简介 在Android应用开发中&#xff0c;Fragment和ViewModel是两个非常重要的概念&#xff0c;它们分别属于架构组件库的一部分&#xff0c;旨在帮助开发者构建更加模块化、健壮且易维护的应用。 Fragment Fragment是Android系统提供的一种可重用的UI组件&#xff0c;它能够作为…

nacos在k8s上的集群安装实践

目录 概述实践nfs安装使用 k8s持久化nacos安装创建角色部署数据库执行数据库初始化语句部署nacos ingress效果展示 结束 概述 本文主要对 nacos 在k8s上的集群安装 进行说明与实践。主要版本信息&#xff0c;k8s: 1.27.x&#xff0c;nacos: 2.0.3。运行环境为 centos 7.x。 实…

centos 使用证书验证拉取gitee代码 配置

简单记录下过程 按官方网站提示即可 cd ~/.ssh/ #如果没有证书 生成一个 ssh-keygen -t rsa[root萨法是的 .ssh]# ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa):▽ Enter passphrase (empty for …

logstash配置文件中明文密码加密

1 案例背景 应用配置文件中禁止使用明文密码&#xff0c;需要加密处理 上图中&#xff0c;红框打码位置为es的明文密码&#xff0c;需要对其进行处理 2 创健keystore文件 /rpa/logstash/bin/logstash-keystore --path.settings /rpa/isa/conf/logstash/ create 注&#xff1…

2024全网最全面及最新且最为详细的网络安全技巧四 之 sql注入以及mysql绕过技巧 (2)———— 作者:LJS

目录 4.5 DNS记录类型介绍(A记录、MX记录、NS记录等&#xff0c;TXT&#xff0c;CNAME&#xff0c;PTR) 4.5.1 DNS 4.5.2 A记录 4.5.3NS记录 4.5.4 MX记录 4.5.5 CNAME记录 4.5.6 TXT记录 4.5.7 泛域名与泛解析 4.5.8域名绑定 4.5.9 域名转向 4.6 Mysql报错注入之floor报错详解…

Okhttp响应Json数据

简介 OkHttp是一个高效、现代的HTTP客户端库&#xff0c;专为Android和Java应用程序设计&#xff0c;用于发送网络请求和处理响应。它支持HTTP/2和SPDY协议&#xff0c;允许连接复用&#xff0c;减少延迟&#xff0c;提高网络效率。OkHttp还处理了常见的网络问题&#xff0c;如…

【目标检测】Yolov8 完整教程 | 检测 | 计算机视觉

学习资源&#xff1a;https://www.youtube.com/watch?vZ-65nqxUdl4 努力的小巴掌 记录计算机视觉学习道路上的所思所得。 1、准备图片images 收集数据网站&#xff1a;OPEN IMAGES 2、准备标签labels 网站&#xff1a;CVAT 有点是&#xff1a;支持直接导出yolo格式的标…

Flutter实现页面间传参

带参跳转 步骤 在router中配置这个路由需要携带的参数,这里的参数是 arguments,注意要用花括号包裹参数名称 在相应组件中实现带参构造函数 在state类中可以直接使用${widget.arguments}来访问到传递的参数 在其他页面中使用Navigator.pushNamed()带参跳转

ansible自动化运维,(2)ansible-playbook

三种常见的数据格式&#xff1a; XML&#xff1a;可扩展标记语言&#xff0c;用于数据交换和配置 JSON&#xff1a;对象标记法&#xff0c;主要用来数据交换或配置&#xff0c;不支持注释 YAML&#xff1a;不是一种标记语言&#xff0c;主要用来配置&#xff0c;大小写敏感&…

BUG cn.bing.com 重定向的次数过多,无法搜索内容

BUG cn.bing.com 重定向的次数过多&#xff0c;无法搜索内容 环境 windows 11 edge浏览器详情 使用Microsoft Edge 必应搜索显示"cn.bing.com"重定向次数过多&#xff0c;无法进行正常的检索功能 解决办法 检查是否开启某些科_学_上_网&#xff08;翻_墙&#xf…

电脑高手推荐:三款超实用软件,让你的电脑如虎添翼!

7Zip 7-Zip是一款免费且开源的文件压缩工具&#xff0c;支持多种文件格式&#xff0c;包括其自带的7z格式、ZIP、GZIP、BZIP2和TAR等。该软件由Igor Pavlov于1999年开发&#xff0c;具有高压缩比的特点。7-Zip不仅可以在Windows操作系统上使用&#xff0c;还可以在Unix-like的操…

做到这九点,工作就无后顾之忧

大家好&#xff0c;今天又跟大家分享一篇&#xff0c;怎么在职场上做到挺起腰杆做事。全文共分9点&#xff0c;尤其最后一点最为重要。篇幅有点长&#xff0c;全文共计三千多字&#xff0c;请耐心看完。 如果您觉得对您有些帮助&#xff0c;点赞收藏关注。谢谢您的支持。 在职场…

LDO芯片手册,实例应用分析

在进行电路设计时LDO是经常用到的&#xff0c;尤其在为芯片&#xff0c;晶振等敏感电路进行供电时应用更多&#xff0c;下面选取一款比较常用的LDO芯片&#xff0c;一起进行更深入的学习。 SGM2036特点简介 SGM2036&#xff0c;圣邦微一款比较常用的LDO芯片手册 可以先大致看…

广州数据中心机房搬迁验收要求

1.验收要求 新机房装修工程全部竣工&#xff0c;各类环境设备安装到位&#xff0c;包括空调、UPS、柴油发电机等设备安装调试完毕&#xff0c;机房接地、防雷、消防系统检验合格&#xff0c;机房综合布线工作完成&#xff0c;机房各项环境指标达标&#xff0c;机房整体通过验收…