把之前的一些writeup整理一下
看雪CTFQ2
第一题:神秘来信
签到题,没什么好说的
1 | if ( v4 < 7 && v14 == 51 && v13 == 53 && v12 == 51 && v11 + v10 + v9 == 149 ) |
判断成立即可
401353
第六题:消失的岛屿
main函数
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
逻辑非常简单,将输入base64_encode之后与常量比较,看一眼base64_encode
没什么问题,很base64,进入charEncrypt
1 | char __cdecl charEncrypt(int data) |
拿到table ‘tuvwxTUlmnopqrs7YZabcdefghij8yz0123456VWXkABCDEFGHIJKLMNOPQRS9+/‘
看到table还经过了简单的变换,直接复制出来然后解base64就搞定了
1 | import string |
KanXue2019ctf_st
第四题:达芬奇密码
ida32载入,找到入口函数
1 | int __thiscall sub_401EA0(CWnd *this) |
程序逻辑十分简单,首先判断输入字符串长度是否为十六,之后将宽字符转换为字符然后调 VirtualProtect这个API,之后qmemcpy(sub_4010E0, &byte_5647B8, 0x330u);
将一段字符复制到sub_4010E0上然后继续用调 VirtualProtect这个API,进入if语句,调用sub_4010E0函数,返回值v10 = 1时正确
看似十分复杂,确实非常复杂,静态分析是不可能的,这辈子都不可能了,果断OD动态开调,发现真正对字符串加密就只有if语句中的sub_4010E0函数,即od中的TheDaVin.002A10E0
1 | 002A1FAA |. 8D85 F0FEFFFF lea eax,[local.68] |
菜鸡只能逐行看汇编代码,再转换为人类能看懂的语言
1 | test = '???? ???? ???? ????' |
仔细一看wtf,就是解一条方程
x^2 = 7 * y^2 + 8
嗯,双曲线方程,求64位整数解
将解和table异或一下就可以得到flag了(flag = x<<64 + y,小端序)
作为一个数学蒻鶸,这怎么解得出来呢!闲着没事干搜了一下椭圆曲线,发现可以根据小整数解组推出大整数解组?果断爆破
1 | for i in range(0xffff): |
爆破得到这么几组整数解
1 | 0x6 0x2 0x24 0x1c |
蒻鶸怎么可能看得懂数学原理呢,自己动手找规律完事,依据两组小整数解组就可以理论上求出无穷大的整数解组
求解代码
1 | x1 = 0x6 |
可以得到63位和64位的解,和table异或一下
1 | 0x557f3b3b9e1a55a 0x2050988b2bd38de |
得到
1 | 0x61396b325a6d334c 0x4d4d443633613053 |
排除第二组解,求出flag
1 | '0x61396b325a6d334c'[2:].decode('hex')[::-1]+'0x4d4d443633613053'[2:].decode('hex')[::-1] |
L3mZ2k9aS0a36DMM
ps:逆向方面不难,耐心点追踪汇编代码仿佛看源码,主要是最后的解方程
RCTF babyre
main函数校验flag长度是否为16位,若是则依次对flag加密后,而后若flag正确则输出常量”Bingo!“(此处有多解)
1 | if ( v6 > 0x10 ) // flag.len == 16 |
第一个函数很简单,主要是对flag通过置换表进行置换,如 ’ab’ 则 置换为 0xab
由于小端序,所以如果我们输入是‘1234567890123456’(128bits)那么经过置换后变成一个64位的整数 0x5634129078563412(64bits)(由于置换表的缘故此处也存在多解,譬如 ‘f’ ‘F’ ‘~’ 均被置换为0xF)
第二个函数是对flag置换后形成的64位整数进行32轮的轮换加密形成一个新的64位整数X ,然后校验高8位是否小于4,并将BYTE(BYTE7(X))(X)置0
轮换加密函数很长,但是由于a2 < -1且 v27 <= 2 所以只需分析那一小部分即可
1 | if ( a2 < -1 ) |
第三个函数是一些位操作,并且验证返回值是否为 0x69E2,如果是则将X按字节从低到高异或0x17输出
题目提示输出’Bingo!’,所以将其异或回去得到一个长度为6的byte数组,(由于小端序所以是64位整数的低48位)合理猜测高8位为0x02,所以只需爆破次高8位数据即可(由此构造一个64位数经前两轮解密而后动态调试验证第三个函数返回了0x69E2且输出’Bingo!’)
题目提示 md5(rctf{your answer}) == 5f8243a662cf71bf31d2b2602638dc1d
由此可以爆破flag了,算了一下大概需要 0xff*(2^n+km)次运算(n取决于flag经过第一轮置换字母的数量,km取决了flag经过第一轮置换f的数量,k是常量)
权衡了一下先爆破第二轮加密过程的多解,第一轮默认为小写,且0xf均视为’f’
幸运的是爆破到第二个flag就出来了
rctf{05e8a376e4e0446e}
加解密脚本如下
1 | #include<iostream> |
SCTF babyre
总共有三关
第一关 三维迷宫
没啥好说的,走迷宫就是了
入口时0x73,出口是0x23
障碍物是0x2A,通道是0x2E
1 | # 三维迷宫 方向 上下左右:wsad 空间上下:xy |
第二关
字符串输入后进入一个函数加密,加密之后与 “sctf_9102“比较
跟踪加密函数发现每4个字符为一轮通过一些查找、移位、或操作将之编码为三个字符
直接脚本爆破
1 | data = [0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000003E, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000003F, 0x00000034, 0x00000035, 0x00000036, 0x00000037, 0x00000038, 0x00000039, 0x0000003A, 0x0000003B, 0x0000003C, 0x0000003D, 0x0000007F, 0x0000007F, 0x0000007F, 0x00000040, 0x0000007F, 0x0000007F, 0x0000007F, 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, 0x0000000F, 0x00000010, 0x00000011, 0x00000012, 0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, 0x00000018, 0x00000019, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000001A, 0x0000001B, 0x0000001C, 0x0000001D, 0x0000001E, 0x0000001F, 0x00000020, 0x00000021, 0x00000022, 0x00000023, 0x00000024, 0x00000025, 0x00000026, 0x00000027, 0x00000028, 0x00000029, 0x0000002A, 0x0000002B, 0x0000002C, 0x0000002D, 0x0000002E, 0x0000002F, 0x00000030, 0x00000031, 0x00000032, 0x00000033, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F] |
第三轮爆破出了三个结果,排除掉前两个,pass2为 c2N0Zl85MTAy
第三关
第三关看似很复杂,其实并不难,尤其是可以F5看伪代码
首先定义了一个长度为16的数组
1 | v8 = 0xBE; |
不难发现这是加密后的数组常量
同理得到输入也为长度为16的字符串,加密算法如下
1 | v1 = (a1[6] << 8) | (a1[5] << 16) | (a1[4] << 24) | a1[7]; |
sub_55FF5657978A类似于将之改为大端序
重点是sub_55FF5657A43B函数,函数体如下
1 |
|
由此得到
pass1 : ddwwxxssxaxwwaasasyywwdd
pass2 : c2N0Zl85MTAy
pass3 : fl4g_is_s0_ug1y!
最终flag为 sctf{ddwwxxssxaxwwaasasyywwdd-c2N0Zl85MTAy(fl4g_is_s0_ug1y!)}
ps : 第一关其实输入为 sxss sxsads……都是可以check的,但是并不是基于三维迷宫来做的,而题目又提示plz tell me the shortest password1实在坑了好久