美文网首页CTF Re&&Pwn
【CTF-RE】Shiyanbar

【CTF-RE】Shiyanbar

作者: Kirin_say | 来源:发表于2018-05-01 20:04 被阅读43次

    0x01 bin100(ebCTF 2013)

    运行可执行文件
    是一个掷筛子游戏
    需要我们每一步掷出特定数字
    然后得出flag
    记住关键字


    关键字

    载入IDA,查找字符串:
    找到关键函数:WinMain
    大致看一下流程,发现到最后:

    v55 = std::operator<<<std::char_traits<char>>(&std::cout, "[*] You rolled 3-1-3-3-7, what does that make you? ELEET! \\o/");
    std::ostream::operator<<(v55, std::endl<char,std::char_traits<char>>);
    v56 = std::operator<<<std::char_traits<char>>(&std::cout, "[*] Nice job, here is the flag: ");
    v57 =std::operator<<<char,std::char_traits<char>,std::allocator<char>>(v56, &v84);
    

    我们看到最后输出字符串v84
    Alt+T查找v84
    Ctrl+T查看下一个v84
    找到两处关键:

    std::string::operator=(&v84, &unk_444240);
    
    i = 0;
      for ( j = 0; ; ++j )
      {
     fctx.call_site = 1;
      v52 = std::string::size((std::string *)&v84);
       if ( j >= v52 )
     break;
     v64 = (_BYTE *)std::string::operator[](&v84, j);
     v53 = (_BYTE *)std::string::operator[](&v87, i);
      *v64 ^= *v53;
      ++i;
     v54 = std::string::length((std::string *)&v87);
     if ( i >= v54 )
       i = 0;
         }
     fctx.call_site = 1;
     if ( std::string::find((std::string *)&v84, "ebCTF", 0) == -1 )
     {
     fctx.call_site = 1;
     v59 = std::ostream::operator<<(&std::cout, std::endl<char,std::char_traits<char>>);
     v60 = std::operator<<<std::char_traits<char>>( v59, "[!] It seems you did something wrong :( No flag for you.");
    

    可以看出:

    • &unk_444240处的数据以字符串形式赋值给v84
    • 在满足每一次掷数字的条件后,会将v84与v87的每一位进行异或运算,且当v87位数不足时,v84再与v87第一位运算(可以看成v87头尾相接的循环)

    下面查找v87:
    发现几个关键点:

    std::string::operator=(&v87, &unk_444309);
    
     for ( i = 0; ; ++i )
    {
     fctx.call_site = 1;
    v36 = std::string::size((std::string *)&v87);
    if ( i >= v36 )
    break;
     v37 = (_BYTE *)std::string::operator[](&v87, i);
     *v37 ^= v86;
     }
    
    for ( i = 0; ; ++i )
    {
    fctx.call_site = 1;
     v50 = std::string::size((std::string *)&v87);
     if ( i >= v50 )
     break;
    v51 = (_BYTE *)std::string::operator[](&v87, i);
    *v51 ^= v85;
     }
    

    可以看出:

    • &unk_444309处的数据以字符串形式赋值给v87
    • v87的每一位先与v86异或,再与v85异或

    下面查找v86和v85:
    v86:

     if (isDebuggerPresent() )
        v86 = 66;
      else
        v86 = 16;
    

    这里调用 isDebuggerPresent() 检测是否在调试环境下运行:
    显然:
    v86=16
    v85则是每一步掷数字成功都进行判断,仔细跟进:

    v85 = 6
    v85 *= 2
    v85 += 4
    v85 *= 3
    v85 += 2
    v85 *= 2;
    v85 *= 50;
    v85 /= 50;
    v85 += 65;
    v85 -= 65;
    v85 *= 42;
    v85 /= 42;
    

    得到:
    v85=100
    而后:
    Shift+E提取&unk_444240和&unk_444309处的数据
    注意:字符串以00结尾,在脚本破解的时候注意把提取的00删去(或者直接不提取)
    提取后脚本破解:

    key1="132138153D3357472D276A73440526595C79174445771A75497D054A78746A70420271050F2208"
    key2="02370F350F3C15073C302A30551237151E350151"
    key3=16
    key4=100
    flag=""
    for i in range(0,len(key1),2):
        ans1=int(key1[i:i+2],16)
        j=i%len(key2)
        ans2=int(key2[j:j+2],16)
        flag+=chr(ans2^key3^key4^ans1)
    print flag
    

    有几点注意:

    • 这里每一步都调用了time(),以防掷数字中间设置断点,从而防止调试:

    OD载入我们看到这里:


    time

    这里调用了time类函数
    我们可以进入此地址:

    00412690    - FF25 F4C24400 jmp dword ptr ds:[<&msvcrt.time>]        ;  msvcrt.time
    

    可以看出这里是函数列表,所有call指令都先对应到这个列表,再进行对应跳转,我们将此处改为retn,便会使其不能调用time类的函数
    不过有一个问题,从ida也可以看到,其会比较两次time之差:

    v16 = time(0);
    v79 = v16;
    v81 = v16 - v80;
    if ( v16 - v80 > 2 )
     v85 *= 2;
    

    如果不调用time,那么比较的就会是默认值
    我们调试一下,定位到这里:

    .text:00401AF8 call    _time
    .text:00401AFD mov     [ebp+var_68], eax
    .text:00401B00 mov     edx, [ebp+var_64]
    .text:00401B03 mov     eax, [ebp+var_68]
    .text:00401B06 sub     eax, edx
    .text:00401B08 mov     [ebp+var_60], eax
    .text:00401B0B cmp     [ebp+var_60], 2
    .text:00401B0F jle     short loc_401B19
    

    可以看出,这里将两个time结果放到 [ebp+var_64]和[ebp+var_68]中
    然后再传入eax和edx进行比较
    如果不调用这里就会使用原来的edx和eax中的值,也可能>2,从而不能达到预期结果
    所以这里需要在每次判断下断点修改,或者找到所有判断位置修改汇编代码

    • 注意isDebuggerPresent()的那一步判断:
    0040175A   .  E8 FD170000   call Dice.00402F5C
    0040175F   .  85C0          test eax,eax
    00401761   .  74 09         je XDice.0040176C
    00401763   .  C745 D4 42000>mov dword ptr ss:[ebp-0x2C],0x42
    0040176A   .  EB 07         jmp XDice.00401773
    0040176C   >  C745 D4 10000>mov dword ptr ss:[ebp-0x2C],0x10
    

    这里同样在判断处下断点,修改标志寄存器即可

    • 上面都是分析方法,最简单的还是修改每一次的判断跳转为nop,然后保存成新文件,五次enter之后即得flag
    • 这里留个问题,isDebuggerPresent()在OD下调试会直接返回false,在ida下返回true,这个问题有待解决

    0x02 该题不简单

    根据题目描述:
    找出用户名为hello的注册码
    下载并运行程序,输入用户名:hello
    注册码随意输入
    发现关键字:

    密钥无效
    这里两种方法:

    直接调试分析:

    OD载入:
    通过中文字符查找到关键部分,并在跳转上一步下断点调试:

    调试

    即可在堆栈窗口得到所需注册码:


    flag

    IDA分析

    载入IDA:
    Alt+T查找关键字符:“密钥”


    结果

    可以看到几点:

    • aZgb和Text变量分别保存了“密钥无效”和“密钥正确”的返回信息
    • 两者都在DialogFunc中被调用

    我们查看一下DialogFunc函数的源码,看到关键判断部分:

     if ( !sub_4011D0() )
        {
          MessageBoxA(hWnd, Text, Caption, 0);
          return 0;
        }
        MessageBoxA(hWnd, aZgb, Caption, 0);
      }
    

    可以看到关键的判断函数是sub_4011D0:
    其中关键部分:

     v1 = 0;
        if ( strlen(String) != 0 )
        {
          do
          {
            *(&String2 + v1) = (v1 + v1 * String[v1] * String[v1]) % 0x42 + 33;
            ++v1;
          }
          while ( v1 < strlen(String) );
        }
        strcpy(String, "Happy@");
        lstrcatA(String, &String2);
        result = lstrcmpA(&String1, String) != 0;
    

    可以看到String保存用户名
    String1保存注册码
    而后经过运算生成新的String最后与String1比较
    我们需要result返回false,故而需要两者相等
    下面计算hello为String时对应的String1:

    flag="Happy@"
    i=0
    for j in "hello":
        flag+=chr((i+i*ord(j)*ord(j))%0x42+33)
        i+=1
    print flag
    

    运行脚本即得flag

    0x03 证明自己吧

    题目描述:
    The Key is your Correct Input
    我们要求的便是正确的输入Key的值
    打开程序,随便输入,发现关键字:

    关键字
    将程序载入IDA:
    打开查找字符串并看到字符串定义信息:
    字符串
    很快定位到两个关键函数:
    _main和sub_401060
    看到_main的关键部分:
      if ( sub_401060((const char *)&v4) )
      {
        sub_4011BA((int)aGoodTheKeyIsYo, v4);
        result = 0;
      }
      else
      {
        sub_4011BA((int)aYouDonTGuessIt, v4);
        result = 0;
      }
      return result;
    

    我们需要sub_401060返回true
    看到sub_401060关键部分:

      if ( strlen(a1) == strlen((const char *)&v5) )
      {
        v1 = 0;
        if ( strlen(a1) != 0 )
        {
          do
            a1[v1++] ^= 0x20u;
          while ( v1 < strlen(a1) );
        }
        v2 = 0;
        if ( strlen((const char *)&v5) != 0 )
        {
          do
            *((_BYTE *)&v5 + v2++) -= 5;
          while ( v2 < strlen((const char *)&v5) );
        }
        v3 = 0;
        if ( strlen((const char *)&v5) == 0 )
          return 1;
    while ( *((_BYTE *)&v5 + v3 + a1 - (const char *)&v5) == *((_BYTE *)&v5 + v3) )// a1 - (const char *)&v5=0
        {
          ++v3;
          if ( v3 >= strlen((const char *)&v5) )    // >=0
            return 1;
        }
      }
      return 0;
    

    显然有以下两点:

    • 我们输入的a1长度需要等于&v5长度
    • a1的每一位与0x20异或后和&v5的每一位减5相同

    首先在

    if ( strlen(a1) == strlen((const char *)&v5) )
    

    处下断点调试,发现关键汇编代码段:

    .text:00401092                 mov     edi, esi
    .text:00401094                 or      ecx, 0FFFFFFFFh
    .text:00401097                 xor     eax, eax
    .text:00401099                 mov     edx, dword_407094
    .text:0040109F                 repne scasb
    .text:004010A1                 not     ecx
    .text:004010A3                 dec     ecx
    .text:004010A4                 mov     [esp+1Ch+var_8], edx
    .text:004010A8                 mov     edx, ecx
    .text:004010AA                 lea     edi, [esp+1Ch+var_10]
    .text:004010AE                 or      ecx, 0FFFFFFFFh
    .text:004010B1                 repne scasb
    .text:004010B3                 not     ecx
    .text:004010B5                 dec     ecx
    .text:004010B6                 cmp     edx, ecx
    .text:004010B8                 jnz     loc_40115A
    

    可以看出,edx和ecx中利用 repne scasb分别存放两字符串长度,再进行比较,由此可得到我们需要输入的字符串长度:


    ecx

    继续调试:
    在:

      *((_BYTE *)&v5 + v2++) -= 5;
    

    处下断点:

    .text:004010F7                 mov     al, byte ptr [esp+edx+1Ch+var_10]
    .text:004010FB                 lea     edi, [esp+1Ch+var_10]
    .text:004010FF                 add     al, 0FBh
    .text:00401101                 or      ecx, 0FFFFFFFFh
    .text:00401104                 mov     byte ptr [esp+edx+1Ch+var_10], al
    .text:00401108                 xor     eax, eax
    .text:0040110A                 inc     edx
    .text:0040110B                 repne scasb
    .text:0040110D                 not     ecx
    .text:0040110F                 dec     ecx
    .text:00401110                 cmp     edx, ecx
    .text:00401112                 jb      short loc_4010F7
    

    可以看出,&v5的每一位便是每一次循环的ax中的值:


    AX

    得到每一个值后脚本破解即得结果:

    key=[0x63,0x52,0x14,0x43,0x4B,0x69,0x53,0x73,0x4F,0x65,0x14,0x53,0x59,0x1] 
    flag=""
    for i in key: 
        flag+=chr(i^0x20)
    print  flag
    

    相关文章

      网友评论

        本文标题:【CTF-RE】Shiyanbar

        本文链接:https://www.haomeiwen.com/subject/cmrfrftx.html