Frida官方手冊 - JavaScript API(篇二)

freakish發表於2017-10-24

JavaScript API

Int64

  • new Int64(v): 以v為引數,建立一個Int64物件,v可以是一個數值,也可以是一個字串形式的數值表示,也可以使用 Int64(v) 這種簡單的方式。
  • add(rhs), sub(rhs), and(rhs), or(rhs), xor(rhs): Int64相關的加減乘除。
  • shr(n), shl(n): Int64相關的左移、右移操作
  • compare(rhs): Int64的比較操作,有點類似 String.localCompare()
  • toNumber(): 把Int64轉換成一個實數
  • toString([radix = 10]): 按照一定的數值進位制把Int64轉成字串,預設是十進位制

UInt64

  • 可以直接參考Int64

NativePointer

  • 可以直接參考Int64

NativeFunction

  • new NativeFunction(address, returnType, argTypes[, abi]):address(使用NativePointer的格式)地址上建立一個NativeFunction物件來進行函式呼叫,returnType 指定函式返回型別,argTypes 指定函式的引數型別,如果不是系統預設型別,也可以選擇性的指定 abi 引數,對於可變型別的函式,在固定引數之後使用 “…” 來表示。
  • 類和結構體

    • 在函式呼叫的過程中,類和結構體是按值傳遞的,傳遞的方式是使用一個陣列來分別指定類和結構體的各個欄位,理論上為了和需要的陣列對應起來,這個陣列是可以支援無限巢狀的,結構體和類構造完成之後,使用NativePointer的形式返回的,因此也可以傳遞給Interceptor.attach() 呼叫。
    • 需要注意的點是, 傳遞的陣列一定要和需要的引數結構體嚴格吻合,比如一個函式的引數是一個3個整形的結構體,那引數傳遞的時候一定要是 [‘int’, ‘int’, ‘int’],對於一個擁有虛擬函式的類來說,呼叫的時候,第一個引數一定是虛表指標。
  • Supported Types

    • void
    • pointer
    • int
    • uint
    • long
    • ulong
    • char
    • uchar
    • float
    • double
    • int8
    • uint8
    • int16
    • uint16
    • int32
    • uint32
    • int64
    • uint64
  • Supported ABIs

    • default
    • Windows 32-bit:
      • sysv
      • stdcall
      • thiscall
      • fastcall
      • mscdecl
    • Windows 64-bit:
      • win64
    • UNIX x86:
      • sysv
      • unix64
    • UNIX ARM:
      • sysv
      • vfp

NativeCallback

  • new NativeCallback(func, returnType, argTypes[, abi]): 使用JavaScript函式 func 來建立一個Native函式,其中returnTypeargTypes分別指定函式的返回型別和引數型別陣列。如果不想使用系統預設的 abi 型別,則可以指定 abi 這個引數。關於argTypes和abi型別,可以檢視NativeFunction來了解詳細資訊,這個物件的返回型別也是NativePointer型別,因此可以作為 Interceptor.replace 的引數使用。

SystemFunction

  • new SystemFunction(address, returnType, argTypes[, abi]): 功能基本和NativeFunction一致,但是使用這個物件可以獲取到呼叫執行緒的last error狀態,返回值是對平臺相關的數值的一層封裝,為value物件,比如是對這兩個值的封裝, errno(UNIX) 或者 lastError(Windows)。

Socket

SocketListener

SocketConnection

IOStream

InputStream

OutputStream

UnixInputStream

UnixOutputStream

Win32InputStream

Win32OutputStream

File

SqliteDatabase

SqliteStatement

Interceptor

  • Interceptor.attach(target, callbacks): 在target指定的位置進行函式呼叫攔截,target是一個NativePointer引數,用來指定你想要攔截的函式的地址,有一點需要注意,在32位ARM機型上,ARM函式地址末位一定是0(2位元組對齊),Thumb函式地址末位一定1(單位元組對齊),如果使用的函式地址是用Frida API獲取的話, 那麼API內部會自動處理這個細節(比如:Module.findExportByName())。其中callbacks引數是一個物件,大致結構如下:
    • onEnter: function(args): 被攔截函式呼叫之前回撥,其中原始函式的引數使用args陣列(NativePointer物件陣列)來表示,可以在這裡修改函式的呼叫引數。
    • onLeave: function(retval): 被攔截函式呼叫之後回撥,其中retval表示原始函式的返回值,retval是從NativePointer繼承來的,是對原始返回值的一個封裝,你可以使用retval.replace(1337)呼叫來修改返回值的內容。需要注意的一點是,retval物件只在 onLeave函式作用域範圍內有效,因此如果你要儲存這個物件以備後續使用的話,一定要使用深拷貝來儲存物件,比如:ptr(retval.toString())。
  • 事實上Frida可以在程式碼的任意位置進行攔截,但是這樣一來 callbacks 回撥的時候,因為回撥位置有可能不在函式的開頭,這樣onEnter這樣的回撥引數Frida只能儘量的保證(比如攔截的位置前面的程式碼沒有修改過傳入的引數),不能像在函式頭那樣可以確保正確。
  • 攔截器的attach呼叫返回一個監聽物件,後續取消攔截的時候,可以作為 Interceptor.detach() 的引數使用。
  • 還有一個比較方便的地方,那就是在回撥函式裡面,包含了一個隱藏的 this 的執行緒tls物件,方便在回撥函式中儲存變數,比如可以在 onEnter 中儲存值,然後在 onLeave 中使用,看一個例子:
  • 另外,this 物件還包含了一些額外的比較有用的屬性:
    • returnAddress: 返回NativePointer型別的 address 物件
    • context: 包含 pc,sp,以及相關暫存器比如 eax, ebx等,可以在回撥函式中直接修改
    • errno: (UNIX)當前執行緒的錯誤值
    • lastError: (Windows) 當前執行緒的錯誤值
    • threadId: 作業系統執行緒Id
    • depth: 函式呼叫層次深度
    • 看個例子:
  • Interceptor.detachAll(): 取消之前所有的攔截呼叫
  • Interceptor.replace(target, replacement): 函式實現程式碼替換,這種情況主要是你想要完全替換掉一個原有函式的實現的時候來使用,注意replacement引數使用JavaScript形式的一個NativeCallback來實現,後續如果想要取消這個替換效果,可以使用 Interceptor.revert呼叫來實現,如果你還想在你自己的替換函式裡面繼續呼叫原始的函式,可以使用以 target 為引數的NativeFunction物件來呼叫,來看一個例子:
  • Interceptor.revert(target): 還原函式的原始實現邏輯,即取消前面的 Interceptor.replace呼叫
  • Interceptor.flush(): 確保之前的記憶體修改操作都執行完畢,並切已經在記憶體中發生作用,只要少數幾種情況需要這個呼叫,比如你剛執行了 attach() 或者 replace() 呼叫,然後接著想要使用NativeFunction物件對函式進行呼叫,這種情況就需要呼叫flush。正常情況下,快取的呼叫操作會在當前執行緒即將離開JavaScript執行時環境或者呼叫send()的時候自動進行flush操作,也包括那些底層會呼叫 send() 操作的函式,比如 RPC 函式,或者任何的 console API

Stalker

  • Stalker.follow([threadId, options]): 開始監視執行緒ID為 threadId(如果是本執行緒,可以省略)的執行緒事件,舉個例子:
  • Stalker.unfollow([threadId]): 停止監控執行緒事件,如果是當前執行緒,則可以省略 threadId 引數
  • Stalker.parse(events[, options]): 按照指定格式介些 GumEvent二進位制資料塊,按照options的要求格式化輸出,舉個例子:
  • Stalker.garbageCollect(): 在呼叫Stalker.unfollow()之後,在一個合適的時候,釋放對應的記憶體,可以避免多執行緒競態條件下的記憶體釋放問題。
  • Stalker.addCallProbe(address, callback): 當address地址處的函式被呼叫的時候,呼叫callback物件(物件型別和Interceptor.attach.onEnter一致),返回一個Id,可以給後面的Stalker.removeCallProbe使用
  • Stalker.removeCallProbe(): 移除前面的 addCallProbe 呼叫效果。
  • Stalker.trustThreshold: 指定一個整型x,表示可以確保一段程式碼在執行x次之後,程式碼才可以認為是可靠的穩定的, -1表示不信任,0表示持續信任,N表示執行N次之後才是可靠的,穩定的,預設值是1。
  • Stalker.queueCapacity: 指定事件佇列的長度,預設長度是16384
  • Stalker.queueDrainInterval: 事件佇列查詢派發時間間隔,預設是250ms,也就是說1秒鐘事件佇列會輪詢4次

ApiResolver

  • new ApiResolver(type): 建立指定型別type的API查詢器,可以根據函式名稱快速定位到函式地址,根據當前程式環境不同,可用的ApiResolver型別也不同,到目前為止,可用的型別有:
    • Module: 列舉當前程式中已經載入的動態連結庫的匯入匯出函式名稱。
    • objc: 定位已經載入進來的Object-C類方法,在macOS和iOS程式中可用,可以使用 Objc.available 來進行執行時判斷,或者在 try-catch 塊中使用 new ApiResolver(‘objc’) 來嘗試建立。
    • 解析器在建立的時候,會載入最小的資料,後續使用懶載入的方式來持續載入剩餘的資料,因此最好是一次相關的批量呼叫使用同一個resolver物件,然後下次的相關操作,重新建立一個resolver物件,避免使用上個resolver的老資料。
  • enumerateMatches(query, callbacks): 執行函式查詢過程,按照引數query來查詢,查詢結果呼叫callbacks來回撥通知:
    • onMatch: function(match): 每次列舉到一個函式,呼叫一次,回撥引數match包含nameaddress兩個屬性。
    • onComplete: function(): 整個列舉過程完成之後呼叫。
    • 舉個例子:
  • enumerateMatchesSync(query): enumerateMatches()的同步版本,直接返回所有結果的陣列形式

DebugSymbol

  • DebugSymbol.fromAddress(address), DebugSymbol.fromName(name): 在指定地址或者指定名稱查詢符號資訊,返回的符號資訊物件包含下面的屬性:
    • address: 當前符號的地址,NativePointer
    • name: 當前符號的名稱,字串形式
    • moduleName: 符號所在的模組名稱
    • fileName: 符號所在的檔名
    • lineNumber: 符號所在的檔案內的行號
    • 為了方便使用,也可以在這個物件上直接使用 toString() ,輸出資訊的時候比較有用,比如和 Thread.backtrace 配合使用,舉個例子來看:
  • DebugSymbol.getFunctionByName(name), DebugSymbol.findFunctionsNamed(name), DebugSymbol.findFunctionsMatching(glob): 這三個函式,都是根據符號資訊來查詢函式,結果返回 NativePointer 物件。

Instruction

  • Instruction.parse(target):target 指定的地址處解析指令,其中target是一個NativePointer。注意,在32位ARM上,ARM函式地址需要是2位元組對齊的,Thumb函式地址是1位元組對齊的,如果你是使用Frida本身的函式來獲取的target地址,Frida會自動處理掉這個細節,parse函式返回的物件包含如下屬性:
    • address: 當前指令的EIP,NativePointer型別
    • next: 下條指令的地址,可以繼續使用parse函式
    • size: 當前指令大小
    • mnemonic: 指令助記符
    • opStr: 字串格式顯示運算元
    • operands: 運算元陣列,每個運算元物件包含typevalue兩個屬性,根據平臺不同,有可能還包含一些額外屬性
    • regsRead: 這條指令顯式進行讀取的暫存器陣列
    • regsWritten: 這條指令顯式的寫入的暫存器陣列
    • groups: 該條指令所屬的指令分組
    • toString(): 把指令格式化成一條人比較容易讀懂的字串形式
    • 關於operandsgroups的細節,請參考CapStone文件

ObjC

Java

  • Java.available: 布林型取值,表示當前程式中是否存在完整可用的Java虛擬機器環境,Dalvik或者Art,建議在使用Java方法之前,使用這個變數來確保環境正常。
  • Java.enumerateLoadedClasses(callbacks): 列舉當前程式中已經載入的類,每次列舉到載入的類回撥callbacks:
    • onMatch: function(className): 列舉到一個類,以類名稱進行回撥,這個類名稱後續可以作為 Java.use() 的引數來獲取該類的一個引用物件。
    • onComplete: function(): 所有的類列舉完畢之後呼叫
  • Java.enumerateLoadedClassesSync(): 同步列舉所有已經載入的類
  • Java.use(fn): 把當前執行緒附加到Java VM環境中去,並且執行Java函式fn(如果已經在Java函式的回撥中,則不需要再附加到VM),舉個例子:
  • Java.use(className): 對指定的類名動態的獲取這個類的JavaScript引用,後續可以使用$new()來呼叫類的建構函式進行類物件的建立,後續可以主動呼叫 $dispose() 來呼叫類的解構函式來進行物件清理(或者等待Java的垃圾回收,再或者是JavaScript指令碼解除安裝的時候),靜態和非靜態成員函式在JavaScript指令碼里面也都是可見的, 你可以替換Java類中的方法,甚至可以在裡面丟擲異常,比如:
  • Java.scheduleOnMainThread(fn): 在虛擬機器主執行緒上執行函式fn
  • Java.choose(className, callbacks): 在Java的記憶體堆上掃描指定類名稱的Java物件,每次掃描到一個物件,則回撥callbacks:
    • onMatch: function(instance): 每次掃描到一個例項物件,呼叫一次,函式返回stop結束掃描的過程
    • onComplete: function(): 當所有的物件都掃描完畢之後進行回撥
  • Java.cast(handle, klass): 使用物件控制程式碼handle按照klass(Java.use方法返回)的型別建立一個物件的JavaScript引用,這個物件引用包含一個class屬性來獲取當前物件的類,也包含一個$className屬性來獲取類名稱字串,比如:

單篇兒太長,下篇繼續

相關文章