X64Dbg 介紹->表達式


未經博主允許禁止轉載盜取以及爬蟲,出處 博客園:ibinary
請遠離垃圾網站: 原文出處 https://www.cnblogs.com/iBinary/
請讀者看到之後去原文觀看.

X64Dbg 介紹->表達式

一丶 字符串格式與Log指令

1.1 前言

x64Dbg是一個開源的ring3層的調試器,其內置了很多強大的命令供我們使用.

而在調試的過程中熟悉它的命令以及腳本語法則會讓我們逆向的時候事半功倍.

二丶字符串格式指令學習

2.1 Log指令

在學習X64Dbg的時候最好優先學習Log指令. 因爲其可以幫我們最快的將指令結果進行輸出. 便於我們驗證命令是否正確.

log指令格式爲如下:

log "{?:expression}"

其中 ? 是類型的意思我們可以自定義的類型以及複雜的類型. 或者說可以省略不寫,而着重於後面的expression表達式,expression(表達式)可以使用X64dbg的命令.也可以是C語言的表達式.

在命令中 {}裏面可以視爲是命令或者說表達式. 而 : 前邊則可以視爲是顯示的數據類型.可以勝

最簡單的log輸出命令則爲如下:

log "HelloWorld"   ===> HelloWorld

如果是C語言的表達式則可以使用 括號 位運算 << >> + -* /等來進行表示

如下:

log "{(1+2)}"   ==> 3  //這裏省略的顯示部分,也就是 ?: 可以直接在{}寫自己的表達式

Log 配合字符串格式指令則能產生強大的效果. 請先下面小節.

2.2 log-簡單指令-字符串格式

類型 含義 應用 輸出結果
d 表示輸出後面命令的結果爲有符號的10進制類型  log  "{d:0x00401000}" 4198400
u 同上,表示無符號十進制 同上,改成d查看 同上
p 表示零前綴開頭的指針類型(pointer) log "{p:0x00401000}" 00401000
s 表示是string的指針 log "{s:0x0040004E}" "This program cannot be run in Dos mode.\r\r\n$"
x 表示16進制 log "{x:0x00400000}" 400000
a 表示地址信息 log "{a:0x00400000}" exeName.00400000
i 輸出彙編指令,按照彙編解析 log "{i:0x00400000}" dec ebp
f 單精度浮點的指針或者寄存器 log "{f:0x00400000}" 1.32567e-038
F 雙精度的浮點指針或者寄存器 log "{F:0x00400000}" 6.37066138261923e-314

補充:

  1. log裏面輸出的地址 可以換成寄存器

    log "{x:eax}" 則是以16進制輸出eax裏面的值 設eax = 0xA7 而輸出 A7
    
  2. x 和 a不同

    x輸出的是純淨的16進制地址,而a輸出的是帶着模塊名字的.

    假設我們的主模塊爲 abc.exe

    則使用a輸出的結果爲

    log "{a:0x00400000}"  ==> abc.00400000
    
  3. 注意單精度與雙精度的大小寫

    大小寫是不同的,請注意.

    且說過是可以使用寄存器的,所以可以使用 XMM寄存器和YMM寄存器來進行解析. 但是請注意,如果使用的是f 那麼如果寄存器是XMM 那麼只會使用 XMM的 31->0這個區間 如果是YMM 則會使用 255-224區間.

  4. s指令

    s指令後面跟着的地址是一個字符串的開始的首地址,s是吧這個地址視爲了0結尾的字符串地址,然後直接解析的.

2.3 log-複雜指令-字符串格式

語法 含義 應用 結果 補充
{winerror@code} 輸出windows錯誤碼,並且format格式化後輸出結果 log "{winerror@5}" ERROR_ACCESS_DENIED:拒絕訪問
`{ntstatus@0} 輸出NTSTATUS類型的錯誤碼 log "{ntstatus@0}" STATUS_SUCCESS:STATUS_SUCCESS
{mem;size@address} 從address地址開始打印size個字節 log "{mem;2@0x00400000}" 4D5A 與之對應我們還可以使用 log "{2:[0x00400000]}"來進行打印,帶引的結果爲 5A4D
{ascii[;length]@address}
{ansi[;length]@address}
從address地址開始打印length長度.以ascii編碼解析 log "{ascii;20@0x0040004E}" This program cannot be run in DO
{utf8[;length]@address} 同上,只不過是以UTF-8類型打印 同上
{utf16[;length]@address} 同上只不過是utf-16來解析 同上

在複雜類型中可以看到 可以使用 xxx@xxxx的方式來進行結果的輸出 而如果是以 string類型來進行輸出的話 那麼需要你加上一個 可選的 length長度 如果不加也可以,那麼輸出結果可能不是你想要的.

其它類型

語法 含義 應用 結果 補充
{disasm@address} 打印此地址的反彙編 log "{disasm@0x00400000}" dec ebp 此指令等價於 {i:address}
{modname@address} 打印模塊的名稱 log "{modname@0x00400000}" abc.exe 爲什麼輸出abc.exe參考2.1log指令介紹
{label@address} 打印此地址處的(自動)標籤 log "{label@76570A50}" MessageBoxA 0x76570A50是MessageBox的地址,其標籤也是命名的MessageBoxA
{comment@address} 打印此地址處的自動註釋 同上 自動的意思可能是默認的意思,默認的註釋.
{bswap[;size]@value} 反轉字節 log "{bswap;4@0x12345678}" 78654321 bswap是反轉字節的意思,可以理解爲是函數,如果是函數那麼後面跟着; 分號後面則是參數,比如要反轉的字節的長度是4個字節,要反轉的值是0x12345678

2.4 命令例子

例子 說明 結果 補充
log "aaaa:{eax}" 輸出eax的值,顯示名稱爲aaaa {}括號裏面的內容則是命令 aaaa:123
log "passWorld:{s:0x0040004e}" 顯示名稱爲passWorld 以s類型解析0x0040004e passWorld:"This is xxxx"
log "passWorld:{ansi;20@0x0040004e}" 以ansi解析20個字節,從0x00400004e位置 passWorld:This program cannot be run in DO  注意它解析的字符串沒有雙引號
log "要進行反轉了:{bswap(0x12345678)}" 反轉0x12345678 78654321 這裏使用的bswap是表達式的函數,後面會講解x64提供的表達式函數有哪些.

三丶X64Dbg輸入命令介紹

3.1 內存的應用

函數 說明 應用 結果
[addr] 帶方括號可以直接從裏面讀取直接地址,默認省略了大小 [0x00400000] 00905A4D
n:[addr] 同上,上面默認省略的的大小,這裏可以顯示給定 1:[0x00400000] 4D
seg:[addr] 從所處的段中讀取 DWORD/QWORD,取值可以是gs es cs fs ds ss fs gs cs:[0x00401000] CCCCCCCC

可以看到地址的訪問可以用 中括號[] 來進行訪問, 前邊加上 大小則是代表在這個地址裏面取出多少字節來用. 不常用的就是 seg:[addr] 在32/64下已經沒有 "段的概念了"

3.2 標誌僞指令

在x64dbg中想要直接訪問標誌的值可以使用 _標誌名 來進行訪問

如下:

_cf_pf_af_zf_sf_tf_if_df_of_rf_vm_ac_vif_vip_id

使用log可以進行輸出

log "CF的值是:{_cf}"  ===> CF的值是1

3.3 模塊的應用

模塊分爲如何訪問加載的模塊以及解析函數的地址.

  • 加載模塊的應用

    這些命令很常見,因爲在X64DBG裏面跳轉地址的話可以直接使用模塊基地址+偏移(RVA)來進行跳轉.

    命令如下:

    命令 說明 應用 結果
     module :0 訪問模塊基地址 :0 或者[abc.exe]:0 0x00400000
    module :base 同上,可以省略[] :base或[abc.exe]:base 0x00400000
    module :imagebase 同上
    module :header 同上

爲了不讓MarkDown給將[]翻譯成網址,所以在[]後面加了個空格,然後加了一個: 其實原指令爲如下:

[module]:0/base/imagebase/header
  • RVA偏移的使用

    根據上面所屬我們可以獲取模塊的基地址 所以後面我們直接加 RVA(偏移即可)

    [module]:0 + [rva/offset]   應用--->  :0 + 0x0010000  = 0x004010000
    [module]:0$[rva/offset]     同上
    [module]:0#[rva/offset]     同上
    
  • 模塊入口點

    命令如下:

    ```[module]:entry
    

    --->
    ?oep
    ?entry
    ?ep

```cpp
可以使用Log輸出

```cpp
log "base =:0x{:0} oep = :0x{abc.exe.oep}"
--->
base = 0x00400000 oep = 0x00401520

?的方式
log "base =:0x{:0} oep = :{?oep}"
--->
結果同上

在CTRL+G中也可以使用這個 oep(ep entry)命令直接跳轉到OEP.

  • 函數或者模塊導出函數的應用.
[module].dll.[api]
[module]:[api]
[module]:[ordinal]

使用:

user32.dll.MessageBoxA    ---> 顯示對應的地址
user32.MessageBoxA
user32.dll:MessageBoxA
--->log使用方式
log "user32.MessageBoxA Addr= 0x{user32.MessageBoxA}"
----->結果
user32.MessageBoxA Addr= 0x76570A50

其實也可以配合 OEP entry等命令使用. 這裏本質就是獲取 每個模塊的導出函數的地址.

編譯我們的操作.

四丶表達式

4.1 C語言表達式

調試器是允許基本的表達式進行命令操作的. 我們只需要在命令窗口中使用表達式即可.結果則會顯示到控制檯中. 表達式除了計算之外還可以使用類似C的語法快速更改變量.

C表達式可以使用如下:

()        括號
[]        中括號
-1 ~1 !0  負數 取反 邏輯非
+-*/  %    加減乘除取餘
<< >> <<< >>> 無符號左移(shl無符號 sal有符號) 無符號右移(shr sar)
> < <= >= != 大於小於 等運算
& | ! ^ || && -> 等符號

4.2 字符串方法

既然有表達式,那麼表達式函數也是我們要掌握的.這個是 x64dbg爲我們提供的. 可以使用.

它分爲如下幾個模塊的命令.需要單獨展開說明.

或者去官網查看.

表達式函數 — x64dbg 文檔

4.2.1 字符串函數

字符串表達式函數我們在Log講解的時候已經接觸過了. 其命令如下:

utf8(addr)             -->以utf-8解析地址輸出utf-8字符串
utf16(addr)            -->同上,只不過是utf16
strstr(str1,str2)      -->同C庫函數,查找A字符裏面是否包含B字符串
streq(str1,str2)       -->比較兩個字符串
strlen(str)            -->計算字符串的長度

這些表達式函數都可以作爲參數用作其它函數的的輸入參數. 所有表達式計算的結果都必須爲一個有效的數字,因此不是有效的expresion,因此不能作例爲跟蹤條件.如 utf8 utf16 str utf8(rax)

實戰如下:

strstr(utf8(reg/addr),"This")  ---> 結果返回1
streq(utf8(00040004E),"abc")   ---> 結果返回0
strlen(utf8(0040004E))         ---> 結果返回0x2B
strlen("abc")            ---> 結果返回3

4.2.2 模塊方法

mod.party(addr) ---> 獲取模塊的模式編號, addr = 0則是用戶模塊,1則是系統模塊
mod.base(addr)  --->  獲取模塊基址
mod.size(addr)  --->  返回模塊大小
mod.hash(addr)  --->  返回模塊hash
mod.entry(addr) --->  返回模塊入口
mod.system(addr)--->  如果addr是系統模塊則爲true否則則是false
mod.user(addr)  --->  如果是用戶模塊則返回true 否則爲false
mod.main()      --->  返回主模塊基地址
mod.rva(addr)   --->  如果addr不在模塊則返回0,否則返回 addr所位於模塊的 RVA偏移
mod.offset(addr)--->  獲取地址所對應的文件偏移量,如果不在模塊則返回0
mod.isexport(addr) ---> 判斷該地址是否是從模塊導出的函數,true是 false則不是
mod.fromname(str)  ---> 獲取模塊的基址,注意是str是str
str 0 mod.fromname("ntdll.dll")

使用:

mod.party(0x77960000) --> 返回1 0x77960000是NTDLL,屬於系統模塊
mod.party(0x00401000) --> 返回0 0x00401000是用戶模塊
mod.base(0x00401111)  --> 返回  0x00400000 
mod.size(0x00401111)  --> 返回模塊大小 
mod.hash(0x00401111)  --> 返回哈希值  16進制
mod.entry(0x00401111) --> 返回OEP入口點
下面同上 不演示

4.2.3 進程方法

peb()   ---> 獲取PEB的地址
teb()   ---> 獲取TEB的地址
tid()   ---> 獲取當前線程的ID
kusd()  ---> 查詢X64Dbg 應該是獲取用戶共享數據 地址

使用:

log "Peb Addr = 0x{peb()} teb addr = 0x{teb()} tid = 0x{tid()}"
--->
peb Addr = 0x268000
teb addr = 0x26b000
tid      = 0x1E30

如果想要顯示10進制的 tid(或者其它十六進制值可以改成如下命令)

tid = {d:tid()}
tid == 7728

4.2.4 一般常用方法

bswap(value) 進行字節交換,也就是反轉.
ternary(condition,val1,val2) 判斷參數1的條件,如果條件爲0則返回,否則返回val1 val2
GetTickCount(); 獲取刻度值 

用法:

bswap(0x12345678)  --->  0x78563412

4.2.5 內存方法

mem.valid(addr) 判斷addr是否有效,有效則返回True
mem.base(addr)  或者當前addr的基址
mem.size(addr)  獲取當前addr內存的大小
mem.iscode(addr) 判斷當前 addr是否是可執行頁面,成功返回TRUE
mem.decodepointer(ptr) 解密指針,相當於調用了API. DecodePointer ptr

4.2.6 內存指針方法

ReadByte(addr / reg); 從addr或者寄存器中讀取一個字節內存並且返回
Byte(addr) byte(addr)  同上

-------------
ReadWord(addr)  Word(addr) word(addr) 同上 讀取兩個字節
ReadDDword(addr) Dword(addr) dword(addr) 同上 讀取四個字節
ReadQword(addr) Qword(addr) qword(addr) 同上 讀取8個字節,但是隻能是64位程序方可使用
ReadPtr(addr) 從地址中讀取指針(4/8字節)並返回讀取的指針值
ReadPointer(addr) ptr(addr) Pointer(addr) pointer(addr) 都同上

例子:
讀取主模塊中的內容.

ptr(mod.main()) --> 00905A4D
byte(mod.main()) --> 0x0000004D

4.2.7 反彙編方法

dis.len(addr)      獲取addr處的指令長度。
dis.iscond(addr)   判斷當前addr位置是否是條件指令(比如jxx) 返回值: 是的話True 否則False
dis.isbranch(addr) 判斷當前地址是否是分支指令   返回值: 同上
dis.isret(addr)    判斷是否是ret指令          返回值: 同上  
dis.iscall(addr)   判斷是否是call指令         返回值: 同上  
dis.ismem(addr)    判斷是否是內存操作數        返回值: 同上
dis.isnop(addr)    判斷是否是nop             返回值: 同上
dis.isunusual(addr)判斷當前地址是否指示爲異常地址 返回值: 同上
dis.branchdest(addr):將指令的分支目標位於(如果按 Enter 鍵,它將遵循什麼)。addr
dis.branchexec(addr):如果 分支 at 要執行,則爲 true。addr
dis.imm(addr)       獲取當前指令位置的立即數(這一行指令中出現的立即數)
dis.brtrue(addr):指令在 的分支目標。addr
dis.brfalse(addr):下一條指令的地址(如果指令 at 是條件分支)。addr
dis.next(addr):    獲取addr的下一條地址
dis.prev(addr):    獲取addr上一條低地址
dis.iscallsystem(addr) 判斷當前指令是否是系統模塊指令
dis.mnemonic(addr)返回addr的助記符號,可以當作參數給 字符串函數使用 straddrstr.streq(dis.mnemonic(cip), "cpuid")
dis.text(addr):以字符串的形式返回指令文本。可用作與條件,或者其它函數的輸入參數.
dis.match(addr, str):正則表達式匹配,str是正則表達式,判斷addr是否命中
例:您可以使用 來查看可以匹配的內容。dis.match(rip,"text.+,0x1")

4.2.8 "方法/Call"" 相關方法

1.引用相關

我們知道 可以在 X64Dbg中 對一個函數 按快捷鍵 CTRL+R 可以看到有多少地址引用了它.

然後進行分析. 那麼 自然我們也有表達式函數供我們使用.

ref.count() 獲取當前參考視圖中引用的個數
ref.addr(index); 獲取當前EIP位置引用此函數的第幾個index函數地址. 

ref.addr其實是將所有引用函數列成了一個數組,而我們傳入的index就是獲取第幾個引用函數的索引.

2.參數相關

我們可以使用表達式函數來對我們傳入的參數進行操作.

arg.get(index); 獲取當前函數堆棧中的第幾個參數,假設返回地址在堆棧上,並且我們在函數內部.
arg.set(index,value);設置的索引位置的值爲

4.2.9 異常相關

異常在我們調試的時候也很常用, 比如有時候需要獲取異常發生的地址.以及異常代碼.等信息.

那麼表達式函數一樣提供了.

ex.firstchance():最後一個異常是否爲第一次機會異常。
ex.addr():最後一個異常地址。例如,導致異常的指令的地址。
ex.code():最後一個異常代碼。
ex.flags():最後一個異常標誌。
ex.infocount():上次異常信息計數(參數數)。
ex.info(index):最後一個異常信息,如果索引超出範圍,則爲零。

關於異常我們最應該熟悉的就是 異常記錄結構體 EXCEPTION_RECORD

這個結構體裏面存儲了我們的異常信息, 比如異常代碼 異常的地址等信息.

typedef struct _EXCEPTION_RECORD {
  DWORD                    ExceptionCode;
  DWORD                    ExceptionFlags;
  struct _EXCEPTION_RECORD *ExceptionRecord;
  PVOID                    ExceptionAddress;
  DWORD                    NumberParameters;
  ULONG_PTR                ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;

請參考MSDN
EXCEPTION_RECORD結構

五丶變量

5.1 保留的變量

在X64Dbg中有一些保留的變量. 分別爲如下:

$res / $result    ---> 保存了常規結果的變量
$resN / $resultN  ---> 可選的其它結果變量 N的取值爲1 - 4 不能超過5 也不能爲0
$pid              ---> 保存了當前進程的Pid
$hp / $hprocess   ---> 保存了調試的可執行句柄
$lastalloc        ---> 保存了最後一次使用腳本命令alloc之後的結果
$breakpointcondition ---> 控制條件斷點命令中的暫停行爲
$breakpointcounter  ---> 在計算條件斷點的條件之前 設置的斷點的命中計數器
$breakpointlogcondition  --->條件斷點的日誌條件
$breakpointerexceptionaddress  --->異常斷點設置的異常地址
$breakpointlogcondition        --->日誌條件變量

除了上述變量 還有由系統創建的變量 如果是System 創建的 那麼只能讀取和寫入 不能刪除.

如果是隻讀變量,那麼由系統創建的變量則只能讀取,不能寫入或者刪除.

使用例子 打開腳本界面(Alt+S) -> 加載腳本 -->腳本中的內容如下:

可以按TAB 單步執行腳本, 也可以按空格直接運行起來腳本

mov $res,1
mov $res,1
mov $res1,2
mov $res2,3
mov $res3,4
mov $res4,5
log "{$res}{$res1}{$res2}{$res3}{$res4}"
log "pid = {$pid}"
log "最後一次申請的內存地址爲{$lastalloc}"
log "條件斷點暫停條件變量 {$breakpointcondition}"
log "條件斷點的計數變量 {$breakpointcounter}"
log "條件斷點的日誌條件變量 {$breakpointlogcondition}"
ret-->
12345
pid = 9B8
最後一次申請的內存地址爲0
條件斷點暫停條件變量 0
條件斷點的計數變量 0
條件斷點的日誌條件變量 0

六丶條件斷點的應用

6.1 命中斷點時x64執行的應用

  1. 如果斷點是異常斷點,那麼變量 $breakpointerexceptionaddress則會被設置

  2. 如果不是那麼就遞增 計數器($breakpointcounter)

  3. 如果設置的中斷條件 那麼就計算 表達式的值

    3.1 如果設置的是日誌條件 則計算表達式

    3.2 如果設置的是命令條件 則計算表達式

    3.3 表達式的結果只會有兩種情況 要麼爲 1(True) 要麼爲0 (False)

  4. 日誌的條件表達式如果計算爲0 那麼就會按照日誌文本格式打印,(字符串格式參考第二大章)

    4.1 如果設置的命令條件計算結果爲1 那麼就更新中斷條件變量($breakpointcondition)

    並且更新 日誌條件變量 ($breakpointlogcondition)

    如果腳本修改了 $breakpointcondition 這個中斷條件的系統變量則腳本能控制調試對象是否會被中斷.

因爲計數器用的可能比較多.所以我們可以使用 命令中的條件斷點控制(Api)來清除計數器.

 ResetBreakpointHitCount(addr,newCount) 對addr地址設置爲新的計數變量

6.2 條件斷點的常規應用.

  • 寄存器值判斷

    eax == 1 && ebx == 2; 如果eax == 1 並且 ebx == 2斷下 
    
  • 內存值判斷

    byte(addr) == 0x4d    如果取出的內存結果爲0x4d 則斷下 (可以不加前綴0)
    
  • 斷點次數命中判斷

    $breakpointcounter == 3  
    ($breakpointcounter % 3) == 0
    
  • 線程id斷下

    tid() == 1C0
    
  • 重要 斷點爲字符串的時候斷下

以CreateFile爲例,如果我們想命中它打開指定文件的時候斷下 我們則可以進行如下操作.

strstr(utf8(ecx),"d:\\123") 
或
strstr(utf16([esp + 4]),"123")  == 1   包含123得時候斷下
strstr(utf16([esp + 4]),"123") != 1    不包含123得時候斷下

其中uft8 utf16在上面已經介紹過了. 我們可以使用這些表達式函數.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章