当前位置:首页 > 技术学习 > g2uc CTF2018 [线上赛] 部分Writeup题解

g2uc CTF2018 [线上赛] 部分Writeup题解

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文件,查壳后可知exeupx加壳,解之.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,hookenc0的调用函数,然后即可得知传入的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”在经过如源码中的一系列hashsubstr后得到的结果为:

0e63444494459748

可以看到这串hash有两个特点,一是0e开头,二是0e之后为纯数字.PHP中被比较的两个字符串同时满足这两个条件时,对其进行==操作会返回true.

因此我们要做的就是找到一个和字符串”6Jgtsk”不一样,且其经过一系列hashsubstr后的结果也以0e开头并在0e之后有14位纯数字的字符串

此处我直接用易语言写了个简单的脚本,进行筛选,找到其中一个合适的key0001 '

带上该参数后访问题目网址即可得到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.

 

这段代码计算检测的flag9-16位的内容的正确性,不过其具体的算法我没有研究,只是通过输入类如azAz019_这样不同类型的头尾字符,然后调试查看_0x8bce计算的返回内容,将这八位内容推测了出来”y0u_1ik3”.

4.

 

接下来这段函数就是最有意思的一段了,图中所示的是原本未经修改的函数.

经过分析可知其功能为验证flag18-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位是否相等,由此可判断未知的字符?应该就是_,

所以现在flaggctf{d0_y0u_1ik3_!!!...7@u_5e3}

你会发现,有无限多的flag可以满足这个计算条件,不过按照常理来说,flag应该是最短的那个,

像这样gctf{d0_y0u_1ik3_!!7@u_5e3}

只有步骤4flag18-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位数字密码字符串再加上前两段取md532位哈希值,然后按位进行异或后的内容,异或所使用的值是该密码的其中一位.

例如若加密test1234密码123456t1异或以此类推,若密码长度不够则从密码头部重新来算.

因此我猜测flag5位为flag{,用脚本暴力枚举后得到密码前五位是12345

最后一位密码则也可以直接爆破,0-9中很容易可以筛选出正确的flag.

FLAG值:

flag{Yahaha_y0u_f0und_it}

 

除特别注明外,本站所有文章均为Moeray 的博客 | MoerayBlog原创,转载请注明出处来自https://www.r966.com/post/11.html

赞 (2
Ray's Qq:1422401186.

发表评论

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。