0x01 babyre 这个程序的关键在于几个线程的调用,和key的值。
首先key 的值在主函数的异或前是先赋值过的,不是原本静态存储的那个值,可以通过动调得到
1 0x77, 0x74, 0x78, 0x66, 0x65, 0x69
然后关于这个线程的调用我也不是很懂,大概就是创建四个线程,每个线程里存有各自的函数的地址,然后按顺序依次调用,调用了一个线程后该线程对应的信号量减一(wait),下一个线程的信号量加一(post),这时就调用下一个线程的函数,依次循环,直到全局变量 i 为31停止,最后退出线程,四个线程总共循环8次(按理来说,因为flag是32位,全部加密一轮需要8*4=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 #include <iostream> #include <string.h> __int32 enc[33 ] = {12052 ,78 ,20467 ,109 ,13016 ,109 ,27467 ,-110 , 9807 ,91 ,21243 ,-100 ,11121 ,20 ,10863 ,-107 ,10490 ,29 ,10633 ,-101 , 10420 ,78 ,17670 ,-38 ,6011 ,-4 ,16590 ,125 ,10723 , 15 ,7953 ,255 , 250 }; __int32 key[6 ] = {0x77 , 0x74 , 0x78 , 0x66 , 0x65 , 0x69 }; int main () {int i = 31 ; while (i >= 0 ){ enc[i] ^= (enc[i + 1 ] - key[(i + 1 ) % 6 ]); i--; enc[i] /= (enc[i + 1 ] + key[(i + 1 ) % 6 ]); i--; enc[i] += (key[(i + 1 ) % 6 ] ^ enc[i + 1 ]); i--; enc[i] -= (key[(i + 1 ) % 6 ] * enc[i + 1 ]); i--; } for (int j=0 ;j<32 ;j++){ printf ("%c" ,enc[j]); } return 0 ; }
hgame{you_are_3o_c1ever2_3Olve!}
0x02 ezcpp 一进去看了一眼就是TEA加密,不过这个TEA加密和普通的不太一样,他是依次分别取input(char类型)(用户输入值)的下标为0和4,1和5,2和6,3和7的首地址,然依此地址为最低位,向后取3个char值和自己本身拼合成32位的int数,比如0x11,0x22,0x33,0x44,就以小端序存储成0x44332211。
在这个加密里,取flag[0]的地址解引用,然后传入某个变量v,其实就是将flag0,1,2,3 的值以上述方式拼合成int类型然后传入变量v,知道这个后,其实不难发现该加密只加密了前11个字符(因为以flag[7]的地址加密,最多拼合7到10,总共4个字符成int,也就是最多加密到flag[10]也就是第11的字符)
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 #include <iostream> #include <string.h> int flag[32 ] = {0x88 , 0x6A , 0xB0 , 0xC9 , 0xAD , 0xF1 , 0x33 , 0x33 , 0x94 , 0x74 , 0xB5 , 0x69 , 0x73 , 0x5F , 0x30 , 0x62 , 0x4A , 0x33 , 0x63 , 0x54 , 0x5F , 0x30 , 0x72 , 0x31 , 0x65 , 0x6E , 0x54 , 0x65 , 0x44 , 0x3F , 0x21 , 0x7D }; int v20,v21,v18,v19,v10,v9,v5,v6,sum,delta;int key1 = 2341 , key2 = 1234 , key3 = 4123 , key4 = 3412 ;void trans (int v,int i) { flag[i] = v & 0xff ; flag[i+1 ] = (v>>8 ) & 0xff ; flag[i+2 ] = (v>>16 ) & 0xff ; flag[i+3 ] = (v>>24 ) & 0xff ; } int main () { delta = -559038737 ; sum = delta * 32 ; v20 = flag[3 ]|(flag[4 ]<<8 )|(flag[5 ]<<16 )|(flag[6 ]<<24 ); v21 = flag[7 ]|(flag[8 ]<<8 )|(flag[9 ]<<16 )|(flag[10 ]<<24 ); for (int i = 0 ;i < 32 ;i++){ v21 -= (sum + v20) ^ (key3 + 32 * v20) ^ (key4 + 16 * v20); v20 -= (sum + v21) ^ (key1 + 32 * v21) ^ (key2 + 16 * v21); sum -= delta; } trans (v20,3 ); trans (v21,7 ); sum = delta * 32 ; v18 = flag[2 ]|(flag[3 ]<<8 )|(flag[4 ]<<16 )|(flag[5 ]<<24 ); v19 = flag[6 ]|(flag[7 ]<<8 )|(flag[8 ]<<16 )|(flag[9 ]<<24 ); for (int i = 0 ;i < 32 ;i++){ v19 -= (sum + v18) ^ (key3 + 32 * v18) ^ (key4 + 16 * v18); v18 -= (sum + v19) ^ (key1 + 32 * v19) ^ (key2 + 16 * v19); sum -= delta; } trans (v18,2 ); trans (v19,6 ); sum = delta * 32 ; v9 = flag[1 ]|(flag[2 ]<<8 )|(flag[3 ]<<16 )|(flag[4 ]<<24 ); v10 = flag[5 ]|(flag[6 ]<<8 )|(flag[7 ]<<16 )|(flag[8 ]<<24 ); for (int i = 0 ;i < 32 ;i++){ v10 -= (sum + v9) ^ (key3 + 32 * v9) ^ (key4 + 16 * v9); v9 -= (sum + v10) ^ (key1 + 32 * v10) ^ (key2 + 16 * v10); sum -= delta; } trans (v9,1 ); trans (v10,5 ); sum = delta * 32 ; v5 = flag[0 ]|(flag[1 ]<<8 )|(flag[2 ]<<16 )|(flag[3 ]<<24 ); v6 = flag[4 ]|(flag[5 ]<<8 )|(flag[6 ]<<16 )|(flag[7 ]<<24 ); for (int i = 0 ;i < 32 ;i++){ v6 -= (sum + v5) ^ (16 * v5 + key4) ^ (32 * v5 + key3); v5 -= (sum + v6) ^ (16 * v6 + key2) ^ (32 * v6 + key1); sum -= delta; } trans (v5,0 ); trans (v6,4 ); for (int i = 0 ;i < 32 ;i++){ printf ("%c" ,flag[i]); } return 0 ; }
0x03 arithmetic 先upx脱壳,由于被改了特征值导致没法用工具upx -d脱壳,用010editor打开,然后修改,将3E0h行的首四个十六进制数改成55 50 58 21,对应字符UPX!,若没有这个特征值将无法upx -d脱壳。
关于upx加壳的文章
之后便是求解数塔问题了,根据提示是求最大路径和,左1右2,在大佬朋友的帮助下也是把脚本弄出来了。
out里面存着数塔的数据,500行,粘贴到崩溃。
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 #include <iostream> #include <algorithm> using namespace std;int n;int a[1000 ][1000 ];int step[1000 ];int b[1000 ][1000 ];int main () { cin >> n; for (int i = 0 ; i < n; i++){ for (int j = 0 ; j <= i; j++){ cin >> a[i][j]; b[i][j] = a[i][j]; } } for (int i = n - 2 ; i >= 0 ; i--){ for (int j = 0 ; j <= i; j++){ a[i][j] += max (a[i + 1 ][j], a[i + 1 ][j + 1 ]); } } int x = 0 , y = 0 ; step[0 ] = b[0 ][0 ]; for (int i = 0 ; i < n - 1 ; i++){ if (a[x + 1 ][y] > a[x + 1 ][y + 1 ]){ step[i + 1 ] = b[x + 1 ][y]; x++; cout<<"1" ; } else { step[i + 1 ] = b[x + 1 ][y + 1 ]; x++; y++; cout<<"2" ; } } return 0 ; }
得出1,2串后,再去在线网站md5加密一下就是flag值了
在线md5加密解密网站
hgame{934f7f68145038b3b81482b3d9f3a355}
0x04 findme 文件用ida打开,两个fake_flag,然后就不知道该怎么做了,经过式神的hint,才知道put(buffer)的端倪。
上网查MZ,出现dos头和pe文件等的东西,可以找这篇文件了解一下,讲的很好:PE文件详解
用010editor打开文件,果不其然,文件的前面果然有mz头等数据文件
记住这些东西对接下来的逆向有很大的帮助。
上文提到buffer,从这个地址开始,有M…Z等字符,猜测是把一个可执行PE文件用某些方式藏在了数据段里,在010editor里查找4D 00 00 00 5A 00 00 00,果不其然,确实有这样一个东西。
而且我惊喜地发现,这些数据文件竟然就是dos头的那些数据,只不过把原本8位的数据用32位的方式存储了,也就是这里显示出来的,一个有用十六进制数再加6个0。从上往下,搜寻一下这种存储方式的所有数据,把他们全部复制到新的文件里(注意要复制直到没有这种存储规则的数据),我这里命名为 1 。(随便名的),接下来就是用个脚本删去这些无意义的00 00 00了。
由于不会写修改二进制文件的脚本,我干脆用python把他读取出来,然后再把读出来的文件粘贴到一个十六进制文本文件里。
1 2 3 4 5 6 7 8 cnt = 0 with open (r"C:\Users\28382\Desktop\1" ,'rb' ) as file: content = file.read() for i in content: if cnt % 4 == 0 : print (hex (i)[2 :]) cnt += 1
运行后的十六进制数直接粘贴到010editor里新建的十六进制文本文件就好了。
注意:粘贴十六进制数要用shift+ctrl+V,不然就会粘贴成文本
然后保存,用die查一下文件类型,
真是非常amazing啊,变成了应该PE文件,这下就可以用ida开始逆向了。
打开后去除一堆花指令,(这里只有jz和jnz),然后f5看看伪代码。有两个函数,看着像是rc4加密。
直到我发现有负数下标的数组,我就知道这东西不简单,
以input的首地址为基准,负数代表向后取地址,调试后发现input后面存放的便是key数组,由此推断result的取值和你input什么东西是没有任何关系的,因为result 的值都来自于key数组,而且向后取地址的多少也取决于key,也就是这个-(v4+ key[v1]) 的值,只和key数组有关系。动调把result 的值都跑出来,然后用enc减去他就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> int enc[32 ] = {0x7D , 0x2B , 0x43 , 0xA9 , 0xB9 , 0x6B , 0x93 , 0x2D , 0x9A , 0xD0 , 0x48 , 0xC8 , 0xEB , 0x51 , 0x59 , 0xE9 , 0x74 , 0x68 , 0x8A , 0x45 , 0x6B , 0xBA , 0xA7 , 0x16 , 0xF1 , 0x10 , 0x74 , 0xD5 , 0x41 , 0x3C , 0x67 , 0x7D }; int result[32 ] = {0x15 ,0xC4 ,0xe2 ,0x3c ,0x54 ,0xf0 ,0x4d ,0xc1 , 0x6a ,0x59 ,0x15 ,0x56 ,0x78 ,0xf2 ,0x18 ,0x77 ,0x41 ,0x09 , 0x34 ,0xe0 ,0xf9 ,0x41 ,0x48 ,0xb0 ,0x7f ,0xdc ,0xd ,0x63 ,0xe0 , 0xce ,0xf3 ,0x0 }; int temp;int main () { for (int i = 0 ;i <32 ;i++){ temp = enc[i] - result[i]; if (temp < 0 ){ temp += 256 ; } printf ("%c" ,temp); } return 0 ; }
0x05 mystery 这题一打开就发现是个RC4加密。
这里的key实际上被修改过,按x查找引用,
这些全都不管,反正大概知道这么个事情,然后直接点开sub_1500 的加密函数,和rc4相差的地方在于最后一步是减法而不是异或,原理一样,直接开虚拟机动调把每一轮的result跑出来,然后直接和密文相加就能得到flag了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 result = [0x18 ,0x25 ,0x29 ,0x20 ,0x19 , 0x27 ,0xb9 ,0xc9 ,0x34 ,0xc7 , 0x71 ,0xc9 ,0xac ,0x17 ,0xb4 , 0x1e ,0xe5 ,0xe9 ,0xfc ,0x2a , 0x4a ,0x01 ,0xea ,0x79 ,0xc7 , 0x82 ,0xfe ,0x51 ,0xe7 ,0xb1 , 0xae ,0x28 ] enc = [0x50 , 0x42 , 0x38 , 0x4D , 0x4C , 0x54 , 0x90 , 0x6F , 0xFE , 0x6F , 0xBC , 0x69 , 0xB9 , 0x22 , 0x7C , 0x16 , 0x8F , 0x44 , 0x38 , 0x4A , 0xEF , 0x37 , 0x43 , 0xC0 , 0xA2 , 0xB6 , 0x34 , 0x2C ] flag = '' for i in range (28 ): flag += chr ((enc[i] + result[i]) & 0xff ) print (flag)
0x06 change 其实这题挺简单的,但是加了很多混淆视听的东西。
题目里有大量的这种函数,一开始看着懵懵的,后面调试才发现这是一个取地址的函数,第一个参数是地址,第二个参数是偏移量,返回偏移后的地址,实际上就是一个数组enc[i]。
加密函数里也一堆这种逻辑的函数,这里简化后就是分别取input[i]和key[i%key_len],然后进去beep加密,对,按理来说应该是这样的,不过不知道为什么,这个beep函数里面调用的那个函数里面啥也没有,静态上是一个变量,没有被赋值。这里猜测是使用了函数指针,在之前给这个beep里面的变量赋值了一个函数指针,向上查找还真有,这里的sub_7FF7A…的函数,点进去,将传入的函数指针,也就是这个sub…443650,赋值给了beep里面的变量,然后通过函数指针调用这个加密函数。
点进sub…443650就能发现是什么了,另外一个是a2 ^ a1,没有加10,判断一下i的奇偶就行了。
1 2 3 4 5 6 7 8 9 10 11 enc = [0x13 , 0x0A , 0x5D , 0x1C , 0x0E , 0x08 , 0x23 , 0x06 , 0x0B , 0x4B , 0x38 , 0x22 , 0x0D , 0x1C , 0x48 , 0x0C , 0x66 , 0x15 , 0x48 , 0x1B , 0x0D , 0x0E , 0x10 , 0x4F ] key = "am2qasl" flag = "" for i in range (24 ): if i % 2 == 0 : enc[i] -= 10 flag += chr (ord (key[i%7 ])^enc[i]) print (flag)