0x00 ez.net
操作内容:
下载文件后扔到Exeinfo PE可知为.net框架的程序
因此将程序导入到Reflector中尝试查看源码
可发现当输入”UmV2ZXJzZVNpZ25pbiE=”的Base64解码后形式即”ReverseSignin!”即可得到flag
FLAG值:
flag{N0wY0uAre1evelZer0!}
0x01 达拉崩吧
操作内容:
下载文件后解压得到一个exe文件和一个dll文件,扔到Exeinfo PE可知皆为.net框架的程序.
运行后可知该程序是模拟一个勇士打巨龙的文字游戏,但是因为出题者有意所为,勇士正常情况下是不可能击败巨龙且自己不死的,因此可判断打败巨龙后即可得到flag.
将两个PE文件拖入Reflector中尝试查看源码,发现exe文件无法正常载入,但dll文件可以正常加载,从源码中可知,dll文件是用来定义勇士和巨龙的血量,魔法,技能伤害值等数据,因此只需修改其某项数据即可达到目的,修改数据需借助插件Reflexil.
此处我首先尝试修改了元气弹技能的伤害为7001(巨龙血量7000),测试后发现即使一击毙命游戏还是会失败,判定为同归于尽,因此还需要修改勇士的血量.
这里尝试将魔法盾技能的回复生命值修改至12000,测试成功,最后成功得到flag
FLAG值:
flag{0ne_Punch_Man!}
0x02 PictureLock[SP Edition]
操作内容:
下载解压文件后得到一个exe,一个dll,和一个.lock文件,查壳后可知exe为upx加壳,解之.dll为无壳pe文件.
运行程序后可知该程序的作用是加密一个图片并以.lock后缀结尾写出文件,因此我们的任务即为找到图片加密算法,写出解密算法,将题目文件中的.lock文件解密为图片,以得到flag
为了弄清楚dll的作用,将其替换为一个毫无联系的dll文件,运行程序报错提示如下:
此时可初步得知sp.dll负责通过enc0函数进行加密操作.
将dll导入ida找到enc0函数,可看到该加密算法的全貌,然后用C++重现一遍代码后再写出逆向解密的代码,即可得到flag.
解密代码如下:
_BYTE key[32] = { 0x64, 0x34, 0x31, 0x64, 0x38, 0x63, 0x64, 0x39, 0x38, 0x66, 0x30, 0x30, 0x62, 0x32, 0x30, 0x34, 0x65, 0x39, 0x38, 0x30, 0x30, 0x39, 0x39, 0x38, 0x65, 0x63, 0x66, 0x38, 0x34, 0x32, 0x37, 0x65 }; _BYTE unk_36E4[256] = { 0x72, 0x6A, 0x08, 0x96, 0xDE, 0x6E, 0x20, 0xEB, 0x87, 0xDD, 0xE0, 0x12, 0x36, 0xEF, 0xCB, 0x05, 0xEA, 0x4F, 0xB6, 0xAC, 0x2D, 0x56, 0x3F, 0xFA, 0x61, 0xAF, 0x59, 0x00, 0x53, 0xD5, 0xC3, 0xD4, 0x38, 0x8F, 0xDB, 0xC4, 0xB8, 0xEC, 0xBD, 0xCE, 0x7C, 0x94, 0x33, 0xE5, 0x21, 0xE4, 0x17, 0x60, 0xAE, 0x25, 0x1F, 0xCC, 0x3C, 0x27, 0xA3, 0x04, 0x70, 0x52, 0x49, 0xED, 0x5A, 0x88, 0xC2, 0x19, 0xE2, 0x6D, 0x79, 0xF4, 0xAD, 0x85, 0xB9, 0x77, 0x03, 0xA2, 0xBF, 0xF3, 0x1B, 0x5D, 0xC9, 0xD1, 0xF8, 0x62, 0x69, 0xC5, 0x2F, 0xF1, 0xA1, 0xCA, 0x44, 0x98, 0x3B, 0x6F, 0xE7, 0xFB, 0x16, 0x78, 0x0B, 0x45, 0x5B, 0xFF, 0x24, 0x42, 0x15, 0x2C, 0x75, 0x14, 0x5F, 0xC0, 0xB1, 0x97, 0x64, 0xAB, 0x41, 0x46, 0xD3, 0x30, 0x9D, 0x93, 0x7F, 0xA9, 0x55, 0x51, 0x2B, 0x1A, 0x4A, 0x9C, 0xB4, 0xE8, 0xD7, 0x73, 0xC1, 0x9E, 0xDA, 0xE9, 0x91, 0x2E, 0x09, 0x9A, 0x7A, 0x01, 0xFC, 0xF2, 0x6C, 0xD2, 0x47, 0x90, 0xA0, 0xBC, 0x71, 0xEE, 0xA5, 0xF7, 0xCF, 0x1D, 0x32, 0xD6, 0x5C, 0x13, 0x4B, 0x0D, 0x65, 0xDC, 0x86, 0xAA, 0x63, 0xB3, 0x50, 0x1C, 0xB0, 0x07, 0x4D, 0x76, 0xBA, 0x7B, 0xC8, 0x80, 0x67, 0x81, 0x3E, 0x99, 0x7E, 0x54, 0x8B, 0xB2, 0x06, 0x8D, 0x29, 0xA8, 0x43, 0x82, 0x5E, 0x8A, 0xE6, 0x9B, 0x68, 0x3A, 0xD8, 0xFE, 0x1E, 0x6B, 0xDF, 0xA7, 0x22, 0x66, 0x0A, 0x37, 0x74, 0x58, 0x48, 0x83, 0x31, 0x7D, 0x39, 0xBB, 0xD9, 0x4C, 0xF0, 0x0E, 0x3D, 0x26, 0xA6, 0xC7, 0xE1, 0xB7, 0x89, 0x34, 0x8E, 0xB5, 0x23, 0x4E, 0x8C, 0x92, 0xF6, 0xC6, 0x0F, 0x02, 0x9F, 0x11, 0x57, 0xE3, 0x95, 0x28, 0x18, 0x2A, 0xD0, 0xF5, 0xCD, 0x0C, 0xBE, 0xFD, 0xF9, 0x40, 0x35, 0x84, 0x10, 0xA4 }; void dec1(_BYTE *result, int *a2); void dec2(_BYTE *result); void dec3(_BYTE *result); void enc1(_BYTE *result, int *a2); void enc2(_BYTE *result); void sub_162C(unsigned __int8 *result); void enc3(_BYTE *result); int main() { int i; FILE* in; FILE* out; char *v3; //读取的数据 _BYTE *v2; //加密后数据 char *v27; size_t Rsize; size_t RsizeNew; int *v30; int *v9; // [esp+34h] [ebp-24h] _BYTE *v36 = (_BYTE *)malloc(sizeof(_BYTE) * 16); v9 = (int *)malloc(0x180u); for ( i = 0; i <= 383; ++i ) *((_BYTE *)v9 + i) = *(_BYTE *)(i % 32 + key) ^ i; in = fopen("1.bmp.lock", "rb"); if(in){ out = fopen("1.bmp","wb"); if(out){ v3 = (char *)malloc(256); //分配空间 v2 = (uint8 *)malloc(256); for(i = 0; ; ++i ){ char v24 = *(_BYTE *)(key + (i & 0x1F)); Rsize = fread(v3, 1, v24 , in); //printf("%d|%d\n",key [i % 32],Rsize); if(!Rsize) goto LABEL_31; RsizeNew = Rsize; if (Rsize <= 15){ v27 = &v3[Rsize]; int v28 = 16 - (RsizeNew & 0xF); if((RsizeNew & 0xF) != 16){ memset(v27, 16 - (Rsize & 0xF), (unsigned __int8)(16 - (Rsize & 0xF))); v27 = &v3[v28 + RsizeNew]; } RsizeNew = 16; *v27 = 0; } v30 = (int *)(v9 + 48); if(!(v24 & 1)) v30 = (int *)v9; v36[0] = *v3; // 初步打乱,交换顺序 v36[4] = v3[1]; v36[8] = v3[2]; v36[12] = v3[3]; v36[1] = v3[4]; v36[5] = v3[5]; v36[9] = v3[6]; v36[13] = v3[7]; v36[2] = v3[8]; v36[6] = v3[9]; v36[10] = v3[10]; v36[14] = v3[11]; v36[3] = v3[12]; v36[7] = v3[13]; v36[11] = v3[14]; v36[15] = v3[15]; dec1(v36, v30 + 40); dec3(v36); dec2(v36); dec1(v36, v30 + 36); dec3(v36); dec2(v36); dec1(v36, v30 + 32); dec3(v36); dec2(v36); dec1(v36, v30 + 28); dec3(v36); dec2(v36); dec1(v36, v30 + 24); dec3(v36); dec2(v36); dec1(v36, v30 + 20); dec3(v36); dec2(v36); dec1(v36, v30 + 16); dec3(v36); dec2(v36); dec1(v36, v30 + 12); dec3(v36); dec2(v36); dec1(v36, v30 + 8); dec3(v36); dec2(v36); dec1(v36, v30 + 4); dec3(v36); dec2(v36); dec1(v36, v30); *v2 = v36[0]; v2[1] = v36[4]; v2[2] = v36[8]; v2[3] = v36[12]; v2[4] = v36[1]; v2[5] = v36[5]; v2[6] = v36[9]; v2[7] = v36[13]; v2[8] = v36[2]; v2[9] = v36[6]; v2[10] = v36[10]; v2[11] = v36[14]; v2[12] = v36[3]; v2[13] = v36[7]; v2[14] = v36[11]; v2[15] = v36[15]; if(RsizeNew >= 17){ // 16之后的字节按这个加密 int v31 = 16; _BYTE *v32 = key; do{ v2[v31] = v3[v31] ^ *(_BYTE *)(v32 + v31 % 32); ++v31; }while (v31 < RsizeNew); } if(fwrite(v2, 1, RsizeNew, out) != RsizeNew) break; } } } LABEL_31: free(v3); free(v2); fclose(in); fclose(out); system("pause"); return 0; } void dec1(_BYTE *result, int *a2) { int v2; // r2 int v3; // r2 int v4; // r2 int v5; // r1 v2 = *a2; result[0] ^= (unsigned int)*a2 >> 24; //取高8位 result[4] ^= BYTE2(v2); result[8] ^= BYTE1(v2); result[12] ^= v2; v3 = a2[1]; result[1] ^= HIBYTE(v3); result[5] ^= BYTE2(v3); result[9] ^= BYTE1(v3); result[13] ^= v3; v4 = a2[2]; result[2] ^= HIBYTE(v4); result[6] ^= BYTE2(v4); result[10] ^= BYTE1(v4); result[14] ^= v4; v5 = a2[3]; result[3] ^= HIBYTE(v5); result[7] ^= BYTE2(v5); result[11] ^= BYTE1(v5); result[15] ^= v5; } void dec2(_BYTE *result) { for(int i2=0;i2<16;i2++) { for(int i=0;i<256;i++){ if(result[i2]==unk_36E4[i]){ result[i2]=i; break; } } } } void dec3(_BYTE *result) { char v1; // r1 char v2; // r1 char v3; // r1 char v4; // r1 v1 = result[7]; result[7] = result[6]; result[6] = result[5]; result[5] = result[4]; result[4] = v1; v2 = result[8]; result[8] = result[11]; result[11] = v2; v3 = result[13]; result[13] = result[14]; result[14] = result[15]; result[15] = result[12]; result[12] = v3; v4 = result[10]; result[10] = result[9]; result[9] = v4; v4=result[0]; v4=result[1]; }
其中值得一提的是unk_36E4的数据内容在dll中即可找到,而key的数据内容在exe文件中,在调用enc0时传参给dll
调用方式:enc0(待加密图片路径,输出加密文件路径,32字节的key)
此处我采用的方法是编写一个动态库注入到exe中,hook其enc0的调用函数,然后即可得知传入的key的指针地址,再用CheatEngine读取其地址复制出key的内容.
FLAG值:
flag{simp1er_picture_l0cker!}
0x03 签到
操作内容:
没什么好说的.
FLAG值:
flag{welcome_to_GCTF}
0x04 Steganography
操作内容:
下载题目文件后以压缩包形式打开,得到flag
FLAG值:
flag{D0_y0u_1ike_what_y0u_see}
0x05 Undefined
操作内容:
这题脑洞的确挺大的,如果没有后面加上的提示我还真解不出来.
下载题目文件后可发现是一个以.c结尾的文件,疑似c的源码文件,根据题目Undefined和提示#define _______ return可以知道,这题就是要你把不同长度的下划线定义成c语言常用的命令名称等.
在原文件内容的头部加上以下定义:
#define _______ return
#define ______ main
#define ___ int
#define _____ char
#define __ argv
#define ____ printf
然后编译运行源码即可得到flag.
FLAG值:
g2uc_CTF{heLLo_WOrid}
0x06 Very water
操作内容:
打开题目所给的网址看到如下内容:
可以初步判断该题目是一个关于md5或者sha1函数在PHP中存在比较漏洞的题.
题中用于比较的字符串”6Jgtsk”在经过如源码中的一系列hash并substr后得到的结果为:
0e63444494459748
可以看到这串hash有两个特点,一是0e开头,二是0e之后为纯数字.当PHP中被比较的两个字符串同时满足这两个条件时,对其进行==操作会返回true.
因此我们要做的就是找到一个和字符串”6Jgtsk”不一样,且其经过一系列hash并substr后的结果也以0e开头并在0e之后有14位纯数字的字符串
此处我直接用易语言写了个简单的脚本,进行筛选,找到其中一个合适的key值”0001 '”
带上该参数后访问题目网址即可得到flag
http://172.22.28.21/web/b9f5d37a207bdf85d180aa7702d654e0/?key=0001%20%2
FLAG值:
flag{thi5_prOb13m_i5_re@l1y_w@ter}
0x07 Just enjoy it
操作内容:
引言.
这一题感觉还是有点难的,访问网址后经分析可知,提交的flag的验证算法在js文件里,也就是不经过网络直接本地计算,因此我们直接把网页保存到本地进行调试.
打开其js后可看出js进行了简单的混淆,为了能看的清楚一点,我用脚本进行了简单的处理.
经调试后发现其代码中有几处反调试,不得不说js有的反调试还蛮隐秘的,比如最显眼的反调试函数是_0x0113这个函数,其功能是断下调试器并不停刷新console控制台,一开始我是直接把这个函数删除掉的,但是到后面就踩坑了,这个下面再说.
经过简单处理后的代码大概是这样:
至少我们可以比较清晰的看到运行的流程,接下来我们分几部分来讲flag的验证过程.
1.
很简单的运算,就是把"XTeWlU!P"的ASCII码值加上0x0f即可得到flag的前八个字节”gctf{d0_”.
2.
这一段起初我直接忽视掉了,因为完全不影响返回的结果,但其实这段代码也透露出了一个重要的信息,即经过几个判断后将flag的第0x15(21)之后一位的字符替换为”@”.具体判断内容下面再说.
3.
这段代码计算检测的flag的9-16位的内容的正确性,不过其具体的算法我没有研究,只是通过输入类如”azAz019_”这样不同类型的头尾字符,然后调试查看_0x8bce计算的返回内容,将这八位内容推测了出来”y0u_1ik3”.
4.
接下来这段函数就是最有意思的一段了,图中所示的是原本未经修改的函数.
经过分析可知其功能为验证flag的18-20位是否正确,但是算法中隐藏着一处反调试,就是第103行的代码,其功能是检测开头我们所说的反调试函数_0x0113是否存在,若未定义该函数则会抛出异常,返回0x2提示”gg”.删掉这段命令即可解决,不过一开始我一直以为这是算法的一部分,坑了我挺久.
因为这段算法是不可逆的,因此我写了个脚本,枚举了符合该算法的所有字符串,然后放着先看下面的代码.
5.
最后这一段就比之前的简单多了,首先是判断flag中是否存在字符”7”且该字符是否不在末尾,满足条件则把”7”移动到flag的末尾.
其次是通过位异或计算flag的最后八位是否符合,由此可推出最后八位是”@u_5e3}7”,又因为”7”被置尾,所以正确的后八位应该是”7@u_5e3}”.
结尾.
根据目前得到的结果,flag应该是这样的:gctf{d0_y0u_1ik3?!!!...7@u_5e3}
?是代表未知的字符,!!!是代表步骤4中得到的很多符合验证条件的字符串,...是代表可以随便写也可以不写的字符串(因为js中的算法并未对长度做出明确的要求).
不过在步骤2中我们提到的算法里有这么一句判断:
inputFlag["substr"](0x10, 0x1) == inputFlag["substr"](0xb, 0x1)
判断flag的第12位和第17位是否相等,由此可判断未知的字符?应该就是”_”,
所以现在flag是gctf{d0_y0u_1ik3_!!!...7@u_5e3}
你会发现,有无限多的flag可以满足这个计算条件,不过按照常理来说,flag应该是最短的那个,
像这样gctf{d0_y0u_1ik3_!!7@u_5e3}
只有步骤4中flag的18-20位是最随机的,因此我枚举出所有第20位是”7”的内容.
gctf{d0_y0u_1ik3_th7@u_5e3}
但是经hash后不符合题目要求中的正确哈希值.因此排除.现在我们考虑多一位长度的情况:
gctf{d0_y0u_1ik3_!!!7@u_5e3}
经过脚本批量判断后发现,还是没有一个值符合题目所给的哈希值,难道我们的思路错了吗?其实在步骤2的代码还有作用.你会发现他在判断若第22位内容为”_”则将其变为”@”,是的,
因此正确的flag应该是这样的形式:gctf{d0_y0u_1ik3_!!!7_u_5e3}
此时再用脚本进行枚举对比即可成功拿到flag.
FLAG值:
gctf{d0_y0u_1ik3_wh47_u_5e3}
0x08 Pig raising
操作内容:
猪圈密码,百度一下你就知道!
FLAG值:
flag{PIGSTY}
0x09 base64?
操作内容:
Base64解码后为”gctNe002_fi_cruc{cdt}”可以看出为栅栏密码,尝试以3字一组成功解密
FLAG值:
g2uc_ctf{Nice_d0ct0r}
0x10 cipher
操作内容:
这一题我的做法稍微包含了点猜测的成分,首先根据下载的题目文件可知是一个py的源码,和一个该脚本加密运算后的十六进制数据文本.
根据py源码的内容和题目提示可知,该加密数据文本就是将flag字符串加上6位数字密码字符串再加上前两段取md5的32位哈希值,然后按位进行异或后的内容,异或所使用的值是该密码的其中一位.
例如若加密”test1234”密码”123456”则t用1异或以此类推,若密码长度不够则从密码头部重新来算.
因此我猜测flag前5位为”flag{”,用脚本暴力枚举后得到密码前五位是”12345”
最后一位密码则也可以直接爆破,从0-9中很容易可以筛选出正确的flag.
FLAG值:
flag{Yahaha_y0u_f0und_it}