使用Frida对app进行hook分析的基本方法介绍 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

使用Frida对app进行hook分析的基本方法介绍

h1apwn 技术 2020-01-19 10:20:00
1521399
收藏

导语:Frida是现在Hook实践中最友好的工具。 frida-trace可以实时检测hook函数的所有输入输出和动作,使用Frida可以迅速开发复杂的Hook逻辑,Frida可以分析程序行为,也可以通过Hook改变程序行为。

0x01  概述

我将对使用Frida进行程序检测和在Windows上hook进行基本介绍。在整个帖子中,我将使用frida-trace,因为它提供了方便的实时流,我可以实时检查对函数hook所做的更改。一旦掌握了JavaScript语法,就可以将该知识扩展到各种Frida绑定(Python / C / Node / Swift / .Net / QML)上。

为什么选择Frida?它提供了一个简单的界面,可以在其中快速开发复杂的hook逻辑,并随着需求对其进行更改,与重新部署C ++函数hook的复杂过程比较就方便多了。将Frida用于什么用途?正如我的标题所述的那样,是检查(查看应用程序内部以分析其行为)和hook(更改应用程序的行为)。从安全角度看,Frida是一种研究工具,不适合用于武器部署。话虽如此,Frida可以用于制作攻击性hook的原型,以后可以在其他框架如EasyHook中实施这些hook以进行部署。

https://easyhook.github.io/

0x02 注册表检查

在本节中,我将研究在任意Windows应用程序中被动监视注册表活动。首先,我们将看看RegOpenKeyExW,它最常用于打开注册表项的句柄。下面可以看到C ++函数原型。

 LONG WINAPI RegOpenKeyEx(
   _In_     HKEY    hKey,       // Handle to the open registry key (commonly the registry hive).
   _In_opt_ LPCTSTR lpSubKey,   // The name of the registry subkey to be opened.
   _In_     DWORD   ulOptions,  // REG_OPTION_OPEN_LINK/NULL.
   _In_     REGSAM  samDesired, // A mask that specifies the desired access rights to the key to be opened.
   _Out_    PHKEY   phkResult   // A pointer to a variable that receives a handle to the opened key.
 );

大多数API都有ANSI和Unicode版本。出于我的目的,我应该假定我hook的Windows应用程序将使用Unicode版本,将Frida附加到流程并定义/打印所有这些参数。

Frida-Trace-01.png

Frida使此过程极其容易。当使用trace时,Frida在当前目录中创建一个“ handlers”文件夹,在其中使用onEnter / onLeave原型填充JS文件,以用于用户指定的任何功能。取出函数参数就像在数组中打印参数一样容易。上图所示的JS处理程序如下所示。

 /*
  * Auto-generated by Frida. Please modify to match the signature of RegOpenKeyExW.
  * This stub is currently auto-generated from manpages when available.
  *
  * For full API reference, see: http://www.frida.re/docs/javascript-api/
  */
  
 {
   /**
    * Called synchronously when about to call RegOpenKeyExW.
    *
    * @this {object} - Object allowing you to store state for use in onLeave.
    * @param {function} log - Call this function with a string to be presented to the user.
    * @param {array} args - Function arguments represented as an array of NativePointer objects.
    * For example use Memory.readUtf8String(args[0]) if the first argument is a pointer to a C string encoded as UTF-8.
    * It is also possible to modify arguments by assigning a NativePointer object to an element of this array.
    * @param {object} state - Object allowing you to keep state across function calls.
    * Only one JavaScript function will execute at a time, so do not worry about race-conditions.
    * However, do not use this to store function arguments across onEnter/onLeave, but instead
    * use "this" which is an object for keeping state local to an invocation.
    */
   onEnter: function (log, args, state) {
     log("[+] RegOpenKeyExW");
     log("¦- hKey: " + args[0]);
     log("¦- lpSubKey: " + args[1]);
     log("¦- ulOptions: " + args[2]);
     log("¦- samDesired: " + args[3]);
     log("¦- PHKEY: " + args[4] + "\n");
   },
  
   /**
    * Called synchronously when about to return from RegOpenKeyExW.
    *
    * See onEnter for details.
    *
    * @this {object} - Object allowing you to access state stored in onEnter.
    * @param {function} log - Call this function with a string to be presented to the user.
    * @param {NativePointer} retval - Return value represented as a NativePointer object.
    * @param {object} state - Object allowing you to keep state across function calls.
    */
   onLeave: function (log, retval, state) {
   }
 }

能够提取所有这些函数参数,但是出于快速查看注册表活动的目的,最有用的参数可能是lpSubKey。当然,字符串指针并不是特别有用,但是我们可以轻松地重写onEnter函数来查看unicode字符串,如下所示。

 onEnter: function (log, args, state) {
     log(Memory.readUtf16String(args[1]));
 }

如果我保存更改并在应用程序中执行一些新活动,将看到该应用程序正在访问完整的子项注册表路径。

Frida-Trace-02.png

如果仔细查看输出,将会发现缺少某些内容。实际上并没有获得注册表配置单元,这是因为该调用使用的是先前打开的要查询的配置单元的句柄。通过在使用过的句柄上进行查找并在调用之间跟踪它们,可以知道配置单元是什么,但这不在本文讨论范围之内。

如果通过COM Hijacking使用某些恶意代码来进行特权提升和持久驻留,其中列出了对包含“ CLSID \”的路径的注册表访问,其中调用的结果是失败的(很可能因为正在HKEY_CURRENT_USER中查询子项)。可以如下快速修改POC来捕获这些调用。

onEnter: function (log, args, state) {
     this.SubKey = Memory.readUtf16String(args[1]); // @this is available in onLeave
     if (this.SubKey) {                             // Make sure the value is not null
         if (this.SubKey.indexOf("CLSID\\") >= 0) {
             this.ContainsCLSID = 1;                // Bool -> contains substring
         }
     }
 },
  
 onLeave: function (log, retval, state) {
     if (this.ContainsCLSID) {                      // Check Bool
         if (retval != 0){                          // If return value is not ERROR_SUCCESS
             log(this.SubKey);                      // Print subkey
         }
     }
 }

保存的POC只会返回尝试打开CLSID子项,结果不是ERROR_SUCCESS。

Frida-Trace-03.png

同样,可以跟踪哪些查询成功。如果想知道成功打开子键句柄后正在访问哪些键值,该怎么办?通常是通过使用RegQueryValueEx。如果现在hook这两个函数,可以实现一些简单的逻辑,在其中存储成功调用RegOpenKeyEx返回的句柄,并在调用RegQueryValueEx时将输入句柄与保存的句柄进行比较,如果它们匹配,则可以打印出被查询。可以在下面看到实现此目的的代码。

 // The contents of the RegOpenKeyExW.js
 //---------------------------------------------------------
  
 onEnter: function (log, args, state) {
     this.SubKey = Memory.readUtf16String(args[1]); // @this is available in onLeave
     if (this.SubKey) {                             // Make sure the value is not null
         if (this.SubKey.indexOf("CLSID\\") >= 0) {
             this.ContainsCLSID = 1;                // Bool -> contains substring
             this.hSubKey = args[4];
         }
     }
 },
  
 onLeave: function (log, retval, state) {
     if (this.ContainsCLSID) {                      // Check Bool
         if (retval == 0){                          // If return value is ERROR_SUCCESS
             state.HandleKey = new Array(Memory.readInt(this.hSubKey), this.SubKey);
         }                                          // @state persists across API calls
                                                    // We create an array with the handle & path
     }
 }
  
 // The contents of the RegQueryValueExW.js
 //---------------------------------------------------------
 onEnter: function (log, args, state) {
     if (state.HandleKey) {                         // Check our array exists
         if (state.HandleKey[0] == args[0]) {       // Compare stored handle with the new handle
             if (Memory.readUtf16String(args[1])) { // Make sure the value is not null
                 log("[+] hKey: " + state.HandleKey[0] + "; Path: " + state.HandleKey[1]);
                 log("¦- KeyValue: " + Memory.readUtf16String(args[1]) + "\n");
                 state.HandleKey = null;            // We null here to clear the array
             }
         }
     }
 },
  
 onLeave: function (log, retval, state) {
 }

现在,刷新POC仅返回过滤的条目。

Frida-Trace-04.png

这是一个简单的示例,但是可以看到Frida允许轻松地对函数进行测试并使用它们,而无需进行复杂的的Compile-> Test-> Compile循环。

0x03  Hook MessageBox

到目前为止,已经看到了如何进行被动检测,在本节中,将了解如何影响应用程序的行为。作为一个基本示例,我选择使用MessageBox,因为它类似于Windows API的“ Hello World”。为了使我能够动态地测试函数hook,我用C#编写了一个小的Windows MessageBox测试工具,可以在下面看到主要功能。

 using System;
 using System.Runtime.InteropServices;
 using System.Windows.Forms;
  
 namespace msgbox
 {
     public partial class Form1 : Form
     {
         // Unmanaged MessageBoxA import
         [DllImport("user32.dll")]
         public static extern int MessageBox(
             IntPtr hWnd,
             String lpText,
             String lpCaption,
             UInt32 uType);
  
         public Form1()
         {
             InitializeComponent();
         }
  
         private void button1_Click(object sender, EventArgs e)
         {
             // Grab our textbox inputs
             String lpText = textBox1.Text;
             String lpCaption = textBox2.Text;
             UInt32 uType = Convert.ToUInt32(textBox3.Text);
  
             MessageBox(IntPtr.Zero, lpText,lpCaption,uType);
         }
     }
 }

使用在上一节中看到的相同技术,可以快速hook住应用程序并编写一些基本JS来转储MessageBox参数。注意使用readAnsiString来匹配MessageBoxA。

onEnter: function (log, args, state) {
     log("[+] MessageBoxA");
     log("¦- hWnd: " + args[0]);
     log("¦- lpText: " + Memory.readAnsiString(args[1]));
     log("¦- lpCaption: " + Memory.readAnsiString(args[2]));
     log("¦- uType: " + args[3] + "\n");
 },
  
 onLeave: function (log, retval, state) {
 }

Frida-Trace-05.png

Frida具有许多用于编辑/分配内存原语的功能,建议读者阅读API文档。在我的简单演示中,我修改了JS以实现两种类型的hook:(1)如果lpText是“ Bob”,则将其更改为“ Alice”;(2)如果uType是6,则将其更改为0。

 onEnter: function (log, args, state) {
     log("");
     log("[+] MessageBoxA");
     log("¦- hWnd: " + args[0]);
     log("¦- lpText: " + Memory.readAnsiString(args[1]));
     log("¦- lpCaption: " + Memory.readAnsiString(args[2]));
     log("¦- uType: " + args[3] + "\n");
      
     // uType hook
     if (args[3] == 6) {
         log("[!] Hooking uType: 6 -> 0");
         args[3] = ptr(0); // Overwrite uType with NativePointer(0)
     }
      
     // lpText hook
     if (Memory.readAnsiString(args[1]) == "Bob") {
         log("[!] Hooking lpText: Bob -> Alice");
         this.lpText = Memory.allocAnsiString("Alice"); // Allocate new heap ANSI string
         args[1] = this.lpText; // Replace lpText pointer
     }
 },
  
 onLeave: function (log, retval, state) {
 }

可以在下图中观察结果。请注意,这些参数是单独检查的,因此可能会遇到两个hook都没有/都处于活动状态的情况。

Frida-Trace-06.png

0x04  进程隐藏-> SystemProcessInformation

本文的最后一部分,我想简要地展示一个更复杂的hook示例。如果曾经在Windows上使用未公开的API的机会很大,那么已经使用过NtQuerySystemInformation及其一些信息类。这些类之一是SystemProcessInformation类(0x5)。事实证明,SystemProcessInformation是用户级进程,因此,无论通过哪个API获取进程列表的任何应用程序,最终都会过滤到NtQuerySystemInformation(任务管理器/进程浏览器/ Process Hacker / ..... )。

我最近为该函数Get-SystemProcessInformation编写了PowerShell包装器,所以我认为尝试将此函数与Frida Hook以演示用户界面进程隐藏是一个好主意。

https://github.com/FuzzySecurity/PowerShell-Suite/blob/master/Get-SystemProcessInformation.ps1

SystemProcessInformation内存布局

要了解hook的工作原理,我们需要知道使用SystemProcessInformation类时NtQuerySystemInformation实际返回的内容。希望下面的布局有助于帮助理解。  

 NTSTATUS WINAPI NtQuerySystemInformation(
   _In_      UINT   SystemInformationClass,  // SYSTEM_INFORMATION_CLASS
   _Inout_   PVOID  SystemInformation,       // A pointer to a buffer that receives the requested information
   _In_      ULONG  SystemInformationLength, // Byte count allocated for the request
   _Out_opt_ PULONG ReturnLength             // Pointer to the variable to receives the output size
 );
 
 SystemInformationClass => SystemProcessInformation = 0x5
 SystemInformation => Pointer, eg 0x11223344556 -----------------------|
                                                                       |
                                                                       |
                                                                       |
                                            [Points at an array of SYSTEM_PROCESS_INFORMATION Structs]
                                                                       |
                                                                       |
                     |-------------------------------------------------|
                     |
  |--------------------------------------|
  | [Int]NextEntryOffset (eg:0x1fb)      |  -------------------->
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  |              .......                 |                      |
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  | [UNICODE_STRING]ImageName            |                      |      [2nd Entry = 1st Entry + 0x1fb]
  |   |-> svchost.exe                    |                      |
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  |              .......                 |                      |
  |                                      |                      |
  |                                      |                      |
  | SYSTEM_THREAD_INFORMATION structs    |                      |
  |--------------------------------------|                      |
  | [Int]NextEntryOffset (eg:0x222)      |  
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  |              .......                 |                      |
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  | [UNICODE_STRING]ImageName            |                      |      [3rd Entry = 2nd Entry + 0x222]
  |   |-> powershell.exe                 |                      |
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  |              .......                 |                      |
  |                                      |                      |
  |                                      |                      |
  | SYSTEM_THREAD_INFORMATION structs    |                      |
  |--------------------------------------|                      |
  | [Int]NextEntryOffset (eg:0x3a0)      |  
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  |              .......                 |                      |
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  | [UNICODE_STRING]ImageName            |                      |      [4th Entry = 3rd Entry + 0x3a0]
  |   |-> notepad.exe                    |                      |
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  |              .......                 |                      |
  |                                      |                      |
  |                                      |                      |
  | SYSTEM_THREAD_INFORMATION structs    |                      |
  |--------------------------------------|                      |
                                            .....

这种表示方式并不完全准确,因为在数组的开头有一些固定的process。关键是每个blob的大小会有所不同,具体取决于将多少SYSTEM_THREAD_INFORMATION结构附加到SYSTEM_PROCESS_INFORMATION结构。

SystemProcessInformation Hooking 假设要隐藏该列表中的所有PowerShell进程,我要做的就是遍历该列表并在PowerShell之前重写条目,以便NextEntryOffset指向列表中的下一个条目。  

  |--------------------------------------|
  | [Int]NextEntryOffset (eg:0x1fb 0x41d)|  -------------------->
  |                               I      |                      |
  |                               I      |                      |
  |                               I      |                      |
  |              .......          I==================================> [3nd Entry = 1st Entry + 0x1fb + 0x222
  |                                      |                      |       => 1st Entry + 0x41d]
  |                                      |                      |
  |                                      |                      |
  | [UNICODE_STRING]ImageName            |                      |
  |   |-> svchost.exe                    |                      |
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  |              .......                 |                      |
  |                                      |                      |
  |                                      |                      |
  | SYSTEM_THREAD_INFORMATION structs    |                      |
  |--------------------------------------|                      |
  | [Int]NextEntryOffset (eg:0x222)      |                      |
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  |              .......                 |                      |
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  | [UNICODE_STRING]ImageName            |                      |
  |   |-> powershell.exe                 |                      |
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  |              .......                 |                      |
  |                                      |                      |
  |                                      |                      |
  | SYSTEM_THREAD_INFORMATION structs    |                      |
  |--------------------------------------|                      |
  | [Int]NextEntryOffset (eg:0x3a0)      |  
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  |              .......                 |                      |
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  | [UNICODE_STRING]ImageName            |                      |      [4th Entry = 3rd Entry + 0x3a0]
  |   |-> notepad.exe                    |                      |
  |                                      |                      |
  |                                      |                      |
  |                                      |                      |
  |              .......                 |                      |
  |                                      |                      |
  |                                      |                      |
  | SYSTEM_THREAD_INFORMATION structs    |                      |
  |--------------------------------------|                      |
                                            .....

这似乎有些复杂,但是我要做的就是截获即将返回的API调用,通过读取偏移量/ unicode字符串遍历列表,并为每个已确定的PowerShell进程覆盖一个整数。可以在下面查看我的Frida 实践。请注意,这些偏移量仅在x64 Win10上进行了测试(尽管它们对于Win7-10 x64应该有效)。

 onEnter: function (log, args, state) {
     if (args[0] == 5) {
         log("NtQuerySystemInformation:");
         log("  --> Class : " + args[0] + " [SystemProcessInformation]");
         log("  --> Addr  : " + args[1]);
         log("  --> len   : " + args[2]);
         log("  --> Retlen: " + Memory.readInt(args[3]) + "\n");
          
         this.IsProcInfo = 1;
         this.Address = args[1];
     }
 },
  
 onLeave: function (log, retval, state) {
     if (this.IsProcInfo) {
         while (true) {
             // Get struct offsets
             var ImageOffset = ptr(this.Address).add(64); // ImageName->UNICODE_STRING->Buffer
             var ImageName = Memory.readPointer(ImageOffset); // Cast as ptr
             var ProcID = ptr(this.Address).add(80); // PID
              
             // If PowerShell, rewrite the linked list
             if (Memory.readUtf16String(ImageName) == "powershell.exe") {
                 log("[!] Hooking to hide PowerShell..");
                 log("  --> Rewriting linked list\n");
                 this.PreviousStruct = ptr(this.Address).sub(NextEntryOffset);
                 Memory.writeInt(this.PreviousStruct, (Memory.readInt(this.PreviousStruct)+Memory.readInt(this.Address)))
             }
      
             // Move pointer to next struct
             var NextEntryOffset = Memory.readInt(this.Address);
             this.Address = ptr(this.Address).add(NextEntryOffset);
             if (NextEntryOffset == 0) { // The last struct has a NextEntryOffset of 0
                 break
             }
         }
          
         // Null
         this.IsProcInfo = 0;
     }
 }

0x05  实战演示

为该函数Get-SystemProcessInformation编写了PowerShell包装器,尝试将此函数与Frida Hook以演示用户界面进程隐藏。spacer.gif

可以看到现在开启了三个powershell进程。spacer.gif

执行下面的命令,截获即将返回的API调用,通过读取偏移量/ unicode字符串遍历列表,并为每个已确定的PowerShell进程覆盖一个整数。spacer.gif

执行前powershell进程都是存在的。spacer.gif

执行后在任务管理器中隐藏了所有PowerShell进程。spacer.gif

0x06  学习总结

Frida是现在Hook实践中最友好的工具。

frida-trace可以实时检测hook函数的所有输入输出和动作,使用Frida可以迅速开发复杂的Hook逻辑,Frida可以分析程序行为,也可以通过Hook改变程序行为。

 Frida-trace -p 7276 -i RegOpenKeyExW -x KERNELBASE.DLL -x KERNEL32.DLL

分析注册表:RegOpenKeyExW用于打开注册表,Frida会捕获到这个API的所有参数,使用trace会创建一个JS文件,可以对JS文件做修改,就会输出不同内容:

   onEnter: function (log, args, state) {
     log("[+] RegOpenKeyExW");
     log("¦- hKey: " + args[0]);
     log("¦- lpSubKey: " + args[1]);
     log("¦- ulOptions: " + args[2]);
     log("¦- samDesired: " + args[3]);
     log("¦- PHKEY: " + args[4] + "\n");
   },
   
   
   onEnter: function (log, args, state) {
     log(Memory.readUtf16String(args[1]));
 }

保存后重新执行Frida-trace,就可以看到程序正在访问完整的子项注册表路径

上面是被动监视程序行为,也可以hook改变程序行为,使用相同技术,可以快速hook住应用程序并编写一些基本JS来转储MessageBox参数。

Frida具有许多用于编辑/分配内存原语的功能,修改了JS以实现两种类型的hook:(1)如果lpText是“ Bob”,则将其更改为“ Alice”;(2)如果uType是6,则将其更改为0。

尝试将Get-SystemProcessInformation函数与Frida Hook来演示用户界面进程隐藏,为该函数Get-SystemProcessInformation编写了PowerShell包装器,尝试将此函数与Frida Hook以演示用户界面进程隐藏。

截获即将返回的API调用,通过读取偏移量/ unicode字符串遍历列表,并为每个已确定的PowerShell进程覆盖一个整数。

本文翻译自:https://www.fuzzysecurity.com/tutorials/29.html如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务