第二届虎符CTF,分享一下逆向题解。
链接:https://pan.baidu.com/s/15CH8WMsHoaRmMeIflV3Llg 提取码:z531 复制这段内容后打开百度网盘手机App,操作更方便哦
redemption_code 32位mips架构动态链接的程序。
因为动态链接运行要指定库路径,这里就直接静态分析,这个题也足够了。
首先看到对输入字符串处理的第一个函数pre:判断长度后,一个字符串赋值操作,一起传入check。
然后server_check_redemption_code函数:创建14个表,然后我们的输入的字符串作为index在其中以此递增的做标记。最后用上面赋值的字符串作为index依次查表,直到到最后到达最后一个表才成功。
上面如果不好理解的话,可以把要得到的表打印出来看一下就清楚了。
所以说,要使第一个函数不返回-1,只要我们的输入为赋值字符串的前14个就好了。Ninja Must Die
紧接着,又是一个字符串赋值操作加一个check()函数,与前面不同的是这个check()函数的返回值要为7,计算可以知道也就是check()函数最后要寻找21长度的字符串。而赋值的字符串也变了,如何让我们的输入字符串满足2个check()函数呢。
观察一下第二个字符串的前21位:I Love Ninja Must Die ,它的后14位正好和第一个check()函数要满足的一样,而它的前7位可以跳过的,也正好满足了返回值要为7。
所以最后的flag就是:flag{Ninja Must Die}
CrackMe 程序应该是realse模式编译的,代码都杂合到一起了。做题时注意识别关键代码。
来到main函数,首先就看到了输入和长度判断,明显的stl模板的string结构。
动调看一下这个结构:
后面一直到下一次输入的的地方,动态分析下可以快速知道是将我们的输入前7位和后10位分为了2个string来存储。注意看内存。
接下来关键是第二个输入后进行浮点数运算的地方,输入数据经过浮点运算对比正确后继续进行后面的分支。
我开始直接跳过了这个判断,先看看后面做了什么。
分析得到下面是用输入的前7位和第二次的输入变换的得到的数据进行异或,
继续看下面,可以看到输入的rc4结构,且把我们的前7位做了key:
到这里就清楚程序的整体流程了。
输入位17位,前7位为key,后10位为明文。第二次输入经过浮点运输后,经过判断,正确的话将其经过有规律变换后与第一次输入的前7位进行异或运算,然后与编码的enc对比,正确的话继续后面的rc4加密。
所以现在就是求出第二次输入一个int数据。
开始想的用angr来跑局部,但不熟练😢,换用复制ida中的代码到vs中运行,爆破出这个int数据。
注意一下__m128 这个变量:
爆破代码:
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 #include <iostream> #include <stdio.h> #include <emmintrin.h> __m128 __fastcall sub_7FF64A4E1360 (double a1, double a2) { double v2; __m128 v3; v2 = a1; a1 = pow (a1, a2 - 1.0 ); *((double *)&v3 + 1 ) = *(&a1 + 1 ); *(double *)&v3 = a1 / exp (v2); return (__m128)v3; } int main () { double v96 = 100 ; double v16 = 0.0 ; double v17 = 0.0 ; double v18 = 0.0 ; double v19; int v20; double v21, v22; for (int i = 0 ; i < 0xffffffff ; i++) { double v16 = 0.0 ; double v17 = 0.0 ; double v18 = 0.0 ; v96 = i; v19 = (double )((int )v96 / 12379 ) + 1.0 ; do { v17 = v17 + *(double *)sub_7FF64A4E1360(v18, v19).m128_u64 * 0.001 ; v18 = v18 + 0.001 ; } while (v18 <= 100.0 ); v20 = (int )(v17 + v17 + 3.0 ); v21 = 0.0 ; v22 = (double )((int )v96 % 12379 ) + 1.0 ; do { v16 = v16 + *(double *)sub_7FF64A4E1360(v21, v22).m128_u64 * 0.001 ; v21 = v21 + 0.001 ; } while (v21 <= 100.0 ); printf ("%d\n" , i); if (v20 == 0x13B03 && (int )(v16 + v16 + 3.0 ) == 0x5A2 ) break ; } }
虽然不是很快,但还是跑出来了:99038
接下来动态得到要与第一次输入前7位进行异或的数据。开始是99038,后面都是乘2后的字符串。
进行异或解密:
1 2 3 4 5 >>> s = [57 , 57 , 48 , 51 , 56 , 49 , 57 ]>>> t = [8 , 77 , 89 , 6 , 115 , 2 , 64 ]>>> ans = [s[i]^t[i] for i in range(7 )]>>> bytes(ans)b'1ti5K3y'
这就得到了key。
然后就是进行rc4解密,这个直接动调修改输入数据为密文数据进行一次加密变得到明文了。
最后输入得到flag:flag{1ti5K3yRC4_crypt099038}
再看题
因为当时做题时,没有注意细节或者方法什么的,哪里爆破的很慢,现在是我后面对这个题再次学习写下的。
首先是对__m128数据类型的重新认识,它是一个联合,这里它的成员:
所以题目中的.m128_u64就是取低8字节。
exp()是求e的次方的值。pow(a1, a2-1)/exp(a1)
另外这里是除12379,第一次是用商,第二次用余数,所以这里爆破的步长应该是每次加12379,确定了商,再确定余数就很快了。。
正确的爆破脚本:
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 #include <stdio.h> #include <math.h> int main (void ) { int i, ans; double a = 0 , b = 0 , c; for (i = 0 ; i < 0xffffffff ; i += 12379 ) { a = 0 , b = 0 ; c = i/12379 ; do { a = a + (pow (b, c)/exp (b))*0.001 ; b += 0.001 ; }while (b <= 100 ); ans = (int )(a+a+3 ); if (ans == 80643 ) { printf ("found: %d\n\n" , i); break ; } else printf ("%d\n" , i); } for (i = 0 ; i < 12379 ; i++) { a = 0 , b = 0 ; c = i; do { a = a + (pow (b, c)/exp (b))*0.001 ; b += 0.001 ; }while (b <= 100 ); ans = (int )(a+a+3 ); if (ans == 0x5A2 ) { printf ("found: %d\n" , i); break ; } else printf ("%d\n" , i); } }
其次还有一种用使用idapython来爆破,实质就是调用ida调试器中的函数来控制程序的流程和寄存器值等,为了让自己用的函数有一个统一规范,我统一使用ida_dbg模块的函数(idapython封装过的其实也不错):运行脚本直接得到结果。
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 from ida_dbg import *from ida_bytes import *def init () : rsp = get_reg_val("rsp" ) set_reg_val("rip" , 0x140001768 ) patch_qword(rsp+0x40 , i) i = 0 run_to(0x140001658 ) wait_for_next_event(WFNE_SUSP, -1 ) init() while (True ): print(">>> i: %d" %i) run_to(0x14000184E ) wait_for_next_event(WFNE_SUSP, -1 ) ebx = get_reg_val("ebx" ) eax = get_reg_val("eax" ) if ebx != 80643 : i += 12379 init() elif eax != 1442 : i += 1 init() else : print(">>> find: %d" %i) break exit_process()
感觉idapython里面的函数真的繁杂。。同一个功能各个模块都有。但自己要有个规范。
记一个idapython文档,需要什么都有,直接查就完事了:idapython_docs
Golang encrypter 64位,go语言写的程序,经过符号表还原后,虽然代码还是难看,但有了大多数函数名称还是好很多的。
对输入的进行的第一个检查函数:main_check(),首先判断输入字符串长度,然后引入一个正则表达式对flag的格式进行了过滤,最后提取出每部分的字符串进行连接,最后再进行一个hex()操作。
后面开始用密钥初始化一个密钥对象,从密钥长度开始还是以为aes加密,测试后发现并不是,,
关键是这里的加密还是8个一组,分2次加密,且密钥长度又是16,这就排除aes和des加密了。
然后是从myCipher字符串让我想到这应该是作者自己写的一个加密,进而进入加密函数分析: 首先从动调分析知道下面这个函数就是交换数据字节顺序,_byteswap_ulong() ,这里开始调用了2次,结束调用了2次还原。分为2组,正好取了8个字节。
接着就是关键加密模板了,说实话,难看,其实就是魔改的tea加密,分析数据时细心点。。
把上面的加密化简一下,得到:
1 2 enc[0 ] += (enc[1 ] + ((enc[1 ]>>5 )^(16 *enc[1 ]))) ^ (key[v23]+v22); enc[1 ] += (enc[0 ] + ((enc[0 ]>>5 ) ^ (16 *enc[0 ]))) ^ (key[v25]+v22+0x12345678 );
最后就是解密:注意细心吧。。这里因为加号运算符优先级大于异或,我又忘了在他们一起时给异或加一个括号,一直解密出错,真就硬看了二个小时。。。1个括号,2个小时。。
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 #include <stdio.h> int opcode[100000 ];unsigned char KEY[] = {3 , 2 , 1 , 0 , 7 , 6 , 5 , 4 , 11 , 10 , 9 , 8 , 15 , 14 , 13 , 12 }; unsigned char ENC[] = {14 , 195 , 17 , 240 , 69 , 199 , 154 , 243 , 237 , 245 , 217 , 16 , 84 , 39 , 2 , 203 }; void tea_decode () { int i = 0 , j = 0 ; unsigned int *key = (unsigned int *)KEY; for (i = 0 ; i < 2 ; i++) { unsigned int *enc = (unsigned int *)(ENC+8 *i); for (j = 31 ; j >= 0 ; j--) { enc[1 ] -= (enc[0 ] + ((enc[0 ]>>5 ) ^ (16 *enc[0 ]))) ^ (key[opcode[3 *j+2 ]]+opcode[3 *j]+0x12345678 ); enc[0 ] -= (enc[1 ] + ((enc[1 ]>>5 ) ^ (16 *enc[1 ]))) ^ (key[opcode[3 *j+1 ]]+opcode[3 *j]); } } } void swap (unsigned char enc[]) { int i = 0 ; for (i = 0 ; i < 2 ; i++) { unsigned char tmp = enc[i]; enc[i] = enc[3 -i]; enc[3 -i] = tmp; } } int main (void ) { int i = 0 ; int v17 = 0 , v18 = 0 , v22, v23, v25; while (v17 < 32 ) { v22 = v18; v23 = v18&3 ; v18 = (unsigned int )(v22+0x12345678 ); v25 = ((unsigned int )v18 >> 11 ) & 3 ; opcode[v17*3 ] = v22; opcode[v17*3 +1 ] = v23; opcode[v17*3 +2 ] = v25; v17++; } for (i = 0 ; i < 16 ; i += 4 ) { swap(ENC+i); } putchar (10 ); tea_decode(); for (i = 0 ; i < 16 ; i += 4 ) { swap(ENC+i); } printf ("flag{" ); for (i = 0 ; i < 16 ; i++) { if (i == 4 || i == 6 || i == 8 || i == 10 ) putchar ('-' ); printf ("%x" , ENC[i]); } printf ("}" ); return 0 ; }