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

第二届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

原文发布于微信公众号 - ChaMd5安全团队(chamd5sec)

原文发表时间:2017-03-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Hongten

Lucene学习总结之一:全文检索的基本原理

根据http://lucene.apache.org/java/docs/index.html定义:

1.5K30
来自专栏算法channel

玩转Pandas,让数据处理更easy系列6

玩转Pandas系列已经连续推送5篇,尽量贴近Pandas的本质原理,结合工作实践,按照使用Pandas的逻辑步骤,系统地并结合实例推送Pandas的主要常用功...

10020
来自专栏数说工作室

【SAS Says】基础篇:update、output、transpose以及相关的数据深层操作

特别说明:本节【SAS Says】基础篇:update、output、transpose以及相关的数据深层操作,用的是数说君学习《The little SAS ...

40260
来自专栏SeanCheney的专栏

《利用Python进行数据分析·第2版》第14章 数据分析案例14.1 来自Bitly的USA.gov数据14.2 MovieLens 1M数据集14.3 1880-2010年间全美婴儿姓名14.4

本书正文的最后一章,我们来看一些真实世界的数据集。对于每个数据集,我们会用之前介绍的方法,从原始数据中提取有意义的内容。展示的方法适用于其它数据集,也包括你的。...

43250
来自专栏数说戏聊

Python3分析Excel数据

使用xlrd和xlwt扩展包,确定工作簿中工作表的数量、名称和每个工作表中行列的数量。 1excel_introspect_workbook.py

12320
来自专栏机器学习算法与Python学习

机器学习(31)之频繁集挖掘FP Tree详解

关键字全网搜索最新排名 【机器学习算法】:排名第一 【机器学习】:排名第一 【Python】:排名第三 【算法】:排名第四 明早7:22推送第2期免费送书活动 ...

43460
来自专栏owent

PKU POJ 1724 ROADS 解题报告

题目链接:http://acm.pku.edu.cn/JudgeOnline/problem?id=1724

7220
来自专栏take time, save time

你所能用到的无损压缩编码(二)

     上个月项目荷兰大佬要检查,搞的我想写的东西不断推迟,现在检查完了,我决定继续把我想写的这整个一个系列写完,上一次写的是最简单的无损编码行程编码,这一次...

37590
来自专栏小樱的经验随笔

零基础学并查集算法

并查集是我暑假从高手那里学到的一招,觉得真是太精妙的设计了。以前我无法解决的一类问题竟然可以用如此简单高效的方法搞定。不分享出来真是对不起party了。(pa...

56380
来自专栏大史住在大前端

野生前端的数据结构基础练习(8)——图

图是由边的集合和点的集合组成的。如果图的边有方向(或者说图中的顶点对是有序的)则成为有向图,如果边没有方向则称为无向图。

10430

扫码关注云+社区

领取腾讯云代金券