看雪CTF2019 Q2消失的岛屿

CTF题目解析思路

Posted by JovenHe on June 11, 2019

6月24日中午看雪CTF2019晋级赛Q2结束,消失的岛屿一题的被攻破数位列第二,所以还算是比较简单的,晋级赛Q2一共十道题,我只做出来此题,有两道是找到关键点了,但分析不出答案来,只好作罢。从某种程度上来说大神和菜鸟的差距还是很大的,攻击方前几名是六七百分,而我们这些菜鸟一百多名的只能解出一两道简单题,只有三四十分,真的是差距蛮大的,还是有很长的路要走,下面就开分析这道题。

双击运行exe文件

打开页面.png

随便输入后,错误则显示“Please Try Again”

IDA打开exe

直接来到主方法。

IDA打开.png

F5查看伪代码

主入口伪代码.png

可以看到是base64加密输入的字符串后,和”!NGV%,$h1f4S3%2P(hkQ94==”进行strcmp函数计算

strcmp函数是用于比较两个字符串

设这两个字符串为str1,str2,

若str1=str2,则返回零;

若str1<str2,则返回负数;

若str1>str2,则返回正数。

所以只有两者相等才能打印 “success”,所以输入字符加密后的密文必须是!NGV%,$h1f4S3%2P(hkQ94==

但base64加密后密文中并不会有!%,$(这些特殊字符,所以这个并不是普通的base64,直接查看base64_encode函数

base64加密函数

base64加密.png

因为我本人并未系统学过C语言,所以只好在网上找了个C写的base64加密

#include <stdio.h>
#include <string.h>

// 全局常量定义
const char * base64char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const char padding_char = '=';

/*编码代码
* const unsigned char * sourcedata, 源数组
* char * base64 ,码字保存
*/
int base64_encode(const unsigned char * sourcedata, char * base64)
{
    int i=0, j=0;
    unsigned char trans_index=0;    // 索引是8位,但是高两位都为0
    const int datalength = strlen((const char*)sourcedata);
    for (; i < datalength; i += 3){
        // 每三个一组,进行编码
        // 要编码的数字的第一个
        trans_index = ((sourcedata[i] >> 2) & 0x3f);
        base64[j++] = base64char[(int)trans_index];
        // 第二个
        trans_index = ((sourcedata[i] << 4) & 0x30);
        if (i + 1 < datalength){
            trans_index |= ((sourcedata[i + 1] >> 4) & 0x0f);
            base64[j++] = base64char[(int)trans_index];
        }else{
            base64[j++] = base64char[(int)trans_index];

            base64[j++] = padding_char;

            base64[j++] = padding_char;

            break;   // 超出总长度,可以直接break
        }
        // 第三个
        trans_index = ((sourcedata[i + 1] << 2) & 0x3c);
        if (i + 2 < datalength){ // 有的话需要编码2个
            trans_index |= ((sourcedata[i + 2] >> 6) & 0x03);
            base64[j++] = base64char[(int)trans_index];

            trans_index = sourcedata[i + 2] & 0x3f;
            base64[j++] = base64char[(int)trans_index];
        }
        else{
            base64[j++] = base64char[(int)trans_index];

            base64[j++] = padding_char;

            break;
        }
    }

    base64[j] = '\0'; 

    return 0;
}

可以看到区别最大的是网上的((sourcedata[i] » 2) & 0x3f)与IDA中的 charEncrypt((bindata[i] » 2) & 0x3F),多了charEncrypt函数,直接查看:

charEncrypt.png

可以看到是对加密后的char经过了加减运算,其中负数二进制表示在补码后得到其实是一个正数。

而且aTuvwxtulmnopqr中存储的是’tuvwxTUlmnopqrs7YZabcdefghij8yz0123456VWXkABCDEFGHIJKLMNOPQRS9+/’,0

和正常Base64的”ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/”;不一样,是打乱顺序了。

解密

所以加密顺序是明文->base64字符->乱序字符->运算后的密文

所以密文!NGV%,$h1f4S3%2P(hkQ94==要按加密顺序反向解密得到真正的base64字符串

写个java程序跑一下:

class Untitled {
	
	public static void main(String[] args) {

         String secret="!NGV%,$h1f4S3%2P(hkQ94";
        String special="tuvwxTUlmnopqrs7YZabcdefghij8yz0123456VWXkABCDEFGHIJKLMNOPQRS9+/";
        String nomal="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        StringBuffer stringBuffer=new StringBuffer();
        for (int i = 0; i < secret.length(); i++) {
            stringBuffer.append(nomal.charAt(special.indexOf(String.valueOf((char)getInt(secret.charAt(i))))));
        }
        stringBuffer.append("==");
		
		System.out.println(stringBuffer.toString());
       
	}
	public static int getInt(int m){
        if (m==121)return 47;
        if (m==119)return 43;
        if (m-50>47 && m-50<=57){
            return m-50;
        }
        if (m+64>96 && m+64<=122){
            return m+64;
        }
        if (155-m>64 && 155-m<=90){//负数-101的补码为155
            return 155-m;
        }
        return m;
    }
}

运行得到明文的Base64是 S2FuWHVlMjAxOWN0Zl9zdA==

解码后明文是KanXue2019ctf_st,输入后返回Success

success.png

总结

本题还是比较简单的,用到了古典密码中的置换密码和代替密码,需要对Base64有个整体的了解,了解负数的表示,还是比较容易得到明文的。