看雪2018CTF第12题解题记录

2019-05-16  本文已影响0人  静析机言

由于地址随机化,定位代码比较困难,首先借助于LordPE将其动态加载基址的特性去除,在IDA pro中指定segment的基址。

运行时,提示需要输入命令行参数。

通常,依靠getcommandline函数获取命令行参数,在IDA pro查找getcommandline的引用,定位到sub_140013330。此CM为64位程序,使用x64dbg动态调试,一步步跟踪发现这样的调用关系sub_1400061A0 --> sub_140002E00 --> sub_140001340。在执行sub_140001340时才会打印”registration failed”提示。

观察sub_1400061A0,发现程序执行if这个分支,如果执行else分支则直接打印”input like this:crackme mykey”。a1就是main程序的argc参数。

执行sub_14000E55C后,会将紧跟在crackeme.exe后面的key拷贝至140039300

跟进sub_140002E00,查找140039300的引用即可找到分析的关键所在

定位到140039300的第1处引用,发现140003458处的r8=key的长度,前面的复杂代码都是些垃圾代码,专门用来混淆视听的。要求key的长度为30

定位到140039300的第2处引用,发现程序会对key中的每1个字符做hash操作,它的处理流程与后续将要说明的sub_140002AC0类同。

要求字符经hash处理过的数值==0xAF63B44C8601A894。运行下述破解代码,即可发现为字符’9’

signed __int64 check3more = 0xAF63B44C8601A894;

for (size_t j = 0; j < 256; j++)

{

         if(sub_140002AC0((char*)(&j)) == check3more)

         {

                   printf("\n%c",(char)j);

         }

}

140003525处的代码要来判断key中是否有3个以上的’9’

定位到140039300的第3处引用,程序将对key的前9个字符进行sub_140002AC0哈希运算,要求它们分别与对应数值相等

运行下述破解代码,得出key的前9个字符为KXCTF2018

signed __int64 arr[9] = { 0xAF64064C860233EA,

         0xAF64154C86024D67,

         0xAF63FE4C86022652,

         0xAF64094C86023903,

         0xAF63FB4C86022139,

         0xAF63AF4C8601A015,

         0xAF63AD4C86019CAF,

         0xAF63AC4C86019AFC,

         0xAF63B54C8601AA47 };

signed __int64 check3more = 0xAF63B44C8601A894;

//crack first 9 chars

for (size_t i = 0; i < 9; i++)

{

         for (size_t j = 0; j <256; j++)

         {

                   if(sub_140002AC0((char*)(&j)) == arr[i])

                   {

                            printf("%c",(char)j);

                   }

         }

}

定位到140039300的第4处引用:要求key的所有字符经hash处理后,数值==0x4F8075587499C0FF

key总长度为30,目前仅知道9位,剩下的21位没法枚举,暂且跳过,继续看140039300的第5处引用。

14000502B处将第3个’9’所在位置的数值设为’\x0’

key格式为KXCTF20189xxxxx9xxxxxxxxxxxxx9,当然了9可以在10-30中的任意一个位置。

sub_14000BDD8返回以第一个9开头的字符串,而140004FF2处的”.DLL”常量表明140005003处的sub_14000E46C极有可能为字符拼接函数。如果该猜测正确的话,则140004FBA- 140004FE6,用来取出5个字符,与”.DLL”进行拼接。因此,进一步明确key格式为

KXCTF20189[DllName]9 xxxxxxxxxxxxx9

定位140039300的第6处引用,往前翻,发现2个关键比较,其中一个为

动态调试,此处的代码逻辑为:先后在ntdll.dll、kernel32.dll的导出函数名称作hash运算,要求数值==0x53B2070F。此处hash运算与sub_140002AC0类同,只不过换了相关的常量而已。

最后在kernel32.dll中找到,r8d=0x340(找到后r8d没有加1,实际应为0x341),得出导出函数为LoadLibraryA

14000548B调用LoadLibraryA来加载key中指定的dll

接着来到第2个关键比较

动态调试,代码逻辑为:先后在ntdll.dll、kernel32.dll的导出函数名称作hash运算,要求数值==0xF8F45725。最后在kernel32.dll中找到,r8d=0x24B(少加了1,实际应为0x24C),得出导出函数为GetProcAddress

14000554D处执行GetProcAddress,获取dll中某个导出函数的地址。

动态调试,发现导出函数名称为key中第2个9之后的字符串,很有可能key的格式为:

KXCTF20189[DllName]9[ProcName]9。

当然了,这个仅为key的一种格式,第3个9也有可能往前移,并不在最后一位。我们仅猜测上述为最有可能的排列方式。

前面的sub_14000E55C用来获取ProcName。

假设

hModule = LoadLibraryA(DllName);

GetProcAddress(hModule, ProcName);

运行成功,call r8即为执行导出函数,此导出函数有2个参数。

根据导出函数的返回结果来执行2个分支中的一个。也就是说,依赖于导出函数的返回值来判断key是否正确。

上图中的140038058、140038070等存储了字符常量,后续操作用来解密这些字符的

至此,程序的逻辑已弄清楚了

1.key长度为30

2.key前9个要为"KXCTF2018"

3.key整个HASH为0x4F8075587499C0FF

4.key从10位开始到结束有三个9,将这部分分成两段,前一段长5个字符,为DLL文件的名字,后一段为Proc名字

5.Proc调用参数为0-2个,如有参数,参数1=0,参数2=30

6.DLL文件应该是WINDOWS系统自带的,而且每个版本都应该有,要不就会注册不成功.

proc函数参数和返回值先不考虑,相信HASH相同的没有吧? DLL由于考虑WINDOWS自带的,且名字长度为5,猜是ntdll, 当然如果不是就比较麻烦,要遍历WINDOWS\SYSTEM32下DLL文件, 还要遍历每个DLL的导出函数名

假设为ntdll,如果key格式为KXCTF20189[DllName]9[ProcName]9,则ProcName长度为13。查找出ntdll中所有长度为13的导出函数共有75个

_local_unwind

_vsnwprintf_s

_wsplitpath_s

DbgBreakPoint

DbgUiContinue

EtwEventWrite

LdrResRelease

NtAccessCheck

NtAlertThread

NtCancelTimer

NtCompactKeys

NtCompressKey

NtConnectPort

NtCreateEvent

NtCreateTimer

NtCreateToken

NtFilterToken

NtOpenProcess

NtOpenSection

NtOpenSession

NtQueryEaFile

NtQueryMutant

NtQueryObject

NtRequestPort

NtSetUuidSeed

NtSetValueKey

NtStopProfile

NtUnloadKeyEx

PfxFindPrefix

PfxInitialize

RtlAbortRXact

RtlApplyRXact

RtlAreBitsSet

RtlCopyMemory

RtlCopyString

RtlCreateHeap

RtlFillMemory

RtlFreeHandle

RtlGetVersion

RtlIdnToAscii

RtlInitString

RtlLoadString

RtlMoveMemory

RtlpNtOpenKey

RtlRemoteCall

RtlSetAllBits

RtlStartRXact

RtlUnlockHeap

RtlZeroMemory

TpReleasePool

TpReleaseWait

TpReleaseWork

TpWaitForWait

TpWaitForWork

ZwAccessCheck

ZwAlertThread

ZwCancelTimer

ZwCompactKeys

ZwCompressKey

ZwConnectPort

ZwCreateEvent

ZwCreateTimer

ZwCreateToken

ZwFilterToken

ZwOpenProcess

ZwOpenSection

ZwOpenSession

ZwQueryEaFile

ZwQueryMutant

ZwQueryObject

ZwRequestPort

ZwSetUuidSeed

ZwSetValueKey

ZwStopProfile

ZwUnloadKeyEx

然后运行暴力破解脚本了

const char* funcs[75] =

{"_local_unwind","_vsnwprintf_s","_wsplitpath_s","DbgBreakPoint",......,"ZwUnloadKeyEx" };

char flag[31] = "KXCTF20189NTDLL9XXXXXXXXXXXXX9";

for (size_t i = 0; i < 75; i++)

{

         memcpy(flag + 16,funcs[i], 13);

         if (sub_140002AC0(flag)== 0x4F8075587499C0FF)

         {

                   printf("\n%s",flag);

         }

}

得出key= KXCTF20189NTDLL9DbgUiContinue9

:此题有个坑:dll名字必须为大写。由于windows对大小写不敏感,在调用dll时,LoadLibrayA都能返回正确值。但key中含小写dll名的hash值与key中含大写dll名的hash值不一样。如果dll为小写字符,最后算出的hash值不等于0x4F8075587499C0FF。

上一篇下一篇

猜你喜欢

热点阅读