天堂之門
WoW64是Windows x64提供的一種相容機制,可以認為WoW64是64位Windows系統建立的一個32位的模擬環境,使得32位可執行程式能夠在64位的作業系統上正常執行
所以也弄明白了之前為什麼32位的dll需要放在WoW64裡面了,而64位的dll需要放在System32裡面
系統執行程式的時候,會檢測CS段暫存器的值來呼叫API
CS為0x33的時候會切換到64位模式,當CS為0x23的時候就會切換到32位模式
下面透過天堂之門技術呼叫WIN32 API的過程。這裡我們透過一些操作繞過了WoW64機制,手動切換到64位模式並呼叫64位下的ZwOpenProcess函式,大致流程如下(和圖中不太一樣):
- 將cs段暫存器設為0x33,切換到64位模式
- 從gs:0x60讀取64位PEB
- 從64位PEB中定位64位ntdll基址
- 遍歷ntdll64匯出表,讀取ZwOpenProcess函式地址
- 構造64位函式呼叫
結合最近的做題,我總結就是找到更改cs段暫存器值的地方,然後根據彙編找到跳轉的對應地址
關注0X33和0x23和關鍵的彙編指令 jmp far ptr.....
dump下來可以用bn看,然後就可以逆向解密,最近發現了ida的一個外掛
OllyDumpEx · Issue #41 · mentebinaria/retoolkit (github.com)
也是可以直接在ida裡面直接dump了,對於除錯可以使用windbg或者CE(目前對於這兩個的使用還差的很多,還要學)
unicorn
Unicorn 是一個輕量級, 多平臺, 多架構的 CPU 模擬器框架. 我們可以更好地關注 CPU 操作, 忽略機器裝置的差異. 想象一下, 我們可以將其應用於這些情景: 比如我們單純只是需要模擬程式碼的執行而非需要一個真的 CPU 去完成那些操作, 又或者想要更安全地分析惡意程式碼, 檢測病毒特徵, 或者想要在逆向過程中驗證某些程式碼的含義. 使用 CPU 模擬器可以很好地幫助我們提供便捷.
Programming with C & Python languages – Unicorn – The Ultimate CPU emulator (unicorn-engine.org)
它的亮點 (這也歸功於 Unicorn 是基於 qemu 而開發的) 有:
- 支援多種架構: Arm, Arm64 (Armv8), M68K, Mips, Sparc, & X86 (include X86_64).
- 對 Windows 和 nix 系統 (已確認包含 Mac OSX, Linux, BSD & Solaris) 的原生支援
- 具有平臺獨立且簡潔易於使用的 API
- 使用 JIT 編譯技術, 效能表現優異
官方文件裡講了c和python介面,這裡主要說一下python介面
1 from __future__ import print_function
2 from unicorn import *
3 from unicorn.x86_const import *
4
5 # code to be emulated
6 X86_CODE32 = b"\x41\x4a" # INC ecx; DEC edx
7
8 # memory address where emulation starts
9 ADDRESS = 0x1000000
10
11 print("Emulate i386 code")
12 try:
13 # Initialize emulator in X86-32bit mode
14 mu = Uc(UC_ARCH_X86, UC_MODE_32)
15
16 # map 2MB memory for this emulation
17 mu.mem_map(ADDRESS, 2 * 1024 * 1024)
18
19 # write machine code to be emulated to memory
20 mu.mem_write(ADDRESS, X86_CODE32)
21
22 # initialize machine registers
23 mu.reg_write(UC_X86_REG_ECX, 0x1234)
24 mu.reg_write(UC_X86_REG_EDX, 0x7890)
25
26 # emulate code in infinite time & unlimited instructions
27 mu.emu_start(ADDRESS, ADDRESS + len(X86_CODE32))
28
29 # now print out some registers
30 print("Emulation done. Below is the CPU context")
31
32 r_ecx = mu.reg_read(UC_X86_REG_ECX)
33 r_edx = mu.reg_read(UC_X86_REG_EDX)
34 print(">>> ECX = 0x%x" %r_ecx)
35 print(">>> EDX = 0x%x" %r_edx)
36
37 except UcError as e:
38 print("ERROR: %s" % e)
- 行號 2~3: 在使用 Unicorn 前匯入unicorn模組. 樣例中使用了一些 x86 暫存器常量, 所以也需要匯入unicorn.x86_const模組
- 行號 6: 這是我們需要模擬的二進位制機器碼, 使用十六進位制表示, 代表的彙編指令是: "INC ecx" 和 "DEC edx".
- 行號 9: 我們將模擬執行上述指令的所在虛擬地址
- 行號 14: 使用Uc類初始化 Unicorn, 該類接受 2 個引數: 硬體架構和硬體位數 (模式). 在樣例中我們需要模擬執行 x86 架構的 32 位程式碼, 我 們使用變數mu來接受返回值.
- 行號 17: 使用mem_map方法根據在行號 9 處宣告的地址, 對映 2MB 用於模擬執行的記憶體空間. 所有程序中的 CPU 操作都應該只訪問該記憶體區域. 對映的記憶體具有預設的讀, 寫和執行許可權.
- 行號 20: 將需要模擬執行的程式碼寫入我們剛剛對映的記憶體中. mem_write方法接受 2 個引數: 要寫入的記憶體地址和需要寫入記憶體的程式碼.
- 行號 23~24: 使用reg_write方法設定ECX和EDX暫存器的值
- 行號 27: 使用emu_start方法開始模擬執行, 該 API 接受 4 個引數: 要模擬執行的程式碼地址, 模擬執行停止的記憶體地址 (這裡是 X86_CODE32的最後 1 位元組處), 模擬執行的時間和需要執行的指令數目. 如果我們像樣例一樣忽略後兩個引數, Unicorn 將會預設以無窮時間和無窮指令數目的條件來模擬執行程式碼.
- 行號 32~35: 列印輸出ECX和EDX暫存器的值. 我們使用函式reg_read來讀取暫存器的值.
後面對於這個的使用會補上一些題....
每日一題
最開始點進來發現是一串數字,c了過後感覺也不太對,覺得是SMC,然後動態走一下,小改一下花指令
基本可以確定是SMC了,然後除錯開始亂飛
從這裡開始飛,然後根據邏輯判斷這裡也是關鍵的加密部分
jmp far ptr可以知道的是可以同時改變eip和cs段暫存器值的指令
這種格式的jmp指令實現的是上述的段間轉移,同時修改CS和IP,在跳轉範圍大於-32768~32767時使用jmp near ptr不會編譯失敗,但是會連結失敗。
該指令執行後CS:IP將同時修改
這個不要看反彙編指令,其實是跳轉的是4011D0這個地址,修改的CS段暫存器為0X33
然後可以在bn裡設定64位然後檢視反彙編,有個反除錯PEB.BeingDebugged
記得繞過
dump下來後可以看見
最後求flag的過程不難,就是簡單的異或和迴圈左移右移
貼一個其他師傅寫的指令碼
rol = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))
flag = b'1234adc89012345678901234a27890ab'
cmp = [0xAA, 0x4F, 0x0F, 0xE2, 0xE4, 0x41, 0x99, 0x54, 0x2C, 0x2B, 0x84, 0x7E, 0xBC, 0x8F, 0x8B, 0x78, 0xD3, 0x73, 0x88, 0x5E, 0xAE, 0x47, 0x85, 0x70, 0x31, 0xB3, 0x09, 0xCE, 0x13, 0xF5, 0x0D, 0xCA]
# key = [0x9d,0x44,0x37,0xb5]
key = [4,0x77,0x82, 0x4a]
print(bytes(cmp))
print()
def enc(flag):
enc = b''
num = 0x3CA7259D
for i in range(8):
cur = int.from_bytes(flag[4*i:4*i+4],'little')
cur = (cur+num)&0xffffffff
num ^= cur
enc += cur.to_bytes(4,'little')
print("enc1 ", enc)
enc = bytearray(enc )
cur = int.from_bytes(enc[0:8],'little')
enc[0:8] = rol(cur,0xc, 64).to_bytes(8,'little')
cur = int.from_bytes(enc[8:16],'little')
enc[8:16] = rol(cur,0x22, 64).to_bytes(8,'little')
cur = int.from_bytes(enc[16:24],'little')
enc[16:24] = rol(cur,0x38, 64).to_bytes(8,'little')
cur = int.from_bytes(enc[24:32],'little')
enc[24:32] = rol(cur,0xe, 64).to_bytes(8,'little')
# for i in range(4):
# enc[i*8:i*8+4], enc[i*8+4:i*8+8] = enc[i*8+4:i*8+8], enc[i*8:i*8+4],
print("enc2 ", bytes(enc))
for i in range(32):
enc[i] ^= key[i%4]
print("enc3 ", enc)
return enc
def dec(cmp):
print("dec3 ", cmp)
dec = [0]*32
cmp = bytearray(cmp)
for i in range(32):
cmp[i] ^= key[i%4]
print("dec2 ", cmp)
# for i in range(4):
# cmp[i*8:i*8+4], cmp[i*8+4:i*8+8] = cmp[i*8+4:i*8+8], cmp[i*8:i*8+4]
cur = int.from_bytes(cmp[0:8],'little')
cmp[0:8] = ror(cur,0xc, 64).to_bytes(8,'little')
cur = int.from_bytes(cmp[8:16],'little')
cmp[8:16] = ror(cur,0x22, 64).to_bytes(8,'little')
cur = int.from_bytes(cmp[16:24],'little')
cmp[16:24] = ror(cur,0x38, 64).to_bytes(8,'little')
cur = int.from_bytes(cmp[24:32],'little')
cmp[24:32] = ror(cur,0xe, 64).to_bytes(8,'little')
print("dec1 ", bytes(cmp))
num = 0x3CA7259D
for i in range(8):
num ^= int.from_bytes(cmp[4*i:4*i+4],'little')
for i in range(7,-1,-1):
cur = int.from_bytes(cmp[4*i:4*i+4],'little')
num ^= cur
cur = (cur+0x100000000-num)&0xffffffff
dec[4*i:4*i+4] = cur.to_bytes(4,'little')
return bytes(dec)
assert(dec(enc(flag)) == flag)
print('----------------------')
print(dec(cmp)) # 6cc1e44811647d38a15017e389b3f704