看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路

Editor發表於2018-12-23

看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路


2018年12月3日12:00,『看雪CTF.TSRC 2018 團隊賽』之攻擊篇第二題拉開了序幕。


pizzatql戰隊憑藉1993s的成績,成為首位拿下第二題《半加器》的戰隊。


看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路


激烈的戰勢愈加激烈!截止今天(12月5日中午12:00)第二題攻擊已經關閉。


接下來我們一起來看看本次比賽的最新進展吧!



最新賽況戰況一覽


CTF第二題《半加器》由 防守方 吃瓜小群眾 之隊出題,截止比賽結束已被67個團隊攻破。


看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路


本題過後,攻擊團隊率先領先的Top10團隊為:


看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路


細心的朋友會發現,二殺結束後的Top10和一殺後排名完全一致。


那麼,隨著新題目的解鎖,是否會誕生此次大賽的黑馬呢?


這個結果,我們說了不算,你說了算!


加油吧,勇士們~!



第二題《半加器》 設計思路


下列設計思路由 zengYx 原創。


看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路


團隊名稱:吃瓜小群眾

團長QQ:839667825

參賽題目:CrackMe

題目答案:jmubojgAbqdvnfmw



題目設計說明:


a)題目的流程:輸入一個字串,如果輸入正確,就會顯示ok。


看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路


b)設計思路:


所輸入的字串(稱呼其為A1)在mian函式中做一個異或處理成為字串A2。


在這個程式中有一個全域性字串變數。這個全域性字串(稱呼其為G1)在在全域性物件的解構函式中被 異或成為G2,然後A1和G2進行比較。如果相等,則顯示ok,如果不想等,則什麼都不顯示。


破解思路:找到全域性物件中的解構函式,裡面就有最終的字串比較。


附原始碼圖片(編譯環境2017,debug x86):


看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路


看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路


看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路



第二題《半加器》 解題思路


下列解析文章由 ODPan 原創。


看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路


一、定位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、此時就回到我們開頭分析的位置了。



合作伙伴

看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路


騰訊安全應急響應中心 


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

看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路

看雪CTF.TSRC 2018 團隊賽 第二題 『半加器』 解題思路


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



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












相關文章