首页
论坛
专栏
课程

[原创]CTF2018第十二题分析(qwertyaa)

qwertyaa
10
2018-7-8 20:56 341

这题相对前面几题简单不少,做题完全靠猜...

定位main函数

这是一个64位的程序,所以我们直接用IDA64分析。启动一遍程序可知:该程序的密钥由argv[1]给出。于是定位GetCommandLineA/W,跟几步就可以到达main函数。

分析程序

这个程序将命令行输入复制到一个全局变量处(记作key),接下来调用一个函数判断key是否正确。

 

由于这个判断程序有点花,我把程序F5结果复制下来,主要看出现key处及其前后几行的逻辑。

 

其中,经常出现两个magic number(0xCBF29CE484222325i64和0x100000001B3i64),查询百度可知这里在计算fnvHash(这就是vc-x64标准库中std::hash的算法),计算函数如下:

size_t std::_Hash_bytes(const unsigned char*data, size_t size) {
    size_t hash=0xCBF29CE484222325ll;
    for(size_t i=0ll;i<size;++i)hash=0x100000001B3ll*(hash^data[i]);
    return hash;
}

几经分析,可猜测这个key主要满足以下条件(fnvStr表示字符串的fnvHashfnvChr表示由传入的单一字符组成的字符串的fnvHash):

  1. 长度恰为30
  2. 至少三处fnvChr(key[i])==0xAF63B44C8601A894ll
  3. fnvChr(key[0])fnvChr(key[8])均已给出
  4. 字符串由{prefix}{possibleSuffixA}9{dllName}{possibleSuffixB}9{symBolName}9{possibleSuffixC}组成,{prefix}可由第3点倒推,{dllName}长度恰为5字节,程序最后将会调用位于{dllName}.DLL内的名为{symBolName}的导出函数,且传入参数为(0,30)。
  5. fnvStr(key) == 5728707748789076223ll

猜+暴力得出key

前9字节的hash都已给出,所以我们枚举所有字符并将它们的hash值与给出hash比对就可以得出这一部分的正确值。暴力程序如下(defs.h位于ida的plugins目录下,这个程序顺便计算出条件2所需的字符为9):

#include <stdio.h>
#include "defs.h"
signed __int64 keys[]={-5808510693665524758i64,
-5808494200991101593i64,
-5808519489758550446i64,
-5808507395130640125i64,
-5808522788293435079i64,
-5808606351177179115i64,
0xAF63AD4C86019CAFi64,
0xAF63AC4C86019AFCi64,
0xAF63B54C8601AA47i64,
0xAF63B44C8601A894i64,0};
signed __int64 fnvHash(char *a1)
{
  char v1; // dl
  signed __int64 result; // rax
  signed __int64 v3; // rax

  v1 = *a1;
  for ( result = 0xCBF29CE484222325i64; *a1; result = 0x100000001B3i64 * v3 )
  {
    ++a1;
    v3 = result ^ v1;
    v1 = *a1;
  }
  return result;
}
char ans[100]={0};
int main(){
    unsigned __int64 v183;
    LOWORD(v183) = 0;

    for(char x=1;x<127;x++){
        LOBYTE(v183) = x;
        signed __int64 hash=fnvHash(&v183);
        for(int i=0;keys[i];i++){
            if(keys[i]==hash){
                if(ans[i]==0)ans[i]=x;
                else printf("dup");
            }
        }
    }
    puts(ans);
}

运行可知前9字节为KXCTF2018

 

接下来这个key的剩余部分除了还有三个9外似乎只能猜出来。

 

首先,出于简单考虑,我们不妨假设所有{possibleSuffix*}为空。(最后事实也如此)

 

system32目录下一番摸索后我找到一些相对可疑的dll:

imm32.dll
mmres.dll
msctf.dll
ole32.dll
psapi.dll
ntdll.dll

 

显然ntdll.dll是其中最可疑的,我把它的所有导出函数都存到一个*.h文件中。格式如下:

const char symbol[][0x100]={
"RtlDispatchAPC",
"RtlActivateActivationContextUnsafeFast",
//*** ***
"wcstol",
"wcstombs",
"wcstoul",
NULL};

需要注意的是Win下的文件系统是大小写不敏感的,结合后面补的.DLL可以猜测前面应该是全大写的NTDLL

 

所以我们可以写一个暴力程序(其中{symBolName}的长度即30(总长)-9({prefix})-3(分隔用的9)-5({dllName})=13),如下:

#include <stdio.h>
#include <string.h>
#include "symbols.h"
char test[0x100]={0};
signed __int64 fnvHash(char *a1)
{
  char v1; // dl
  signed __int64 result; // rax
  signed __int64 v3; // rax

  v1 = *a1;
  for ( result = 0xCBF29CE484222325i64; *a1; result = 0x100000001B3i64 * v3 )
  {
    ++a1;
    v3 = result ^ v1;
    v1 = *a1;
  }
  return result;
}
int main(){
    for(int i=0;symbol[i][0];i++)if(strlen(symbol[i])==13){
        strcpy(test,"KXCTF20189");
        strcat(test,"NTDLL9");
        strcat(test,symbol[i]);
        strcat(test,"9");
        if(5728707748789076223i64==fnvHash(test))printf("Key: %s\n",test,symbol[i]);
    }
}

得到keyKXCTF20189NTDLL9DbgUiContinue9,经验证正确。



[防守篇]2018看雪.TSRC CTF 挑战赛(团队赛)11月1日征题开启!

最后于 2018-7-8 21:08 被qwertyaa编辑 ,原因: 校对
最新回复 (0)
返回