看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路

Editor發表於2018-12-23


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


第六題《追凶者也》在今天(12月13日)中午12:00 攻擊結束。


pizzatql 以 2448s 的成績首先攻破此題。


成功攻擊第六題《追凶者也》的團隊共計29支。


最新賽況戰況一覽


第六題之後的最新排名是什麼樣的呢?


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路

前四名依然不變,值得一說得是第10名的A2 晉升到排行榜第5,祝賀~!



第六題 點評


crownless:

“追凶者也”此題的主要困難是設定了異常回撥來進行反除錯,並要求參賽者完成一個簡單的數字拼圖,主要考驗了參賽者通過靜態分析找到關鍵函式的能力和腦筋急轉彎能力。



第六題 出題團隊簡介


出題團隊: one team  


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路



第六題 設計思路


由看雪論壇穿甲葡萄籽 原創


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


程式需要執行在win7 64系統下。



設計思路:


1. 使用tls將GetDlgItemTextA Hook。在GetDlgItemTextA 返回前先獲取到輸入文字,進行判斷。如果成功則修改MessageBoxA的引數,使其彈出成功。


2. 設定異常回撥來進行反除錯。tls中會將回撥函式的程式碼改為一個跳轉到視窗建立函式。


3.設計簡單的演算法,數字拼圖


413

725

860


輸入規則為:wasd表示方向,例如:w1表示將1向上移動一下。


成功拼成


123

456

780


表示成功。


為防止出現多個解,密碼長度限制在20之內,並且會進行雜湊計算校驗。


破解思路:


1.找到關鍵的Hook處理函式,可以根據Hook函式找到關鍵處理函式地址。


2. 其中可以找到一個初始化數字拼圖的函式,初始化一個二維陣列。


413

725

860


3. 很容易就能得到正確的結果。


原文連結:


https://bbs.pediy.com/thread-227070.htm(含附件)



第六題 追凶者也 解題思路


本題解析由看雪論壇HHHso 原創。


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


題目示意:“ 在win7執行 win10可能報錯”


實際上由於其採用模組全徑雜湊來定位,而不僅是模組名,或全大或小寫路徑雜湊,其相容性不是一般的差,路徑稍有出入,win7都不見得一定能跑得起來,需要做一定修正。


這裡在無法確定劫持模組時,從函式名的雜湊值著手結合pefile搜尋目標模組及函式。


這裡給出了一種python程式碼如何無縫呼叫IDA函式shellcode的簡單方式。


實際上IDA出現的任何函式都可以轉為python呼叫,包括那些使用匯入函式和多個子函式呼叫的,這裡只做簡單應用。


Just Do I.T.


上IDA靜態分析,Exports中發現了執行緒回撥函式,它會優先於其它程式程式碼先執行,常見SMC手段之一,至於一些版本的VMP是見面必TlsCallback,這裡與VMP無關。


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


回撥函式邏輯相對簡單,如圖


(1) 呼叫 Hi_smc_mainW_jmpto_main_401D50 函式將封裝函式mainW修正轉呼叫到真正的主函式main。


(2) 呼叫 Hi_hook_GetWindowTextA_401C10 完成 user32.GetWindowTextA 函式的劫持,以插入校驗程式碼函式。


(3) 啟動執行緒函式 Hi_loop_checkset_hook_401CF0 對劫持狀態檢測,若劫持被還原,則進行劫持。


(注:由於其迴圈沒有任何如sleep(0.1)之類的延遲,迴圈CPU開銷不是一般大)


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


(1)Hi_smc_mainW_jmpto_main_401D50 函式偽碼如圖。


 (1.1) Hi_VirtualProtect_P1addr_64h_RWE_4019B0 將特定記憶體屬性修改為可讀、可寫、可執行,而Hi_VirtualProtect_P1addr_64h_backProtect_4019E0 執行相反操作,將記憶體屬性還原為原來的屬性。


(1.2) unsigned char loc_long_jmp[5]存放用於放置在 Hi_fp_mainW_414018指向的封裝函式Hi_mainW_401280偏移+4處的長跳轉指令,跳轉到Hi_fp_mainPtr_414014指向的主函式Hi_main_401220。


長跳指令偏移基本演算法:long_jmp_E9_offset = tgt_addr - (long_jmp_E9_addr+5) //length of long_jmp_E9 = 5


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


Hi_mainW_401280 修改前後:


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


(2)user32.GetDlgItemTextA劫持函式 Hi_hook_GetWindowTextA_401C10


(2.1)Hi_VirtualProtect_P1addr_64h_RWE_4019B0 Hi_VirtualProtect_P1addr_64h_backProtect_4019E0 和長跳指令原理參考(1)


(2.2)   Hi_getaddr_of_user32_GetDlgItemTextA_4018D0主要是獲取目標函式地址函式,以搜尋模組路徑雜湊值和函式名雜湊值的方式;


(2.3) 劫持點位置是 user32.GetWindowTextA+0x20


(2.4)  劫持嵌入的使用者函式為Hi_GetWindowTextA_post_401A10


(2.5)  全域性變數


全域性變數  Hi_fp_user32_GetDlgItemTextA_4147E8  存放的是返回的GetDlgItemTextA地址,


全域性變數  Hi_back_short_jmp_with3_raw_bytes_4147DC  存放被劫持點的5位元組程式碼(剛好一條長跳指令)


全域性變數  Hi_long_jmp_414028  存放我們根據前述長跳偏移演算法計算得到的長跳指令。


我們主要關注(2.2)(2.3)(2.4)


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


(2.2) Hi_getaddr_of_user32_GetDlgItemTextA_4018D0如下


其先通過  Hi_getModule_byHash_4018F5  獲取  user32.dll  模組控制程式碼,以edx暫存器返回,


然後  Hi_getFunction_byHash_401928  獲取函式  GetDlgItemTextA 的函式地址。


在這裡,如果樣例能正常運作,在0x4018ee處斷下,觀察eax的值,一般偵錯程式都會指明是GetDlgItemTextA 函式地址。但在無法執行的情況下該如何是好?且繼續往下。


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


(2.2.1)Hi_getModule_byHash_4018F5原理


fs:[30]為程式快資訊地址,通過windbg的 dt _PEB dwo(fs:[0x30])我們可以知道, 其中0x0C位置為程式先後載入的模組資訊Ldr,樣例選用


+1C位置的初始化順序排列的模組鏈,其中

.+00.Next

.+08 ModuleBase

.+18 ModuleNamePtr


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


1:001> dt _PEB dwo(fs:[0x30])

ntdll!_PEB

+0x000 InheritedAddressSpace : 0 ''

+0x001 ReadImageFileExecOptions : 0 ''

+0x002 BeingDebugged    : 0x1 ''

+0x003 BitField         : 0x4 ''

+0x003 ImageUsesLargePages : 0y0

+0x003 IsProtectedProcess : 0y0

+0x003 IsImageDynamicallyRelocated : 0y1

+0x003 SkipPatchingUser32Forwarders : 0y0

+0x003 IsPackagedProcess : 0y0

+0x003 IsAppContainer   : 0y0

+0x003 IsProtectedProcessLight : 0y0

+0x003 IsLongPathAwareProcess : 0y0

+0x004 Mutant           : 0xffffffff Void

+0x008 ImageBaseAddress : 0x010c0000 Void

+0x00c Ldr              : 0x77630c40 _PEB_LDR_DATA  <-----------------------------------先後載入的模組鏈資訊

+0x010 ProcessParameters : 0x001629d0 _RTL_USER_PROCESS_PARAMETERS

(略)

1:001> dt _PEB_LDR_DATA dwo(dwo(fs:[0x30])+0x0C)

ntdll!_PEB_LDR_DATA

+0x000 Length : 0x30

+0x004 Initialized      : 0x1 ''

+0x008 SsHandle : (null) 

+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x164fd0 - 0x166f50 ]             按載入先後排序的模組鏈

+0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x164fd8 - 0x166f58 ]        按記憶體地址排列的模組鏈

+0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x164ed8 - 0x165470 ] 按初始化順序排列的模組鏈(樣例所選)

+0x024 EntryInProgress  : (null) 

+0x028 ShutdownInProgress : 0 ''

+0x02c ShutdownThreadId : (null)

1:001> dt _LDR_DATA_TABLE_ENTRY dwo(dwo(dwo(fs:[0x30])+0x0C)+0x1C)-0x10

ntdll!_LDR_DATA_TABLE_ENTRY

+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x165460 - 0x164fd0 ]

+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x165468 - 0x164fd8 ]

+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x165830 - 0x77630c5c ]  <---------------遍歷位置

+0x018 DllBase          : 0x77510000 Void //若匹配則返回偏移+8位置的值,為模組基址

+0x01c EntryPoint       : (null) 

+0x020 SizeOfImage      : 0x19c000

+0x024 FullDllName      : _UNICODE_STRING "C:\WINDOWS\SYSTEM32\ntdll.dll" // 偏移+14,+16,+18位置 {len,max,strptr}

+0x02c BaseDllName      : _UNICODE_STRING "ntdll.dll"

+0x034 FlagGroup        : [4]"???"

+0x034 Flags            : 0xa2c4

+0x034 PackagedBinary   : 0y0

+0x034 MarkedForRemoval : 0y0

+0x034 ImageDll         : 0y1

+0x034 LoadNotificationsSent : 0y0

1:001> dt _UNICODE_STRING

ntdll!_UNICODE_STRING

+0x000 Length           : Uint2B

+0x002 MaximumLength    : Uint2B

+0x004 Buffer           : Ptr32 Wchar


(2.2.2) Hi_getFunction_byHash_401928 原理


其主要是遍歷其模組的匯出函式表的匯出函式:


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


(2.2.3)雖然上面已經標註處模組路徑是C:\Windows\sysWOW64\user32.dll,


但實際上win10會將Ldr中的模組鏈的模組路徑修改為C:\windows\system32\user32.dll。


不會有sysWOW64的出現,即使用的是該目錄模組,所以永遠是搜不到,永遠在迴圈中。


這時可將 004018D9 push    3BD696F4h處的常量修改為0x6B87C9A9  = hashw("C:\\WINDOWS\\System32\\USER32.dll"),即可在Win10中跑起來。


如圖,hashw是我們python封裝的IDA看到的樣例hash函式,任何版本系統執行,修改為響應user32.dll全路徑的hashw值即可。


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


(2.2.4)必須承認,開始是無法確定是C:\Windows\sysWOW64\user32.dll的,所以只能從函式名著手。


我們遍歷匯入模組所有匯出函式的hash值,找到目標函式。


DumpBytesFromIDAtoFile 用於從IDA中dump處 Hi_hash_bufPtr_bufSize_4017B0 函式的shellcode


getFuncPtrFromShellCodeFileWithModified 負責將shellcode生成python呼叫約定。


我們對原hash的程式碼另外封裝hashw和hashc版,hashw主要針對模組路徑(UNICODE),而hashc針對函式名(ASCII)。


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


def DumpBytesFromIDAtoFile(ea_start = 0,ea_end = 0,full_file_path_name=''):

cbsize = ea_end - ea_start

bs = array.array('B','\0'*cbsize)

for i in xrange(0,cbsize):

bs[i] = Byte(ea_start+i)

with open(full_file_path_name,'wb') as fout:

bs.tofile(fout)

def LoadShellCodeFile(shellcodefile):

sc = None

with open(shellcodefile,'rb') as fin:

fin.seek(0,2)

scsize = fin.tell()

fin.seek(0,0)

sc = array.array('B')

sc.fromfile(fin,scsize)

return sc

def InitShellCode(sc):

MEM_COMMIT = 0x00001000

PAGE_EXECUTE_READWRITE = 0x40

shellcodesize = 0x1000

shellcodeaddr = windll.kernel32.VirtualAlloc(0,shellcodesize,MEM_COMMIT,PAGE_EXECUTE_READWRITE)

MEM_RELEASE = 0x8000

if sc.__len__() > shellcodesize: 

raise Exception("ShellCodeSize > 0x{:X}".format(shellcodesize))

memmove(shellcodeaddr,sc.buffer_info()[0],sc.__len__())

return shellcodeaddr

def getFuncPtrFromShellCodeFileWithModified(shellcodefile,rModifyFunc,ProtoType):

sc = LoadShellCodeFile(shellcodefile)

shellcodeaddr = InitShellCode(sc)

rModifyFunc(shellcodeaddr)

return ProtoType(shellcodeaddr)

def getProtoType(ProtoTypeName = ""):

from ctypes.wintypes import HWND, LPCSTR, UINT

ProtoTypeName_ProtoType["windll_UintUintUnit"] = WINFUNCTYPE(UINT,UINT,UINT)

return ProtoTypeName_ProtoType[ProtoTypeName]

def ModifyFunc(shellcodeaddr):

pass

DumpBytesFromIDAtoFile(0x4017B0,0x40180B,r'.\CTF06_hash.b')

shellcodefile =r'.\CTF06_hash.b'

rModifyFunc = ModifyFunc

ProtoType = getProtoType("windll_UintUintUnit")

pysc_decrypteStrP1toP2 = getFuncPtrFromShellCodeFileWithModified(shellcodefile,rModifyFunc,ProtoType)

def hashw( s = r'C:\WINDOWS\SYSTEM32\ntdll.dll'):

a = create_unicode_buffer(s)

print "{:0X}".format(pysc_decrypteStrP1toP2(addressof(a),a.value.__len__()*2))

def hashc( s = r'C:\WINDOWS\SYSTEM32\ntdll.dll'):

a = create_string_buffer(s)

return pysc_decrypteStrP1toP2(addressof(a),a.value.__len__())

#print "{:0X}".format(pysc_decrypteStrP1toP2(addressof(a),a.value.__len__()))

rp = r'c:\windows\system32'

ms = ['ntdll.dll','kernel32.dll','kernelbase.dll','user32.dll','gdi32.dll','msvcrt.dll']

mns = {}

for m in ms:

mp = os.path.join(rp,m)

pe = pefile.PE(mp)

print mp

for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:

if exp.name not in [None,'']:

h = hashc(exp.name)

if h not in mns:

mns[h] = [m+'|'+exp.name]

else:

mns[h].append(m+'|'+exp.name)

mns[0x925DF53F]

['user32.dll|GetDlgItemTextA']


於是我們得到要劫持的是user32.dll中的GetDlgItemTextA函式


(2.3) 劫持點 GetDlgItemTextA+0x20如圖中jmp指令位置


即使用者插入的函式  Hi_GetWindowTextA_post_401A10  會在  GetWindowTextA  獲取輸入key後呼叫。


這時已經取得使用者輸入key為圖示lpString,長度為cchMax區域性變數,(記下它們對應的位置ebp+10h,ebp+14h,圈重點,這個會考)


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


(2.4) 由上述jmp劫持呼叫到 Hi_GetWindowTextA_post_401A10 中,我們的的考點來了,ebp不變,即ebp+10h,ebp+14h對應中key和keylen。


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


Hi_GetWindowTextA_post_401A10 偽碼如下,


(2.4.1)通過 Hi_initmap_run_401290(key,keylen) 對內建九宮圖變換校驗,應返回1。


(2.4.2)然後進一步通過 Hi_hash_bufPtr_bufSize_4017B0 檢測器雜湊值,防止多解。


(2.4.3)通過上面兩步後,就會解密出 Hi_sucess_asc_41401C 和 Hi_OK_asc_414024 字串用於顯示成功。


(2.4.4)函式最後會還原劫持,以呼叫GetWindowTextA後續程式碼。(並進而被前述的迴圈檢測執行緒再度劫持)


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


(2.4.1) Hi_initmap_run_401290九宮圖校驗


其定義了九宮圖,unsigned char tblmap[3][3];


初始化為:


413

725

860


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


通過Hi_director_step_401380函式用輸入key變換,最終要求九宮圖變成

123

456

780


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


從圖中我們知道key長都為偶數,每兩個作出一個移動操作,每兩個字元的第一個取值為wdsa之一,分別對應


w > 0  up

d > 1  right

s > 2  down

a > 3  left


而在 Hi_director_step_401380中,要求每對的第二各字元取值不能為0,取值只能在是九宮中出現的12345678之一。


如d3表示將九宮圖中的3右移動一位,原位置置零。於是可得到最短移動方案。


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


即為

d 6

d 8

s 7

s 4

a 1

w 2

a 5

w 6


最終得到 d6d8s7s4a1w2a5w6,其也滿足雜湊要求5634D252


>>> hex(hashc('d6d8s7s4a1w2a5w6'))


'0x5634d252L'


再回頭看看別的有啥發現?


主函式Hi_main_401220 進來就是對話方塊顯示了,對話方塊訊息處理函式Hi_DialogFunc_4011A0 中 Hi_onClink_401040 為確認響應函式。


由於 GetDlgItemTextA 已經被劫持,呼叫GetDlgItemTextA時,若校驗通過,


則 Hi_OK_fail_asc_41400C已被解密填充為OK!


Hi_tip_msg_sptr_414000 解密填充為success。


否則還是原來的“Try again!"和“fail"


>>> e = 'STA@AVU\x00'

>

>> ''.join([chr((0x27-i)^ord(e[7-i])) for i in range(0,8)])[::-1]

"success'"

>

>> e1 = 'oj\x03'

>

>> ''.join([chr((0x22-i)^ord(e1[2-i])) for i in range(0,3)])[::-1]

'OK!'

>

>>



看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路

看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


cl = sark.Line(ea = 0x40D189)

cstr = ''

while cl.ea != 0x40D2A9:

cstr+=chr(cl.insn.operands[1].imm)

cl = cl.next

['Kernel32.dll', 'GetProcAddress', 'LoadLibraryA', 'User32.dll', 'MessageBoxA', 'success', '']


Hi_nop_401020 只是作者原始碼沒修改徹底的無用函式,作者另一個方案是通過Hi_load_call_MessageBoxA_success_40D180函式動態載入呼叫顯示成功資訊,這裡沒用到。




合作伙伴

看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路


騰訊安全應急響應中心 

TSRC,騰訊安全的先頭兵,肩負騰訊公司安全漏洞、黑客入侵的發現和處理工作。這是個沒有硝煙的戰場,我們與兩萬多名安全專家並肩而行,捍衛全球億萬使用者的資訊、財產安全。一直以來,我們懷揣感恩之心,努力構建開放的TSRC交流平臺,回饋安全社群。未來,我們將繼續攜手安全行業精英,探索網際網路安全新方向,建設網際網路生態安全,共鑄“網際網路+”新時代。

看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路

看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路



轉載請註明:轉自看雪學院



看雪CTF.TSRC 2018 團隊賽 解題思路彙總: 


相關文章