WriteUp比赛完就写了= =结果我拖到现在才整理发上来 mini-lctf是校内入门赛啊…… 真正的dalao们都出题去了,没有参加,于是给我单挑打了个第五出来 然而因为没有新生在队内所以不给我奖品咯[掀桌!] 好歹我还ak了re和mobile!(虽然都是入门级的) 好咯,不管你,post一下题目的打包 链接: https://pan.baidu.com/s/1pKXa2rp 密码: gnbq
Web 50 (苏打学姐撞上碳酸钠了) 能看到源码为
1 2 3 4 5 6 7 8 sha1 conllision, Can you do it<!-- $key = "aa3OFF9m" ;$pass = isset ($_GET ['pass' ]) ? $_GET ['pass' ] : "" ;if ($pass != $key && sha1($pass )==sha1($key ) ) { echo $flag ; } else { echo "sha1 conllision</br>" ; } -->
获得pass,与key=”aa30FF9m”的sha1值比较
第一个想到的是sha1碰撞,试了下显然没什么效果,50分不应该很难
后来谷狗搜了下key的sha1值,发现了这个神奇的github
https://github.com/spaze/hashes
这个是php的问题了额
挑了个输入,得到flag
LCTF{conllision_is_so_difficult}
然后web就基本都不会做了= =
Misc 20 (回转十三位) 后面接着4个等号,base32编码,加上回转13位,想到ROT13,得到flag
Misc20 (Easy) 密文是LbbeCaarT3r}Fer{_i
典型栅栏密码
Misc60 (Document) 解压出来后用binwalk跑了下,发现了一堆隐藏的文件
直接后缀名改成.zip,是个压缩包
直接就在子文件夹中找到flag了
Misc100 (Noisy) 下载文件名是wav,音频文件嘛……后缀名改成.wmv,听一下是一段噪音
下了个Audacity分析,瞎搞一下
发现转成频谱图的时候
噢草,厉害了!
Misc150 (Sword Art Online) 一个txt文件,打开发现全是255, 255, 255
然后中间还夹杂着一些别的数据
第一反应就是RGB数据咯,统计了下共1080*1080组数据
写了个脚本复原一下
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 from PIL import Image, ImageDrawimport mathwidth = 1080 height = 1080 image = Image.new('RGB' , (width, height)) colorfd = open ('wtf.txt' ,'r' ) draw = ImageDraw.Draw(image) for x in range (width): for y in range (height): s = colorfd.readline() if x%4 ==1 : y1 = (y+270 )%1080 elif x%4 ==2 : y1 = (y+540 )%1080 elif x%4 ==3 : y1 = (y+810 )%1080 else : y1 = y num = s.split(', ' ) draw.point((x,y1), fill=(int (num[0 ]), int (num[1 ]), int (num[2 ]))) image.save('fix.jpg' ,'jpeg' )
当中一些点的排序有问题,导致图分成了四个部分= = (事实上是我错估了分辨率)
随意魔改了下脚本,虽然还是有问题,但是已经勉强能看出flag了2333333
LCTF{C0ngratulation_y0u_g0t_1t}
Misc玩完了……时间不够不想继续尝试了T_T
Pwn20
这个,gdb调试能发现,写v2时直接溢出就能覆盖到v3,v3只要为非0就是true了
直接输入长点瞎搞一下就出来了……
Pwn80
同样,输入能覆盖到v2、v3,0x63 0x65这些还都是正常的字符, 不需要用python构造输入
gdb调试时确定下相关位置就稳了
pwn基本没怎么接触过,shellcode还不会写T_T
狗带吧!
Mobile20 (Log) 题目都说着Log了……
然后我直接反编译看了下代码
log的数据写到了jni里,这么正常思维就是连上真机/虚拟机调试DDMS看log出来的flag
然后咸鱼如我直接ida了一下lib包……(才20分怎么可能写得很复杂嘛!)
然后就出来了呗
Mobile50 (Base) 说实话这题我不知道考点在哪……代码混淆吗……
毕竟也就50分
就是这段代码
检测是否调试器链接,然后toast输出flag
懵逼了很久……后来我甚至发现了那算法就是base32阿噗
流程就是把写死在程序里的key base32变换一下输出,key转换一下顺序再base32 一下输出
结果就是直接装上真机,按一下按钮flag就出来了……
Mobile100 (Smali) 下载下来就是一个Smali代码
而且是经过一定的处理,不能用Smali2Java之类的工具转换成java代码
那么就直接阅读吧(真棒!!!刚好给我练一下阅读smali代码!)
smali代码指令可以到这查询
http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
很关键的几个字符串AES/ECB/PKCS5Padding MD5
最终结果是
1 2 00000080 const-string v6, "falg" 00000084 invoke-static Log->d(String, String)I, v6, v4
Log输出flag(唔,为什么是falg)
代码不长,大致流程就是
字符串this_is_your_key ,传进Encrypt.handle()函数
用这返回结果作为AES加密的秘钥,
再用这返回结果调用AESEncrypt.Encryption()函数
最后加密结果把byte转回为16进制String输出
分析一下handle函数,其实就是把奇数位和偶数位的字符交换一下
由此复原函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import java.io.UnsupportedEncodingException;public class Learn { public static String handle (String key) throws UnsupportedEncodingException { StringBuilder stringBuilder = new StringBuilder(); for (int i=0 ;i<key.length();i+=2 ){ stringBuilder.append(key.charAt(i+1 )); stringBuilder.append(key.charAt(i)); } return stringBuilder.toString(); } public static void main (String[] args) throws Exception { String key = "this_is_your_key" ; String keyChange = handle(key); System.out.println(keyChange); CryptoTools cipher = new CryptoTools(keyChange.getBytes()); byte [] result = cipher.aesEncryptData(keyChange.getBytes()); System.out.println(CryptoTools.byte2HexString(result)); } }
其中CryptoTools是自己写好的相应的AES加密的类
跑一下出结果
184b4e1bc353510f7841bb126d94259449055ef296e3b665dac507032e4c01e1
值得一提的是,在初始化秘钥的函数有点混淆的地方
就是当传入秘钥参数为空时会把空字符串求MD5作为秘钥,这是库中的一点处理,正常是空字符串求不了的吧……
刚开始还在这卡了点时间,怎么能如此的不熟练
然后就是眼瞎把最后加密用的字符串或秘钥各种看错,把key换成keyChange、keyChange换成key啥的阿噗,ctf真是个考验眼睛的比赛
估计是考虑到科普向,mobile的题就这三题(这才是我主场啊喂)
不过,我也蜜汁ak了re红红火火恍恍惚惚
Re60 跑一下程序运行时让输入flag,正确就会输出对应的flag
直接拖到ida用f5神器看一下
calLenEqual32,函数名说得挺清楚的
然后进到里面也是一个很简单的判断,输入长度为32即返回5
其中sub_3c4d函数就是很简单的一个strcmp函数,相等即返回5,输出flag
所以关键就是在sub_3c4c函数中
分析可知第一个参数是输入的字符串,第二个参数是内存中一段数据,第三个是结果
进到该函数看
在OD中调试能看到strupr是把字符串都转换为大写的函数
iHenStrlenLow是数据字符串的长度,Gap那个很复杂,但input和bc2e(自己改的名)长度应该是相同的,所以都不用管。
关键就是中间一段循环,调用的两个函数
两个函数也很简单,判断输入大小跟64和9的关系,把输入的字符-55、-48或+55、+48
我一开始写解密函数时还想着改写下这两个函数,仔细一看55+9=64,这两个函数时互补的
在OD调试中看到硬编码的字符串,长度32,直接写。
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 #include <bits/stdc++.h> using namespace std;char str[] = "bc2e0d7f253d7fff9f9d56053d051f0b" ;char fun (char in) { return in<=64 ? in-48 : in-55 ; } char fun2 (char in) { return in<=9 ? in+48 : in+55 ; } void toUpper () { for (int i=0 ;i<32 ;i++){ str[i] = toupper (str[i]); } } int main () { char result2[32 ]; toUpper (); for (int i=0 ;i<32 ;i++){ char tmp; rel[i] = fun (rel[i]); tmp = rel[i] ^ fun (str[i]); result2[i] = fun2 (tmp); } for (int i=0 ;i<32 ;i++){ cout<<result2[i]; } cout<<endl; return 0 ; }
跑出来flag是AB7E032568F1084CD8C78B7650AE30BF
Re100 (Easy GUI) windows下的gui逆向,输入正确的password
一般来说有两种定位的思路,一是查找相应的字符串,二是在GetDlgItem这类获取控制句柄的winapi中下断点
这里字符串一下子看不到有用的信息,于是在GetDlgItem下断点,OD跑出来
然后就可以定位到关键函数了,这里发挥自己的想象瞎搞一波
可以直接到ida相关地方f5
也可以直接OD动态调试看寄存器
像这样,稍微阅读汇编发现password长度为13
cmp地方下个断点,反复跑13次程序,就能骗出flag了,这样解密程序都不用写233333
flag: w1napi_1s_old
Re150 (Easy Linux) 拖到linux中跑来看看
结果发现data.dat文件不一起放过去的话是跑不动的
所以可以猜想一部分代码在data.dat中
然后拖进ida就发现了checkpassword函数
非常简单的逻辑,跳转到secret的地方,获得13位数据
写个程序跑一下就出来了
1 2 3 4 5 6 7 8 9 10 11 12 #include <bits/stdc++.h> using namespace std;char str[] = {0xFF ,0xA0 ,0xAF ,0xFC ,0xA1 ,0xA9 ,0xFE ,0x80 ,0xA5 ,0xA2 ,0xB9 ,0xB4 };int main () { for (int i=0 ;i<13 ;i++){ printf ("%c" ,str[i]^0xcc ); } return 0 ; }
flag W3lc0me2Linux
Re200 (壶中的大银河~Easy~) 然后后面这几题就真的学到很多东西了
这题提示着linux signal机制
ida看到main函数很简单,主要有alarm、pause函数
注意在gdb调试中,默认alarm信号是不传进程序的,会导致程序阻塞在pause的地方
学习了一轮signal机制,知道了这么一回事
signal函数 void (*signal (int signo, void (*handler)(int)))(int);
在接受到signo的信号后,会跳转到第二个参数(一个函数指针)去处理
这里主程序是发出了alarm的信号
ida中搜了一下signal,就能发现这个函数
跳转到handler里面
然后查看各个地址,0x8049B40中获得奇怪的字符串一个
写个程序就跑出来了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <bits/stdc++.h> using namespace std;char cmp[] = "8BVXznh]z^VXdAfC}PgE" ;int main () { char rel[20 ]; for (int i=0 ;i<20 ;i++){ if (i & 1 ){ rel[i] = cmp[i] ^ rel[0 ]; } else { rel[i] = cmp[i] ^ 9 ; } } for (int i=0 ;i<20 ;i++){ printf ("%c" ,rel[i]); } printf ("\n" ); return 0 ; }
flag: 1s_is_also_important
Re250 (蓬莱的玉枝) 也是一个winGUI
跟之前同样道理,定位到关键函数,用ida查看
这个函数非常简单!而且Seed是写死的,rand()每次运行程序都一样
那么思路就是获得0x0041c010的数据,写个程序跑出来
嗯……然后出来一堆奇怪的东西,所以这样不对吧
然后用OD调试,发现长度27,到这里会有判断……
又是累一点下个断点一次次跑然后出结果2333333
后来再想其实那个程序为什么会不对呢,有可能在某个函数对那块空间处理了一下
在OD中看那块地址空间果然跟原来的不一样了
后来果然找到了那个函数
咦……IsDebuggerPresent()函数
如果检测到在调试模式下变换出来的字符串是错误的
结果我就发现……用OD调试时IsDebuggerPresent()函数返回的是非!厉害了OD
同时用ida调试了一下,出来的是非0值,检测到在调试,再修改下程序,就跑出来了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <bits/stdc++.h> using namespace std;char ch[] = {0x12 ,0x11 ,0xE2 ,0x86 ,0xA1 ,0x66 ,0xA9 ,0x94 ,0xCE ,0xC0 ,0x47 ,0x4F ,0x8E ,0x5E ,0x98 ,0x96 ,0x9D ,0x00 ,0x25 ,0xB4 ,0x2C ,0xC4 ,0x89 ,0x56 ,0x97 ,0x88 ,0x33 };int main () { srand (17 ); for (int i=0 ;i<27 ;i++){ printf ("%c" ,(ch[i] ^ (rand () % 0xFF u))); } printf ("\n" ); return 0 ; }
LCTF{learn_more_think_more}
Re350 (永遠の春夢) 嗯……做的时候提示还没出来
分析出来后成就感满满的啊噢!
这个比较麻烦我就直接用ida调试了,先看主操作函数
关键就是在sub_401621函数中了
刚开始就懵逼的发现前面有个异或的操作,后面有个a1(a4)这特么是个啥!
然后再前面传入的第一个参数一直分不清到底是数据还是程序
a2的长度刚好就是a1地址中数据的长度
这时看看汇编代码
看到一个call eax
所以那就是个函数咯
那前面异或?这是就想到动态自修改了
调试中看到这段汇编代码,这个就用不了f5了,直接阅读吧
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 .text:00401584 push ebp .text:00401584 sub_401584 endp ; sp-analysis failed .text:00401584 .text:00401585 mov ebp, esp .text:00401587 mov eax, [ebp+8] .text:0040158A mov eax, ds:dword_40EA40[eax*4] .text:00401591 shl eax, 4 .text:00401594 movzx edx, al .text:00401597 mov eax, [ebp+8] .text:0040159A mov eax, ds:dword_40EA40[eax*4] .text:004015A1 shr eax, 4 .text:004015A4 and eax, 0Fh .text:004015A7 or eax, edx .text:004015A9 movzx edx, al .text:004015AC mov eax, [ebp+8] .text:004015AF mov ds:dword_40EA40[eax*4], edx .text:004015B6 mov eax, [ebp+8] .text:004015B9 mov eax, dword_40A020[eax*4] .text:004015C0 lea ecx, [eax+30h] .text:004015C3 mov edx, 80808081h .text:004015C8 mov eax, ecx .text:004015CA mul edx .text:004015CC mov eax, edx .text:004015CE shr eax, 7 .text:004015D1 mov edx, eax .text:004015D3 shl edx, 8 .text:004015D6 sub edx, eax .text:004015D8 mov eax, ecx .text:004015DA sub eax, edx .text:004015DC mov edx, [ebp+8] .text:004015DF mov dword_40A020[edx*4], eax .text:004015E6 mov eax, 0 .text:004015EB pop ebp .text:004015EC retn
大致就是每次取ds:dword_40EA40的值中的8位字符数据低四位与高四位交换一下
再把dword_40A020中的数据进行一点处理,这里似乎是取模?
然后结合外部f5出来的代码可以猜想对输入字符串都进行这个操作了
然后是第二个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 .text:004015ED push ebp ; DATA XREF: sub_4014C8+8Do .text:004015EE mov ebp, esp .text:004015F0 sub esp, 10h .text:004015F3 mov eax, [ebp+8] .text:004015F6 mov eax, ds:dword_40EA40[eax*4] .text:004015FD mov [ebp-4], eax .text:00401600 xor dword ptr [ebp-4], 55h .text:00401604 mov eax, [ebp+8] .text:00401607 mov eax, dword_40A020[eax*4] .text:0040160E cmp eax, [ebp-4] .text:00401611 jz short loc_40161A .text:00401613 mov eax, 0 .text:00401618 jmp short locret_40161F
很简单的异或一下,然后比较
所以就可以写程序跑了
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 #include <bits/stdc++.h> using namespace std;int chr[] = {0x61 ,0x31 ,0xdf ,1 ,0xb2 ,0x30 ,0x51 ,0x31 ,0x70 ,0x93 ,0x32 ,0x70 ,0xd2 ,0xa2 ,0x33 ,0x93 ,0xe1 ,0xd2 ,0xe2 ,0x17 ,0x52 };int chr2[21 ];int main () { for (int i=0 ;i<21 ;i++){ long long d1 = chr[i] + 0x30 ; long long e1 = d1*0x80808081 ; long long e2 = e1>>32 ; e2 = e2>>7 ; long long e3 = e2<<8 ; chr2[i] = d1-(e3-e2); chr2[i] = chr2[i]^0x55 ; int tmp = chr2[i]; int tmp2 = (tmp<<4 ) & 0xff ; int tmp3 = (tmp>>4 ) & 0xf ; chr2[i] = tmp2^tmp3; } for (int i=0 ;i<21 ;i++){ printf ("%c" ,chr2[i]); } printf ("\n" ); return 0 ; }
因为前面没分析出来mul 0x80808081的作用,所以就直接强行模拟了
后来看这结合上下代码似乎是一个取模的操作
跑出来结果
LCTF{SMC_is_excited!}
Excited!