草庐IT

一道Android题目逆向动态调试

蚁景科技 2023-03-28 原文

题目来源于海淀区网络与信息安全管理员大赛,题目中将加密验证算法打包进.so,在程序中动态调用check。

本题目通过System.loadLibrary("native-lib")加载了libnative-lib.so文件,该文件通过jeb可以实现提取

图1 题目关键代码

调试环境选择与配置

  • mumu模拟器 x64位版本,测试后发现sprintf会导致程序崩溃

  • 夜神模拟器x64,x32的版本经过测试后,sprintf均导致程序崩溃

  • 雷电5模拟器测试后,sprintf导致程序崩溃,动态调试libnative-lib.so时,且无法下载libart.so

  • 最终选用 mumu x32位版本可以进行调试

  • 动态调试选用IDA+MUMU x86模拟器对动态库libnative-lib.so调试

调试环境

adb的基础配置

  • mumu模拟器使用的adb为adb_server.exe,这里将adb_server.exe为便于使用重新命名为adb.exe,打开一个cmd终端,adb 接入模拟器中

adb connect 127.0.0.1:7555

 

图2 adb 服务端连接

  • 通过adb 将apk 包安装进安卓的模拟器

adb install test.apk

 

  • 通过cmd再打开一个终端,通过adb shell可以直接进入到模拟器shell中

图3 adb shell连接

 

【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】

 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)

 

应用程序的配置

  • 在新起的cmd终端,通过动态调试模式来启动app

./adb shell am start -D -n com.example.dynamic/.MainActivity
  • android包实际的packet以及类如下图所示com.example.dynamic/.MainActivity

图4 adb 启动程序分析

  • 运行 adb shell am start命令后,mumu模拟器中如图5所示

图5 adb 动态调试程序

IDA 的配置

  • 上传IDA的动态服务端android_x86_server到模拟器/data/local/tmp中,tmp文件夹是具有可执行权限的

./adb push android_x86_server /data/local/tmp

 

图6 查看tmp文件夹权限

  • 赋予android_x86_server可执行权限

chmod +x android_x86_server

 

  • 执行android_x86_server,会监听23946端口,但是仍需要通过adb进行端口转发转发到本地监听

./adb.exe forward tcp:23946 tcp:23946

 

图7 启动IDA 调试server端

  • 通过以上步骤使启动服务端IDA的监听

  • 配置本地IDA remote linux debug参数,如图8所示

图8 配置IDA动态调试

  • 通过attach process 打开远程端的进程

图9 IDA远程attach

  • 选择对应的进程,这里选用1535进程

图10 附加到指定进程

  • 通过以上步骤,将IDA 服务端和.so文件关联到一起,仍需要唤醒被调试的程序,此时mumu模拟器中仍旧如图11所示

图11 dynamic程序界面

  • 通过jdb来唤醒被调试程序,本机调试的时候jdb使用java sdk自带的jdb,需要两步操作

    • 通过adb将进程进行转发,进程号是图n中所示的1535

./adb forward tcp:8700 jdwp:1535

 

  • 通过jdb唤醒操作

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

 

  • 再回到IDA中,选择F9继续运行程序,会弹出框选择本地程序与远程是否一样选项框,主要匹配的是动态库libnative-lib.so这个名字

图12 IDA提示检测到本地.so

  • IDA中断点断在ptrace前,mumu模拟器中界面未完全同步

图13 附加到调试进程后,dynamic界面

  • IDA中界面如下

图14 IDA中显示断点

.so的调试

反调试绕过

  • 该.so使用了ptrace 反调试,在ptrace处设置断点,下断点的时候有两种方案

    1. 一种是设置IDA 的调试调试,设置载入lib的时候suspend

图15 IDA调试选项配置

  • 当看到IDA中载入libnative-lib.so时,通过快捷键Ctrl-S打开加载的段,查找libnative-lib.so所在内存1

图16 查看IDA中的代码段

  • 还可以在模拟器shell中,查看具体的内存信息

图17 adb shell中查看内存中的数据地址分布

  • 在动态调试的过程中,重置ptrace 的返回值,绕过该处反调试

图18 重置eax的值

可以直接右键或者在eax寄存器上使用快捷键0重置

  • 另外一种方式是直接在ptrace上下断点,在调试的时候当IDA弹窗如图17所示时,程序会直接断在ptrace断点处。如果没有弹出该弹窗,直接在IDA中分析该so时下的断点无效。

图19 重置eax的值

注册native的方法

  • 在 Native文件中代码如下

static JNINativeMethod jniMethods[] = {
 {"check", "(Ljava/lang/String;)Z", (void *)hello},
};
boolean xxxx( char* s) {
 // do something
 return JNI_TRUE;
}
#在JNI_OnLoad中调用RegisterNatives方法注册Natives方法到JVM,建立映射关系。
int JNI_OnLoad(JavaVM *vm, void *reserved)
{
    JNIEnv *env;
    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK) {
        return JNI_ERR;
    }
​
    jclass cls = (*env)->FindClass(env, "LHelloJNI");
    if (cls == NULL)
        return JNI_ERR;
​
    int len = sizeof(jniMethods) / sizeof(jnimethods[0]);
    (*env)->RegisterNatives(env, cls, jniMethods, len);
​
    return JNI_VERSION_1_4;
}

check 函数的定位

  • 在apk文件中,反编译后可以看到check函数位于libnative-lib.so中,但是libnative-lib.so中并没有check函数

图20 查找check函数

  • Java调用.so库函数可以通过静态注册和动态注册两种方式,题目通过动态注册的方式来对函数进行调用

  • 在上图中methods一列,是一个JNINativeMethod的数组,JNINativeMethod结构包含三个成员

const char \*name: Java中声明的native方法。
const char \*signature:方法的签名。
void \*fnPtr: 函数指针
  • 在题目的methods中,check字符串,对应的函数指针为Z4xxxxP7_JNIEnvP8_jobjectP8_jstring ; xxxx(JNIEnv *,jobject *,jstring *)也就是xxxx函数。

图21 定位check函数

MD5的简单调试

  • MD5_init的过程如下,根据初始值可以大概判定题目通过md5算hash值

*(_OWORD *)v63 = xmmword_B2E6FA40;
.rodata:B2E6FA40 xmmword_B2E6FA40 xmmword 1032547698BADCFEEFCDAB8967452301h

 

  • 经过fff函数转换后的md5值放入[esp+0B4]中

.text:B2E51040 8D 84 24 B4 00 00 00    lea     eax, [esp+0B4h]
.text:B2E51047 89 44 24 04             mov     [esp+4], eax
.text:B2E5104B 8D 44 24 58             lea     eax, [esp+58h]
.text:B2E5104F 89 04 24                mov     [esp], eax
.text:B2E51052 E8 A9 E7 FF FF          call    __Z4ffffP7MD5_CTXPh ; ffff(MD5_CTX *,uchar *)

 

  • 读取[esp+0xB4]的值

Python>esp=get_reg_value('esp')
Python>data=get_bytes(esp+0xb4,16)
Python>data.hex()
'a82e0cb168bfe134f22dbde167cf046c'

 

  • 通过python计算wojiushidaan0!!!的md5值为

>>> import hashlib
>>> result=hashlib.md5("wojiushidaan0!!!".encode())
>>> result
<md5 _hashlib.HASH object @ 0x00000167FF8BDEF0>
>>> result.hexdigest()
'a82e0cb168bfe134f22dbde167cf046c'

 

  • 两者可以对应起来,题目计算了wojiushidaan0!!!的md5值

  • 程序最终经过memcmp比较的时候的值为

.text:B2F11398 89 54 24 08             mov     [esp+8], edx
.text:B2F1139C 8B 44 24 14             mov     eax, [esp+14h]
.text:B2F113A0 89 44 24 04             mov     [esp+4], eax   ; s2
.text:B2F113A4 89 0C 24                mov     [esp], ecx     ; s1
.text:B2F113A7 E8 84 E4 FF FF          call    _memcmp

 

  • 提取eax的值为

b'c640fc761edbd22f431efb861bc0e28a'

 

  • 提取ecx的值为

b'12345678123456781234567812345678'

 

  • 程序的输入为

图22 调试flag结果

  • 推导可知题目的正确输入为

flag{c640fc761edbd22f431efb861bc0e28a}

图23 验证flag结果

  • 在调试md5的时候,使用了IDA的上色功能,通过单步步过调试,给执行过的代码染色

  • IDAPro 单步步过上色调试脚本

def get_new_color(current_color):
  colors = [0xffe699, 0xffcc33, 0xe6ac00, 0xb38600]
  if current_color == 0xFFFFFF:
    return colors[0]
  if current_color in colors:
    pos = colors.index(current_color)
    if pos == len(colors)-1:
      return colors[pos]
    else:
      return colors[pos+1]
  return 0xFFFFFF
​
addr = ida_dbg.get_ip_val()
while addr < 0xB2ED241F:
  event = wait_for_next_event(WFNE_ANY, -1)
  t = step_over()
  addr = ida_dbg.get_ip_val()
  current_color = get_color(addr, CIC_ITEM)
  new_color = get_new_color(current_color)
  set_color(addr, CIC_ITEM, new_color)

#https://www.cnblogs.com/blacksunny/p/7300271.html参考trace 修改的step over

有待改进的地方

  • 绕过反调试依赖于动态调试时的修改寄存器实现

本文涉及的命令

adb connect 127.0.0.1:7555
adb install test.apk
 ./adb shell am start -D -n com.example.dynamic/.MainActivity
./adb push android_x86_server /data/local/tmp
./adb.exe forward tcp:23946 tcp:23946
./adb forward tcp:8700 jdwp:1535
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

更多靶场实验练习、网安学习资料,请点击这里>>

 
 

有关一道Android题目逆向动态调试的更多相关文章

  1. ruby-on-rails - 无法让 rspec、spork 和调试器正常运行 - 2

    GivenIamadumbprogrammerandIamusingrspecandIamusingsporkandIwanttodebug...mmm...let'ssaaay,aspecforPhone.那么,我应该把“require'ruby-debug'”行放在哪里,以便在phone_spec.rb的特定点停止处理?(我所要求的只是一个大而粗的箭头,即使是一个有挑战性的程序员也能看到:-3)我已经尝试了很多位置,除非我没有正确测试它们,否则会发生一些奇怪的事情:在spec_helper.rb中的以下位置:require'rubygems'require'spork'

  2. ruby - JetBrains RubyMine 3.2.4 调试器不工作 - 2

    使用Ruby1.9.2运行IDE提示说需要gemruby​​-debug-base19x并提供安装它。但是,在尝试安装它时会显示消息Failedtoinstallgems.Followinggemswerenotinstalled:C:/ProgramFiles(x86)/JetBrains/RubyMine3.2.4/rb/gems/ruby-debug-base19x-0.11.30.pre2.gem:Errorinstallingruby-debug-base19x-0.11.30.pre2.gem:The'linecache19'nativegemrequiresinstall

  3. ruby-on-rails - 如何调试 cucumber 测试? - 2

    我有:When/^(?:|I)follow"([^"]*)"(?:within"([^"]*)")?$/do|link,selector|with_scope(selector)doclick_link(link)endend我打电话的地方:Background:GivenIamanexistingadminuserWhenIfollow"CLIENTS"我的HTML是这样的:CLIENTS我一直收到这个错误:.F-.F--U-----U(::)failedsteps(::)nolinkwithtitle,idortext'CLIENTS'found(Capybara::Element

  4. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  5. ruby - Ruby 是否有类似于 Perl 的 "perl -d"的逐步调试器? - 2

    Ruby是否有逐步调试器,类似于Perl的“perl-d”? 最佳答案 ruby-debug(对于ruby1.8),debugger(对于ruby1.9),byebug(对于ruby​​2.0)以及trepanning系列都有一个-x或--trace选项。在调试器内部,命令setlinetrace将打开或关闭线路跟踪。这是themanualforruby-debug原来的答案已经修改,因为数据噪声文章的链接,唉,不再有效了。还添加了ruby​​-debug的后继者 关于ruby-Ruby

  6. ruby - 在 Ruby 中动态创建数组 - 2

    有没有办法在Ruby中动态创建数组?例如,假设我想遍历用户输入的书籍数组:books=gets.chomp用户输入:"TheGreatGatsby,CrimeandPunishment,Dracula,Fahrenheit451,PrideandPrejudice,SenseandSensibility,Slaughterhouse-Five,TheAdventuresofHuckleberryFinn"我把它变成一个数组:books_array=books.split(",")现在,对于用户输入的每一本书,我想用Ruby创建一个数组。伪代码来做到这一点:x=0books_array.

  7. ruby - 是否可以将 IRB 提示配置为动态更改? - 2

    我想在IRB中浏览文件系统并让提示更改以反射(reflect)当前工作目录,但我不知道如何在每个命令后进行提示更新。最终,我想在日常工作中更多地使用IRB,让bash溜走。我在我的.irbrc中试过这个:require'fileutils'includeFileUtilsIRB.conf[:PROMPT][:CUSTOM]={:PROMPT_N=>"\e[1m:\e[m",:PROMPT_I=>"\e[1m#{pwd}>\e[m",:PROMPT_S=>"FOO",:PROMPT_C=>"\e[1m#{pwd}>\e[m",:RETURN=>""}IRB.conf[:PROMPT_MO

  8. ruby-on-rails - carrierwave:在序列化动态属性上安装 uploader - 2

    首先,我使用的是rails3.1.3和来自master的carrierwavegithub仓库的分支。我使用after_init钩子(Hook)来确定基于属性的字段页面模型实例并为这些字段定义属性访问器将值存储在序列化哈希中(希望它清楚我是什么谈论)。这是我正在做的事情的精简版:classPage省略mount_uploader命令让我可以访问我想要的属性。但是当我安装uploader时出现错误消息说“nil类的未定义新方法”我在源代码中读到有方法read_uploader和扩展模块中的write_uploader。我如何必须覆盖这些来制作mount_uploader命令使用我的“虚拟

  9. ruby - 在 Ruby 中动态生成多维数组 - 2

    我正在尝试动态构建一个多维数组。我想要的基本上是这样的(为简单起见写出来):b=0test=[[]]test[b]这给了我错误:NoMethodError:undefinedmethod`test=[[],[],[]]而且它工作正常,但在我的实际使用中,我不会事先知道需要多少个数组。有一个更好的方法吗?谢谢 最佳答案 不需要像您正在使用的索引变量。只需将每个数组附加到您的test数组:irb>test=[]=>[]irb>test[["a","b","c"]]irb>test[["a","b","c"],["d","e","f"]]

  10. ruby-on-rails - 使用 gmaps4rails 动态加载谷歌地图标记 - 2

    如何只加载map边界内的标记gmaps4rails?当然,在平移和/或缩放后加载新的。与此直接相关的是,如何获取map的当前边界和缩放级别? 最佳答案 我是这样做的,我只在用户完成平移或缩放后替换标记,如果您需要不同的行为,请使用不同的事件监听器:在你看来(index.html.erb):{"zoom"=>15,"auto_adjust"=>false,"detect_location"=>true,"center_on_user"=>true}},false,true)%>在View的底部添加:functiongmaps4rail

随机推荐