前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >第二届ZCTF逆向题分析(一)

第二届ZCTF逆向题分析(一)

作者头像
ChaMd5安全团队
发布2018-03-29 11:19:50
2.1K0
发布2018-03-29 11:19:50
举报
文章被收录于专栏:ChaMd5安全团队ChaMd5安全团队

第二届ZCTF逆向题分析(一)

From ChaMd5安全团队核心成员 Poyoten

比赛时由于第二天有事,第三题re没做,所以也就没有写WP。但是后来看了大佬们的WP,可能是题比较简单,所以写得也比较简略,我等菜鸟根本看不懂。于是乎就想尝试分析下这次比赛的逆向题,以希望对和我一样的小白能有所帮助。

(如果想要获得更好的观看效果,建议把本文分享出去,复制其链接在电脑上观看。)

easy reverse

这题是一个dll文件,IDA载入,直接查看export,如下图,除了入口和两个tls,还有三个导出函数encryptencrypt_strget_string

这三个导出函数名也太敏感了,直接先看这三个函数,直接f5看伪代码,如图

主逻辑就在get_string函数中,先将24个字节的目标验证字串复制到栈区,然后接收输入,将输入与常量0xEFBEADDE通过encrypt_str进行处理,处理完的输入与目标验证字串进行比较。而encrypt_str的作为只是调用encrypt对输入进行每两位一处理。

encrypt函数就是核心算法所在了,对两字节的字串使用key1进行加密或变换。先用字串第二个字节计算出第一个字节的新值,再用第一个字节的新值计算第二个字节的新值,如此循环,直至退出循环,最后把变换后的两字节新值写入到输入的相应位置中。 由于做题当时并未看出这是什么算法,又考虑到encrypt是对两个字节进行变换加密,对其它字串并不影响,而且其中的变动因素也只有2个字节的输入。所以思路很明显,我们可以两位两位的爆破,输入位数可以从上面的分析得出:24字节。

在使用伪代码还原算法的过程中,由于核心算法处三处使用了movsx,致使还原出来的算法总是有问题。于是直接使用asm,代码如下:

#include <stdio.h>

#include <stdio.h>

#include <memory.h>

typedef unsigned char  BYTE;

BYTE unk_6E282000[] = { 0xBF,0xF1,0x6A,0x2C,0x10,0x0B,0x16,0x59,0xBA,0x3A,0x8C,0x49,0x05,0x1B,0x04,0xE2,0x85,0xD5,0xC2,0xFC,0xD7,0x9B,0xE9,0x42,0x0 };

int  encrypt( BYTE * a3,BYTE *a4)

{

    BYTE v4; // eax@1

    int result; // eax@3

    BYTE a1,  a2;



    v4 = 0xB9 ;

    _asm {  

                push    edi

                push    esi

                push    ebp

                mov     edi, a4

                mov     eax, a3

                push    eax

                mov     cl, [eax]

                mov     dl, [eax + 1]

                mov     al, 0B9h

                nop



            loc_6E281228 :

                movsx   esi, dl

                mov     ebx, eax

                and     ebx, 3

                mov     bl, [edi + ebx]

                add     ebx, eax

                mov     ebp, esi

                sar     ebp, 5

                shl     esi, 4

                xor     esi, ebp

                add     esi, edx

                xor     ebx, esi

                add     ecx, ebx

                movsx   ebx, cl

                mov     esi, ebx

                sar     esi, 5

                shl     ebx, 4

                xor     esi, ebx

                add     esi, ecx

                movsx   ebx, al

                sar     ebx, 0Bh

                and     ebx, 3

                mov     bl, [edi + ebx]

                add     ebx, eax

                xor     esi, ebx

                add     edx, esi

                sub     eax, 47h

                cmp     al, 0D9h

                jnz     loc_6E281228

                pop     eax

                mov     [eax], cl

                mov     [eax + 1], dl

                pop     ebp

                pop     esi

                pop     edi



       }   

    result = *a3;

    return result;

}



int  encrypt_str(BYTE *a1, signed int a2, BYTE *a3)

{

    int result; // eax@1

    int v6; // ebx@    

    result = a2 / 2;

    if (a2 / 2 > 0)

    {

        v6 = 0;    

        do

        {

            result = encrypt(a1, a3);

            v6 += 2;

        } while (v6 != a2);

    }

    return result;

}



int main()

{

    int result = 0; // eax@1

    BYTE v2[25]; // [sp+1Eh] [bp-3Eh]@1

    BYTE v1[4] = { 0xDE, 0xAD ,0xBE, 0xEF };

    int count = 0;

    bool flag;

    do {

        for (BYTE i1 = 0x20; i1 <= 0x7f; i1++)

        {            

            for (BYTE i2 = 0x20; i2 <= 0x7f; i2++)

            {

                BYTE a[2];

                flag = false;

                a[0] = i1;

                a[1] = i2;

                encrypt_str(a, 2, v1);

                if (a[0] == unk_6E282000[count] && a[1] == unk_6E282000[count + 1]) {

                    printf("%c%c", i1,i2);

                    flag = true;

                    break;

                }

            }

            if (flag) break;

        }

        if (!flag) {

            printf("fail..."); return 0;

        }

        count += 2;

    } while (count < 24);    

    return result;

}

最后得出flag:zctf{ha_hAha_d1l_exp0r7} 后来听别人说,别人又听大佬说核心算法是XTEA加密算法,去查了查资料,果然是,传送门(http://blog.csdn.net/gsls200808/article/details/48243019)。

QExtend

这是一个console程序,先试运行下,如图:

拖进IDA,shift+f12查看字串,如图:

输入提示、出错提示、成功提示字串都有,查看引用这些字串的位置,只有一处引用,伪代码如下:

程序逻辑很简单,有4个条件判断:第一是检查输入字串长度小于32;第二是输入前4个字符为"ZCTF";第三是loc_4026D0返回为真;第四是sub_402800返回为真。根据此处代码,可知输入形如ZCTF{XXXXXXXXXX},其中的XXXXXXXXXX为第三、第四个函数的参数。

在查看loc_4026D0sub_402800处代码时,发现有混淆,如下图:

上图中的.text:004026E0 call sub_4026F5.text:00402808 call sub_40282E采用了相同手法,call之后,返回地址入栈,再将返回地址pop到esi中,实际上的操作将此条命令后的地址传送给esi

除此之外,loc_4026D0处还有个switch结构,里面6个调用函数的返回地址都作了修改。

如上图中的.text:00402688 add dword ptr [ebp+4], 1就将返回地址+1,实际上sub_402680的返回地址是.text:00402740 jmp short loc_402782,其余5个函数的情况类似。

用IDA去除混淆后,伪代码如下:

sub_4026D0中,主要是一个switch结构,程序走向由a2中各字符的低4位-1控制,退出循环只有两个途径:一是sub_402490返回为false,二是进入default选支。

细看case 0-5的调用函数,其作用都是loc_4026E5字串的某两位(一位ascii非0,一位ascii为0)交换。sub_402490的作用是检查loc_4026E5中两个相邻的非0字符低位的是否都小于高位的,如果是则返回true,循环继续,否则就校验失败。default是检查loc_4026E5的6-10个字符不为空,并返回true。

loc_4026E5的初始状态为:

如果sub_4026D0要返回true,则最后loc_4026E5状态应该为:

再对比case0-5的调用函数作用,很明显,这是一个汉诺塔游戏的步数求解。loc_4026E5每5个字节分别表示一个柱子,共三个柱子:A,B,C。"AA"、"BB"、"CC"、"DD"、"EE"表示5个尺寸从小到大的盘子。

而switch值与调用函数作用的对应关系为: case 0 A->B case 1 A->C case 2 B->A case 3 B->C case 4 C->B case 5 C->A

手动解算后,case的序列为053254104123104524104,即输入串中XXXXXXXXXX的ascii码低4位依次为164365215234215635215。

sub_402800则更好理解,根据sub_402430中的幻数及具体算法很容易就能确定是md5算法。最后与loc_40280D处的字串比较,loc_40280D处的值为:

到此,思路就明晰了,只能爆破。但是有21位字符只知其ascii低4位值,每个字符有6种可能性,那就是6**21=21936950640377856,数目过于庞大。考虑到,通常游戏算法类的算法中,相同操作或程序流向对应相同的字符。那我们不妨先假设相同的ascii低4位对应相同的字符进行枚举,则枚举次数最多为6**6=46656。

# -*- coding:utf8 -*-

from hashlib import md5



md5_s = '0f2e7e447593ec9af3463e9c8745b892'  



def main():

  # 164365215234215635215

  # 053254104123104524104

  table = [0x20,0x30,0x40,0x50,0x60,0x70]

  i = [1,2,3,4,5,6]

  for i[0] in table:

    for i[1] in table:

      for i[2] in table:

        for i[3] in table:

          for i[4] in table:

            for i[5] in table:

              tmp = [1,2,3,4,5,6]

              for j in xrange(6):

                tmp[j] = chr(i[j]+j+1)

              tmp_str = tmp[0]+tmp[5]+tmp[3]+tmp[2]+tmp[5]+tmp[4]+tmp[1]+\

                        tmp[0]+tmp[4]+tmp[1]+tmp[2]+tmp[3]+tmp[1]+tmp[0]+\

                        tmp[4]+tmp[5]+tmp[2]+tmp[4]+tmp[1]+tmp[0]+tmp[4]

              if md5(tmp_str).hexdigest() == md5_s:

                print 'ZCTF{%s}' %tmp_str

                return True

  return False



if __name__ == '__main__':

  if main() :

    print 'ok'

  else:

    print 'false'      

XXXXXXXXXX解出来就是A&$#&5rA5r#$rA5&#5rA5

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-03-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ChaMd5安全团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第二届ZCTF逆向题分析(一)
    • easy reverse
      • QExtend
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档