看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路
2018年12月3日12:00,『看雪CTF.TSRC 2018 團隊賽』之攻擊篇第二題拉開了序幕。
pizzatql戰隊憑藉1993s的成績,成為首位拿下第二題《半加器》的戰隊。
激烈的戰勢愈加激烈!截止今天(12月5日中午12:00)第二題攻擊已經關閉。
接下來我們一起來看看本次比賽的最新進展吧!
最新賽況戰況一覽
CTF第二題《半加器》由 防守方 吃瓜小群眾 之隊出題,截止比賽結束已被67個團隊攻破。
本題過後,攻擊團隊率先領先的Top10團隊為:
細心的朋友會發現,二殺結束後的Top10和一殺後排名完全一致。
那麼,隨著新題目的解鎖,是否會誕生此次大賽的黑馬呢?
這個結果,我們說了不算,你說了算!
加油吧,勇士們~!
第二題《半加器》 設計思路
下列設計思路由 zengYx 原創。
團隊名稱:吃瓜小群眾
團長QQ:839667825
參賽題目:CrackMe
題目答案:jmubojgAbqdvnfmw
題目設計說明:
a)題目的流程:輸入一個字串,如果輸入正確,就會顯示ok。
b)設計思路:
所輸入的字串(稱呼其為A1)在mian函式中做一個異或處理成為字串A2。
在這個程式中有一個全域性字串變數。這個全域性字串(稱呼其為G1)在在全域性物件的解構函式中被 異或成為G2,然後A1和G2進行比較。如果相等,則顯示ok,如果不想等,則什麼都不顯示。
破解思路:找到全域性物件中的解構函式,裡面就有最終的字串比較。
附原始碼圖片(編譯環境2017,debug x86):
第二題《半加器》 解題思路
下列解析文章由 ODPan 原創。
一、定位main函式
1、從start函式一路可到sub_4EA710函式
int sub_4EA710() { sub_48E5B5(); return sub_4EA730(); } 對於main函式入口定位,可以自己寫一個VS2015或2017的程式對比一下就可以很快定位main函式。透過對比上面函式可以重新命名如下: int __usercall sub_4EA710@(int a1@, int a2@, int a3@) { j___security_init_cookie(); return _tmainCRTStartup(a1, a2, a3); }
2、_tmainCRTStartup函式,對部分函式進行了重新命名如下:
signed int __usercall _tmainCRTStartup@(int a1@, int a2@, int a3@) { int v4; // [esp+28h] [ebp-2Ch] int *v5; // [esp+30h] [ebp-24h] _DWORD *v6; // [esp+34h] [ebp-20h] char v7; // [esp+3Ah] [ebp-1Ah] char v8; // [esp+3Bh] [ebp-19h] if ( !j___scrt_initialize_crt(1) ) j___scrt_fastfail(a1, a2, a3, 7); v8 = 0; v7 = j___scrt_acquire_startup_lock(); if ( dword_5F357C == 1 ) { j___scrt_fastfail(a1, a2, a3, 7); } else if ( dword_5F357C ) { v8 = 1; } else { dword_5F357C = 1; if ( j_initterm_e((int)&dword_5B1710, (int)&unk_5B1B34) ) return 255; initterm((int)&unk_5B1000, (int)&unk_5B160C); dword_5F357C = 2; } _scrt_release_startup_lock(v7); v6 = (_DWORD *)sub_48C9BD(); if ( *v6 && j___scrt_is_nonwritable_in_current_image((int)v6) ) ((void (__thiscall *)(_DWORD, _DWORD, signed int, _DWORD))*v6)(*v6, 0, 2, 0); v5 = (int *)sub_48DB6A(); if ( *v5 && j___scrt_is_nonwritable_in_current_image((int)v5) ) register_thread_local_exe_atexit_callback(*v5); v4 = main(); if ( !j___scrt_is_managed_app() ) j_exit_checkSn(v4); if ( !v8 ) j_cexit(); j___scrt_uninitialize_crt(1, 0); return v4; }
從main(sub_48E029)函式可以一路到keyInputAndCheck1(sub_4A19B0)。而此函式使用F5反編譯會失敗,是由於在如下2處程式碼堆疊沒有平衡引起的。
.text:004A1A27 push 0
.text:004A1A29 call sub_48C274
.text:004A1A69 push 0
.text:004A1A6B call sub_48C274
先臨時將 push 0 指令改為 nop指令,就可以F5了。
二、 keyInputAndCheck1函式
int __cdecl keyInputAndCheck1(int argc, const char **argv, const char **envp) { int v3; // xmm0_4 int v4; // edx int v5; // ecx int v7; // [esp+0h] [ebp-D8h] int v8; // [esp+0h] [ebp-D8h] int v9; // [esp+4h] [ebp-D4h] int len; // [esp+D0h] [ebp-8h] sub_48D7B4((int)&unk_5F6007); sub_48CD46(v3, (int)&dword_5F31E0, (int)"Please Input:"); GetInputSn(v3, "%s", g_inputSn, 30, v7, v9); len = strlen(g_inputSn); if ( len <= 30 && len >= 10 ) { strncpy(g_inputSn2, 30, (int)g_inputSn); if ( *(_BYTE *)(g_inputSn2 + 7) != 'A' ) { printf(v3, (int)&g_inputErrString); exitProcess(v8); } inputKeyEor0x1F(v3, (char *)g_inputSn2); } else { printf(v3, (int)&g_inputErrString); exitProcess(v8); } return sub_48D935(v5, v4, 1, 0, v3); }
從彙編程式碼上看就比較明顯了。
1、呼叫GetInputSn函式獲取輸入的sn(g_inputSn),如果SN長度不足30位元組,剩餘用0XFE填充。
2、sn長度在10-30之間,如果不是則輸出"輸入錯誤"退出。
3、呼叫strncpy將g_inputSn複製到g_inputSn2。
4、 判斷g_inputSn2[7]是否等於字元'A',如果不等,則輸出"輸入錯誤"退出。
5、呼叫SNEor0x1F函式。
int __usercall SNEor0x1F_0@(int a1@, char *inputKey) { int v2; // edx int v3; // ecx unsigned int i; // [esp+D0h] [ebp-8h] sub_48D7B4((int)&unk_5F6007); inputKey[7] = 0x23; for ( i = 0; i < strlen(inputKey); ++i ) inputKey[i] ^= 0x1Fu; return sub_48D935(v3, v2, 1, (int)inputKey, a1); }
SNEor0x1F函式將g_inputSn2[7] = 0x23,然後按位元組亦或0x1F。
三、檢視對 g_inputSn2引用
我們發現程式沒有對g_inputSn2做更多的檢查。可以看下還有誰對g_inputSn2進行了訪問,如下:
.data:005F3088 00 00 00 00 g_inputSn2 dd 0 ; DATA XREF: sub_495810+3E↑w .data:005F3088 ; sub_49DC80:loc_49DCEC↑r .data:005F3088 ; keyInputAndCheck1+87↑r .data:005F3088 ; keyInputAndCheck1+9D↑r .data:005F3088
可以看到函式sub_49DC80與sub_495810函式中有引用:
int __userpurge sub_49DC80@(int a1@, char *keyString) { int v2; // edx int v3; // ecx unsigned int i; // [esp+E8h] [ebp-14h] sub_48D7B4((int)&unk_5F6007); if ( keyString ) { for ( i = 0; i < strlen(keyString); ++i ) keyString[i] ^= 0x1Cu; if ( !strcmp((int)keyString, g_inputSn2) ) { outPut(a1, (int)&dword_5F31E0, 'o'); outPut(a1, (int)&dword_5F31E0, 'k'); } } return sub_48D935(v3, v2, 1, 0, a1); }
在sub_49DC80設斷點執行,程式可斷下, 其中keyString引數為“invalid argument"。而程式邏輯就比較明顯了:
1、對 “invalid argument"進行按位元組亦或0x1C,得到“urj}pux<}n{iqyrh”;
2、呼叫strcmp與g_inputSn2比較,相等,則輸出“ok”。
四、獲得flag
對字串“urj}pux<}n{iqyrh”按位元組亦或0x1F,再將第7字串替換為“A”。可得flag。
flag:jmubojgAbqdvnfmw
雖然得到flag,但是執行到sub_ 49DC80路徑並不可知,下面開始分析整個程式的執行流程。
五、透過除錯可知校驗函式 sub_49DC80的執行路徑
1、start->4EAA600->4EA710->_tmainCRTStartup(4EA730)->48E029->54A840->54A420->549F90->549EF0->54A1B0
這裡要注意函式: 54A840,其呼叫 54A420 函式:
void __cdecl 54A840(UINT a1) { sub_54A420(a1, 0, 0); } void __cdecl sub_54A420(UINT uExitCode, char checkFlag, int exitProcessFlag) { _DWORD *v3; // ST08_4 char value_2; // al char v5; // [esp+0h] [ebp-10h] char v6; // [esp+Fh] [ebp-1h] if ( !exitProcessFlag && checkPeFile() ) sub_54A670(uExitCode); v6 = 0; v3 = sub_54A0D0(&v5, (int)&checkFlag, (int)&exitProcessFlag, (int)&v6); value_2 = j_return2(); sub_549F90(value_2, (_DWORD **)v3); if ( v6 ) j___scrt_uninitialize_crt(1, 1); if ( !exitProcessFlag ) ExitProcess_0(uExitCode); }
函式sub_54A420的第二個引數 checkFlag是否對g_inputSn2進行進一步的校驗,以及採用何種校驗方式
checkFlag = 0 ---------->採用 全域性變數5F4078中保護的校驗
checkFlag = 1 ---------->採用 全域性變數5F4088中保護的校驗函式
checkFlag > 1---------->不進行校驗,程式退出。
當在函式 keyInputAndCheck1(4A19B0)中發現輸入長度不符合要求時,其會呼叫如下:
48C274->54A7B0->54A420,而函式54A7B0如下:
void __cdecl sub_54A7B0(UINT a1) { sub_54A420(a1, 2, 0); }
可見輸入的checkFlag為2。實際上就是直接退出了。具體在 54A1B0 函式中可以看清楚。
2、54A1B0函式
DWORD *__thiscall sub_54A1B0(_DWORD **this) { _DWORD *result; // eax void (__thiscall *v2)(_DWORD, _DWORD, _DWORD, _DWORD); // ecx _DWORD **v3; // [esp+18h] [ebp-24h] v3 = this; result = (_DWORD *)(unsigned __int8)byte_5F3AE0; if ( !byte_5F3AE0 ) { _InterlockedExchange((volatile signed __int32 *)&unk_5F3AD8, 1); if ( **this ) { if ( **this == 1 )// chcekflag = 1 時 sub_48B57C((unsigned int)&stru_5F4088); } else // checkflag = 0時 { nop((int)*this); if ( dword_5F3ADC != sub_48CFF8() ) { v2 = (void (__thiscall *)(_DWORD, _DWORD, _DWORD, _DWORD))sub_48ACD5(dword_5F3ADC); v2(v2, 0, 0, 0); } sub_48B57C((unsigned int)&g_funPtr);// 5F4078 } if ( !**v3 ) initterm((int)&unk_5B1C38, (int)&unk_5B1F4C); initterm((int)&unk_5B2050, (int)&unk_5B2154); result = v3[1]; if ( !*result ) { byte_5F3AE0 = 1; *(_BYTE *)v3[2] = 1; } } return result; }
1) 當checkFlag = 0 時:
sub_48B57C((unsigned int)&g_funPtr); // 5F4078
2 ) 當 checkFlag = 1時:
sub_48B57C((unsigned int)&stru_5F4088);
3 ) 其他值函式直接退出:
對於 checkFlag = 1 可能是作者另外的一種check函式,我們可以不管。無論 checkFlag 為0還是為1,區別只是呼叫 sub_48B57C引數不同。
我們繼續分析sub_48B57C((unsigned int)&g_funPtr); // 5F4078
對於 g_funPtr :5F4078實際上是一個結構體,結構體的成員怎樣分析出來我們後面在說。
3、checkFunInfo結構及加解密函式
typedef struct checkFunInfo { int *startAddr; int *endAddr; int *maxAddr; }
其中startAddr指向的是一個malloc的buf,這個buf中儲存的是要執行的函式指標陣列,而 48B57C函式其實就是執行 g_funPtr結構中包含的函式列表。但是這個結構的資料包括全域性buf的起始地址,結束地址以及內容函式指標都是經過加密的,加密演算法與本題題意吻合,實際上就是一個移位的演算法:
如果加密整數 data,加解密演算法如下:
__security_cookie ^(data ror (0x20 - __security_cookie % 0x20u )) --->加密演算法
__security_cookie ^(data ror ( __security_cookie % 0x20u )) ---->解密演算法
實際上加密就是一個數迴圈移位 0x20-X, 解密就是迴圈移位X 這樣的話一個數經過加密和解密後迴圈移位了0x20次,即為其本身。
4、 sub_48B57C函式
該函式經過一些列呼叫最終會呼叫到 563A00
48B57C->563D20->563500->563410->563A00
5、 563A00函式
signed int __thiscall sub_563A00(struct checkFunInfo **this) { int v2; // ecx void (__thiscall *v3)(_DWORD); // ST0C_4 int v4; // ecx int v5; // eax int endAddr1; // [esp+8h] [ebp-3Ch] int startAddr1; // [esp+Ch] [ebp-38h] int ___security_cookie; // [esp+14h] [ebp-30h] int *endAddr; // [esp+1Ch] [ebp-28h] unsigned int startAddr_1; // [esp+20h] [ebp-24h] int ***v11; // [esp+28h] [ebp-1Ch] unsigned int startAddr; // [esp+2Ch] [ebp-18h] int *curAddr; // [esp+30h] [ebp-14h] v11 = (int ***)this; if ( !(*this)->startAddr ) return -1; startAddr = decodeData(*(*this)->startAddr); curAddr = (int *)decodeData((**v11)[1]); if ( !startAddr || startAddr == -1 ) return 0; nop(v2); ___security_cookie = j___security_cookie_get_0(); startAddr_1 = startAddr; endAddr = curAddr; while ( 1 ) { do --curAddr; while ( (unsigned int)curAddr >= startAddr && *curAddr == ___security_cookie ); if ( (unsigned int)curAddr < startAddr ) break; v3 = (void (__thiscall *)(_DWORD))decodeData1(*curAddr); *curAddr = ___security_cookie; v3(v3); // 執行對應的函式指標陣列中的函式 startAddr1 = decodeData(***v11); endAddr1 = decodeData((**v11)[1]); if ( startAddr1 != startAddr_1 || (int *)endAddr1 != endAddr ) { startAddr_1 = startAddr1; startAddr = startAddr1; endAddr = (int *)endAddr1; curAddr = (int *)endAddr1; } } sub_48C567(); if ( startAddr != -1 ) sub_48F0C8(startAddr, 2); nop(v4); v5 = j___security_cookie_get(); ***v11 = v5; (**v11)[1] = v5; (**v11)[2] = v5; return 0; }
在g_funPtr結構對應的函式指標陣列中包含函式5AFCB0,而函式經過一些列呼叫會最終呼叫sub_49DC80執行最終的校驗。其呼叫關係如下:
5AFCB0->48C28D->49CEB0->48DACA->49DC80,在函式sub_49CEB0中存在內部key 'invalid argument'。
int __usercall sub_49CEB0@(int a1@) { int v1; // eax int v2; // edx int v4; // [esp+0h] [ebp-E8h] sub_48D7B4((int)&unk_5F6007); v1 = sub_48DACA(a1, (int)aInvalidArgumen_1);// 'invalid argument' return sub_48D935(v4, v2, 1, v1, a1); }
6、 sub_49DC80校驗函式呼叫路徑
從上面分析可知49DC80呼叫流程如下:
start->4EAA600->4EA710->_tmainCRTStartup(4EA730)->48E029->54A840->54A420->549F90->549EF0->54A1B0->48B57C->563D20->563500->563410->563A00->5AFCB0->48C28D->49CEB0->48DACA->49DC80
那麼這裡面的關鍵就是g_funPtr結構的賦值在哪裡實現的呢。下面就分析g_funPtr結構的賦值。
六、 g_funPtr結構賦值
1、 g_funPtr結構初始化流程
_tmainCRTStartup(4EA730)->48BD42->4E9DE0->48E696->564150 char sub_564150() { return sub_48B7D9((int)&off_5D2378, (int)&unk_5D23F8); }
sub_48B7D9函式就是執行off_5D2378與off_5D23F8之間的函式。
10 3E 56 00 off_5D2378 dd offset sub_563E10 ; DATA XREF: sub_564150+A↑o .rdata:005D2378 ; sub_5641B0+A↑o .rdata:005D237C 00 00 00 00 align 10h .rdata:005D2380 C0 3E 56 00 dd offset sub_563EC0 .rdata:005D2384 00 00 00 00 align 8 .rdata:005D2388 FA A5 48 00 dd offset sub_48A5FA .rdata:005D238C C5 DF 48 00 dd offset sub_48DFC5 .rdata:005D2390 A0 3E 56 00 dd offset sub_563EA0 .rdata:005D2394 B0 3E 56 00 dd offset sub_563EB0 .rdata:005D2398 01 C7 48 00 dd offset sub_48C701 .rdata:005D239C 6C C0 48 00 dd offset sub_48C06C .rdata:005D23A0 1C F7 48 00 dd offset sub_48F71C .rdata:005D23A4 6F BD 48 00 dd offset sub_48BD6F .rdata:005D23A8 00 00 00 00 dd 0 .rdata:005D23AC 40 3F 56 00 dd offset sub_563F40 .rdata:005D23B0 0E FB 48 00 dd offset sub_48FB0E .rdata:005D23B4 7B F7 48 00 dd offset sub_48F77B .rdata:005D23B8 BB B7 48 00 dd offset sub_48B7BB .rdata:005D23BC 14 BE 48 00 dd offset sub_48BE14 .rdata:005D23C0 56 E0 48 00 dd offset sub_48E056 .rdata:005D23C4 CB C0 48 00 dd offset sub_48C0CB .rdata:005D23C8 14 C3 48 00 dd offset sub_48C314 .rdata:005D23CC 00 00 00 00 dd 0 .rdata:005D23D0 00 00 00 00 dd 0 .rdata:005D23D4 20 40 56 00 dd offset sub_564020 .rdata:005D23D8 00 00 00 00 dd 0 .rdata:005D23DC 90 3F 56 00 dd offset sub_563F90 .rdata:005D23E0 00 00 00 00 dd 0 .rdata:005D23E4 60 3F 56 00 dd offset sub_563F60 .rdata:005D23E8 70 3E 56 00 dd offset sub_563E70 .rdata:005D23EC 80 3E 56 00 dd offset sub_563E80 .rdata:005D23F0 30 3E 56 00 dd offset allCheckFunStruct_ini .rdata:005D23F4 60 3E 56 00 dd offset sub_563E60 上述函式指標陣列中的最有一個函式563E30(allCheckFunStruct_ini)為初始化g_funPtr: char allCheckFunStruct_ini() { checkFunStruct_ini_0(&g_funPtr); checkFunStruct_ini_0(&stru_5F4088); return 1; }
其呼叫關係為:
9A3E30->8CB9CD->9A3D50(checkFunStruct_ini) int __cdecl checkFunStruct_ini(struct checkFunInfo *a1) { int *__security_cookie; // eax if ( !a1 ) return -1; if ( a1->startAddr == a1->maxAddr ) { nop((int)a1); __security_cookie = (int *)j___security_cookie_get(); a1->startAddr = __security_cookie; a1->endAddr = __security_cookie; a1->maxAddr = __security_cookie; } return 0; }
實際上就是將 0 賦給g_funPtr,經加密後變為__security_cookie
因此g_funPtr 結構初始化呼叫流程為:
_tmainCRTStartup(4EA730)->48BD42->4E9DE0->48E696->564150-> 48B7D9-> 9A3E30->8CB9CD->9A3D50(checkFunStruct_ini)
2、 g_funPtr結構賦值之函式指標BUF申請
在函式_tmainCRTStartup中,會存在如下2個呼叫:
if ( j_initterm_e((int)&dword_5B1710, (int)&unk_5B1B34) ) return 255; initterm((int)&unk_5B1000, (int)&unk_5B160C); j_initterm_e 與initterm實際上就是執行初始化函式 _tmainCRTStartup(4EA730)-8CA979(j_initterm_e)->9A4920->92A600->48D854->4EA250->48DAE8->4EA160->48A361->563D00->48E80D->563DD0->5634C0->563360->563710 signed int __thiscall sub_563710(struct bufInfo *this) { int *mallocSaveCheckSnBuf; // eax int v3; // eax int v4; // eax int v5; // eax int v6; // eax int v7; // eax int __security_cookie; // [esp+0h] [ebp-40h] char v9; // [esp+4h] [ebp-3Ch] _DWORD *v10; // [esp+8h] [ebp-38h] char v11; // [esp+Ch] [ebp-34h] _DWORD *v12; // [esp+10h] [ebp-30h] unsigned int funCnt2; // [esp+14h] [ebp-2Ch] unsigned int funCnt1; // [esp+18h] [ebp-28h] int *i; // [esp+1Ch] [ebp-24h] int *maxAddr; // [esp+20h] [ebp-20h] int startAddr; // [esp+24h] [ebp-1Ch] unsigned int funCnt; // [esp+28h] [ebp-18h] int *endAddr; // [esp+2Ch] [ebp-14h] int mallocSaveCheckSnBuf1; // [esp+30h] [ebp-10h] unsigned int mallocCnt; // [esp+34h] [ebp-Ch] struct bufInfo *v22; // [esp+38h] [ebp-8h] char v23; // [esp+3Fh] [ebp-1h] v22 = this; if ( !*this->strCheckFunInfo ) return -1; startAddr = decodeData((int)(*v22->strCheckFunInfo)->startAddr); endAddr = (int *)decodeData((int)(*v22->strCheckFunInfo)->endAddr); maxAddr = (int *)decodeData((int)(*v22->strCheckFunInfo)->maxAddr); if ( endAddr == maxAddr ) { funCnt = ((signed int)maxAddr - startAddr) >> 2; if ( funCnt <= 0x200 ) funCnt1 = funCnt; else funCnt1 = 512; funCnt2 = funCnt1; mallocCnt = funCnt1 + funCnt; if ( !(funCnt1 + funCnt) ) mallocCnt = 32; mallocSaveCheckSnBuf1 = 0; if ( mallocCnt >= funCnt ) { mallocSaveCheckSnBuf = (int *)sub_48B18F( // malloc startAddr, mallocCnt, 4, 2, (int)"minkernel\\crts\\ucrt\\src\\appcrt\\startup\\onexit.cpp", 'p'); v12 = sub_48AFC8(&v11, (int)mallocSaveCheckSnBuf); mallocSaveCheckSnBuf1 = sub_48AA1E((int)v12); sub_48C4BD((int)&v11); } if ( !mallocSaveCheckSnBuf1 ) { mallocCnt = funCnt + 4; v3 = sub_48B18F(startAddr, funCnt + 4, 4, 2, (int)"minkernel\\crts\\ucrt\\src\\appcrt\\startup\\onexit.cpp", 'w'); v10 = sub_48AFC8(&v9, v3); mallocSaveCheckSnBuf1 = sub_48AA1E((int)v10); sub_48C4BD((int)&v9); } if ( !mallocSaveCheckSnBuf1 ) return -1; startAddr = mallocSaveCheckSnBuf1; endAddr = (int *)(mallocSaveCheckSnBuf1 + 4 * funCnt); maxAddr = (int *)(mallocSaveCheckSnBuf1 + 4 * mallocCnt); v23 = nop(mallocSaveCheckSnBuf1 + 4 * mallocCnt); __security_cookie = j___security_cookie_get_0(); for ( i = endAddr; i != maxAddr; ++i ) *i = __security_cookie; } v4 = encodeData((int)*v22->checkFunPtr); *endAddr = v4; ++endAddr; v5 = j_EncodeData1(startAddr); (*v22->strCheckFunInfo)->startAddr = (int *)v5; v6 = j_EncodeData1((int)endAddr); (*v22->strCheckFunInfo)->endAddr = (int *)v6; v7 = j_EncodeData1((int)maxAddr); (*v22->strCheckFunInfo)->maxAddr = (int *)v7; return 0; }
函式是將一個函式插入到函式指標列表中,如果沒有申請函式指標列表空間則先申請,首先申請的大小是32*4 ,申請完後就將相應的的函式指標加密儲存。
其中bufInfo結構如下:
typedef struct bufInfo { checkFunInfo **pcheckFunInfo; int ** checkFunPtr; }
其中checkFunPtr 為加入到pCheckFunInfo的函式指標。
3、將 5AFCB0函式寫入到 g_funPtr中
在函式中_tmainCRTStartup存在如下呼叫。
initterm((int)&unk_5B1000, (int)&unk_5B160C);就是執行5B1000與5B160C之間的函式。而在5B1000與5B160C之間存在如下:
.rdata:005B14F8 10 58 49 00 dd offset sub_495810
.rdata:005B14FC B0 57 49 00 dd offset sub_4957B0
而函式sub_4957B0如下:
int __usercall sub_4957B0@(int a1@) { int v1; // eax int v2; // edx int v3; // ecx sub_48D7B4((int)&unk_5F6007); v1 = sub_48D854((int)sub_5AFCB0); // 將check函式sub_5AFCB0插入到函式指標陣列中 return sub_48D935(v3, v2, 1, v1, a1); }
其呼叫函式48D854將check函式sub_5AFCB0插入到函式指標陣列中。
七、總結
1、初始化g_funPtr;
2、將真正的校驗函式sub_4957B0加密後插入到 g_funPtr結構中;
3、獲取使用者輸入;
4、判斷輸入長度是否為10與30之間;
5、如果不是則呼叫sub_54A420函式,並將其引數checkflag設定為2,使其不執行校驗函式,程式直接結束;
6、如果輸入的第7個字元不等於字元''A , 則呼叫sub_54A420函式,並將其引數checkflag設定為2,使其不執行校驗函式,程式直接結束;
7、將輸入的第7個字元設定為0x23;
8、呼叫 呼叫sub_54A420函式,並將其引數checkflag設定為0,經過一系列呼叫,最終會呼叫g_funPtr中設定的函式sub_5AFCB0。
9、函式sub_5AFCB0經過一系列呼叫最終會呼叫校驗函式sub_49DC80,執行校驗;
10、此時就回到我們開頭分析的位置了。
合作伙伴
騰訊安全應急響應中心
TSRC,騰訊安全的先頭兵,肩負騰訊公司安全漏洞、駭客入侵的發現和處理工作。這是個沒有硝煙的戰場,我們與兩萬多名安全專家並肩而行,捍衛全球億萬使用者的資訊、財產安全。一直以來,我們懷揣感恩之心,努力構建開放的TSRC交流平臺,回饋安全社群。未來,我們將繼續攜手安全行業精英,探索網際網路安全新方向,建設網際網路生態安全,共鑄“網際網路+”新時代。
轉載請註明:轉自看雪學院
看雪CTF.TSRC 2018 團隊賽 解題思路彙總:
相關文章
- 看雪CTF.TSRC 2018 團隊賽 第七題 『魔法森林』 解題思路2018-12-23
- 看雪CTF.TSRC 2018 團隊賽 第九題『諜戰』 解題思路2018-12-19
- 看雪CTF.TSRC 2018 團隊賽 第一題 『初世紀』 解題思路2018-12-23
- 看雪CTF.TSRC 2018 團隊賽 第五題 『交響曲』 解題思路2018-12-23
- 看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路2018-12-23
- 看雪CTF.TSRC 2018 團隊賽 第八題 『二向箔』 解題思路2018-12-23
- 看雪CTF.TSRC 2018 團隊賽 第十題『俠義雙雄』 解題思路2018-12-21
- 看雪CTF.TSRC 2018 團隊賽 第三題 『七十二疑冢』 解題思路2018-12-23
- 看雪CTF.TSRC 2018 團隊賽 第四題 『盜夢空間』 解題思路2018-12-23
- 看雪CTF.TSRC 2018 團隊賽 第十一題『伊甸園』 解題思路2018-12-23
- 看雪CTF.TSRC 2018 團隊賽 第十三題『 機器人歷險記』 解題思路2018-12-27機器人
- 看雪CTF.TSRC 2018 團隊賽 第十五題『 密碼風雲』 解題思路2019-01-02密碼
- 看雪CTF.TSRC 2018 團隊賽 第十四題『 你眼中的世界』 解題思路2018-12-29
- 看雪CTF.TSRC 2018 團隊賽 第十二題『移動迷宮』 解題思路2018-12-25
- 看雪CTF.TSRC 2018 團隊賽 獲獎名單公示2019-01-02
- 看雪·深信服 2021 KCTF 春季賽 | 第二題設計思路及解析2021-05-12
- 看雪.騰訊TSRC 2017 CTF 秋季賽 第二題點評及解析思路2017-10-28
- 看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路2019-07-01
- 看雪.萬能鑰匙 CTF 2017第一題 WannaLOL 解題思路2017-06-29
- 看雪·深信服 2021 KCTF 春季賽 | 第七題設計思路及解析2021-05-25
- 看雪·深信服 2021 KCTF 春季賽 | 第八題設計思路及解析2021-05-25
- 看雪·深信服 2021 KCTF 春季賽 | 第九題設計思路及解析2021-05-28
- 看雪·深信服 2021 KCTF 春季賽 | 第四題設計思路及解析2021-05-17
- 看雪·深信服 2021 KCTF 春季賽 | 第五題設計思路及解析2021-05-17
- 看雪·深信服 2021 KCTF 春季賽 | 第六題設計思路及解析2021-05-21
- 看雪·深信服 2021 KCTF 春季賽 | 第三題設計思路及解析2021-05-14
- 看雪·深信服 2021 KCTF 春季賽 | 第十題設計思路及解析2021-05-31
- 看雪·眾安 2021 KCTF 秋季賽 | 第十題設計思路及解析2021-12-16
- 看雪·眾安 2021 KCTF 秋季賽 | 第九題設計思路及解析2021-12-09
- 看雪·眾安 2021 KCTF 秋季賽 | 第七題設計思路及解析2021-12-03
- 看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析2021-11-29
- 看雪·眾安 2021 KCTF 秋季賽 | 第三題設計思路及解析2021-11-22
- 看雪·眾安 2021 KCTF 秋季賽 | 第六題設計思路及解析2021-12-01
- 看雪·眾安 2021 KCTF 秋季賽 | 第四題設計思路及解析2021-11-25
- 看雪.騰訊TSRC 2017 CTF 秋季賽 第三題點評及解析思路2017-10-30
- 看雪.騰訊TSRC 2017 CTF 秋季賽 第七題點評及解析思路2017-11-07
- 看雪.騰訊TSRC 2017 CTF 秋季賽 第八題點評及解析思路2017-11-09
- 看雪.騰訊TSRC 2017 CTF 秋季賽 第五題點評及解析思路2017-11-03