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

第三題【七十二疑冢】正在火熱進行中

第3題/共15題

《七十二疑冢》於今天(12月5日)中午12:00開啟,將於12月7日中午12:00結束

趕緊參與進來吧~!

合作夥伴

騰訊安全應急響應中心

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

weixin.qq.com/r/H3Vudjv (二維碼自動識別)

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


推薦閱讀:
相關文章