[toc]
加花指令程序分析
程序功能 根据解除花指令,反编译后的代码分析可知,该程序获取用户的一个输入,然后对该输入进行三次运算,每一次都是先将输入与0x1453异或,然后左移一位,得到最后的结果. 如果这个结果等于50138则输出win.
花指令分析 首先,在函数列表里没有main函数,但是汇编里面有,说明main函数没有被正确反汇编,这里有问题,
在main前面按p解析函数发现报错,
1 2 3 4 5 6 7 8 9 10 11 12 .text:0000000000001257: The function has undefined instruction/data at the specified address. Your request has been put in the autoanalysis queue. .text:0000000000001253 jz short label2 .text:0000000000001255 jnz short label2 .text:0000000000001255 ; --------------------------------------------------------------------------- .text:0000000000001257 db 0E9h
可以发现前面有 jz和jnz,肯定不会执行到1257,1257这里是花指令,改成nop即可
edit - patch program - change word 把E9 改成90 (0x90也就是nop,什么也不执行,往下走 即可)
此时这里还是代表数据,用快捷键c转换成代码,然后再在开头用p转换成函数即可
然后就可以F5反编译
1 2 3 4 5 6 7 8 9 10 11 12 13 int __cdecl main (int argc, const char **argv, const char **envp) { unsigned int v4; unsigned __int64 v5; v5 = __readfsqword(0x28 u); v4 = 0 ; printf ("Input a number: " ); __isoc99_scanf("%d" , &v4); if ( (unsigned int )encrypt(v4) == 50138 ) printf ("win" ); return 0 ; }
可以开到,关键函数是encrypt,进入这里,发现这里面也有问题,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void __fastcall encrypt (unsigned int a1) { if ( a1 > 0x1000 ) exit (0 ); JUMPOUT(0x11EC LL); } .text:00000000000011 EB loc_11EB: ; CODE XREF: encrypt+58 ↓j .text:00000000000011 EB ; encrypt:loc_11EB↑j .text:00000000000011 EB EB FF jmp short near ptr loc_11EB+1 ; Jump .text:00000000000011 ED ; --------------------------------------------------------------------------- .text:00000000000011 ED C0 FF C8 sar bh, 0 C8h ; Shift Arithmetic Right .text:00000000000011F 0 8B 45 FC mov eax, [rbp+var_4] .text:00000000000011F 3 31 45 EC xor [rbp+var_14], eax ; Logical Exclusive OR .text:00000000000011F 6 D1 45 EC rol [rbp+var_14], 1 ; Rotate Left .text:00000000000011F 9 83 45 F8 01 add [rbp+var_8], 1 ; Add
通过gdb动态调试发现, jmp short near ptr loc_11EB+1 是要跳到00000000000011EC这里,(动态运行的话是0x5555555551ec,要加上装载地址),然后往下解析, 但是在ida里面,由于这条指令的存在,它会从00000000000011ED往后解析,(不太清楚这里面的机制原理),而jump到000011EC这里就jump错了地址,所以反编译有问题.
1 2 3 4 5 6 7 8 9 10 11 0x5555555551fd <encrypt+84 > cmp dword ptr [rbp - 8 ], 2 0x555555555201 <encrypt+88 > jle encrypt+66 <encrypt+66 > ↓ 0x5555555551eb <encrypt+66 > jmp encrypt+67 <encrypt+67 > ↓ ► 0x5555555551ec <encrypt+67 > inc eax 0x5555555551ee <encrypt+69 > dec eax 0x5555555551f0 <encrypt+71 > mov eax, dword ptr [rbp - 4 ] 0x5555555551f3 <encrypt+74 > xor dword ptr [rbp - 0x14 ], eax 0x5555555551f6 <encrypt+77 > rol dword ptr [rbp - 0x14 ], 1 0x5555555551f9 <encrypt+80 > add dword ptr [rbp - 8 ], 1
所以要想解决的话,有多个办法,一个是,jmp 到0000011ED就可以了,将FF改成00,也就再往前走一步,这样就可以了,第二个办法是都改成nop就可以了,让指令往下滑.
反编译结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void __fastcall encrypt (unsigned int a1) { char v1; unsigned int v2; int i; v2 = a1; if ( a1 > 0x1000 ) exit (0 ); for ( i = 0 ; i <= 2 ; ++i ) { v1 >>= 7 ; v2 = __ROL4__(v2 ^ 0x1453 , 1 ); } }
a1是输入的值,首先a1不能大于4096, 然后v1右移7位,v2 等于 v2和0x1453异或后 左移1位,( rol dword ptr [rbp - 0x14], 1)
最后的结果是50138,逆向推导,右移一位,异或0x1453,这个操作做三次,就得到了答案789
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> int main () { int input = 50138 ; input >>= 1 ; input = input ^ 0x1453 ; input >>= 1 ; input = input ^ 0x1453 ; input >>= 1 ; input = input ^ 0x1453 ; printf ("%d" ,input); return 0 ; }
运行截图
re1 asm 得到一个asm.s文件,汇编文件,查看可以知道是由test.c汇编而来,可以选择硬读汇编来解,也可以进行编译成二进制文件后,再反汇编反编译成伪代码进行查看.
gcc asm.s 得到二进制文件 a.out,然后拖进ida进行反编译,得到结果
1 2 3 4 5 6 7 int __cdecl main (int argc, const char **argv, const char **envp) { if ( a == 29488 ) return puts ("you win" ); else return puts ("you lose" ); }
re2 bits 概要 这是一道ctf的逆向题目,给了二进制文件和远程服务器连接方式.二进制文件是elf 64位,通过给程序一个正确的输入,当验证输入正确的时候,会从flag文件中读取flag.flag文件在远程服务器上,所以在本地调试好后要和服务器进行交互拿到flag.
先对程序进行了逆向分析,对算法进行逆向编写,编写过程比较困难,最后失败.采取爆破的方法,利用angr工具求解.
程序分析 程序伪代码 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 void __fastcall __noreturn main (int a1, char **a2, char **a3) { __pid_t v3; __pid_t v4; int v5; unsigned int v6; unsigned int v7; unsigned int size; unsigned int size_4; void *ptr; void *s; FILE *stream; unsigned __int64 v13; v13 = __readfsqword(0x28 u); if ( ptrace(PTRACE_TRACEME, 0LL , 1LL , 0LL ) < 0 ) { puts ("Do not trace me!" ); exit (0 ); } v3 = getpid(); v4 = getsid(v3); if ( v4 != getppid() ) { puts ("Do not trace me!" ); exit (1 ); } ptr = 0LL ; s = 0LL ; v7 = sub_BFA(); stream = fopen("code" , "rb" ); if ( !stream ) { fwrite("Could not open file code!\n" , 1uLL , 0x1A uLL, stderr ); exit (1 ); } fseek(stream, 0LL , 2 ); size = ftell(stream); fseek(stream, 0LL , 0 ); ptr = malloc (size); if ( fread(ptr, size, 1uLL , stream) != 1 ) { fwrite("Error reading file code\n" , 1uLL , 0x18 uLL, stderr ); exit (1 ); } size_4 = sub_C31(v7, size, (__int64)ptr); printf ("Your number is %u\n" , size_4); printf ("Your answer: " ); __isoc99_scanf("%d" , &v6); v5 = sub_C31(v6, size, (__int64)ptr); if ( size_4 == v5 ) { puts ("Congrats!" ); s = malloc (0x40 uLL); memset (s, 0 , 0x40 uLL); stream = fopen("flag.txt" , "rb" ); fgets((char *)s, 64 , stream); puts ((const char *)s); free (s); exit (0 ); } puts ("Thanks for coming!" ); free (ptr); exit (0 ); }
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 __int64 __fastcall sub_C31 (unsigned int a1, int size, __int64 ptr) { int v3; int v4; unsigned __int8 v7; unsigned __int8 v8; unsigned __int8 v9; unsigned __int8 v10; unsigned __int8 v11; char v12; int v13; unsigned int v14; unsigned int v15; int v16; int v17; int i; v17 = 0 ; v16 = 0 ; v15 = 0 ; v14 = 0 ; v13 = 0 ; for ( i = 0 ; i < size; ++i ) { v7 = *(_BYTE *)(i + ptr); if ( (v7 & 1 ) != 0 ) { a1 ^= dword_202020[v13]; v13 = ((_BYTE)v13 + 1 ) & 0xF ; } v8 = v7 >> 1 ; v3 = v8 & 3 ; if ( v3 == 2 ) { v15 = dword_202020[v13] & 0xAABBCCDD ; v13 = ((_BYTE)v13 + 1 ) & 0xF ; v9 = v8 >> 2 ; } else if ( v3 == 3 ) { a1 += v14 + v15; v15 = 0 ; v14 = 0 ; v9 = v8 >> 2 ; } else { if ( v3 == 1 ) { v14 = dword_202020[v13] | 0xABCDABCD ; v13 = ((_BYTE)v13 + 1 ) & 0xF ; } v9 = v8 >> 2 ; } if ( (v9 & 1 ) != 0 ) a1 = ~a1; v10 = v9 >> 1 ; if ( (v10 & 1 ) != 0 ) a1 ^= (((a1 << 16 ) ^ a1) >> 16 ) ^ (a1 << 16 ); v11 = v10 >> 1 ; v4 = v11 & 3 ; if ( v4 == 2 ) { v17 = dword_202020[v13] - 539034144 ; v13 = ((_BYTE)v13 + 1 ) & 0xF ; v12 = v11 >> 2 ; } else if ( v4 == 3 ) { a1 += v16 + v17; v17 = 0 ; v16 = 0 ; v12 = v11 >> 2 ; } else { if ( v4 == 1 ) { v16 = 539034132 * dword_202020[v13]; v13 = ((_BYTE)v13 + 1 ) & 0xF ; } v12 = v11 >> 2 ; } if ( (v12 & 1 ) != 0 ) a1 = a1 - (a1 & 7 ) - (a1 & 7 ) + 7 ; } return a1; }
程序执行流程分析
关键流程在于,随机数v7和code经过计算后输出结果.需要根据结果反推随机值.
程序关键算法分析 sub_C31函数对code和随机值v7进行计算,运算过程较为复杂,包含了许多位移运算,与或运算.流程较长.采用直接逆向的方法较为困难.所以采用了angr爆破的方法.
工具使用 ida ida用于静态分析,得到反汇编代码.
gdb gdb用于动态分析,在编写利用代码以及分析程序时,可以观察运行到某一行代码时的内存、寄存器等值和状态.
angr angr是一个基于python的二进制分析框架,能够实施动态符号执行,和不同的静态分析.对本题目来说,angr可以对key的各种约束条件进行求解,从而爆破得到key的值.
解题思路 减少循环、获取key的完整计算过程 angr对于循环的处理较为复杂和缓慢,所以可以通过在混淆代码中加入输出key的运算过程的代码,得到完整的运算流程.
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 #include <stdio.h> #include <stdlib.h> #include <time.h> int hunxiao (int randnum, int size,int *ptr) { int v3; int v4; int v7; int v8; int v9; int v10; int v11; char v12; int v13; int v14; int v15; int v16; int v17; int i; v17 = 0 ; v16 = 0 ; v15 = 0 ; v14 = 0 ; v13 = 0 ; int dword_202020[16 ] = {0x24DD20CF , 0x3E4F0354 , 0x18B2E85F , 0x2F2CAFB8 , 0x5810ADCB ,0x42F7FF85 , 0x36E0D6C2 , 0x5F3EF93F , 0x7F46E74A , 0x44DDC864 ,0x64959795 , 0x39413451 , 0x5DC36C45 , 0x62037E7E , 0x5AEA541F ,0x153F8FAC }; printf ("hunxiao里面的输出%p\n" ,*ptr); printf ("hunxiao里面的输出%p\n" ,*ptr); for ( i = 0 ; i < 2 ; ++i ) { v7 = *((unsigned char *)(i + (unsigned long long )ptr)); printf ("v7的值:%x\n" ,v7); if ( (v7 & 1 ) != 0 ) { randnum ^= dword_202020[v13]; v13 = ((unsigned char )v13 + 1 ) & 0xF ; } v8 = v7 >> 1 ; v3 = v8 & 3 ; if ( v3 == 1 ) { v14 = dword_202020[v13] | 0xABCDABCD ; v13 = ((unsigned char )v13 + 1 ) & 0xF ; } if ( v3 == 2 ) { v15 = dword_202020[v13] & 0xAABBCCDD ; v13 = ((unsigned char )v13 + 1 ) & 0xF ; } if ( v3 == 3 ) { randnum += v14 + v15; v15 = 0 ; v14 = 0 ; } v9 = v8 >> 2 ; if ( (v9 & 1 ) != 0 ) randnum = ~randnum; v10 = v9 >> 1 ; if ( (v10 & 1 ) != 0 ) randnum ^= (((randnum << 16 ) ^ randnum) >> 16 ) ^ (randnum << 16 ); v11 = v10 >> 1 ; v4 = v11 & 3 ; if ( v4 == 1 ) { v16 = 539034132 * dword_202020[v13]; v13 = ((unsigned char )v13 + 1 ) & 0xF ; } if ( v4 == 2 ) { v17 = dword_202020[v13] - 539034144 ; v13 = ((unsigned char )v13 + 1 ) & 0xF ; v12 = v11 >> 2 ; } if ( v4 == 3 ) { randnum += v16 + v17; v17 = 0 ; v16 = 0 ; v12 = v11 >> 2 ; } v12 = v11 >> 2 ; if ( (v12 & 1 ) != 0 ) randnum = randnum - (randnum & 7 ) - (randnum & 7 ) + 7 ; } return randnum; } int main () { void *s;FILE *stream; int size;void *ptr;int size_4;int v0 = time(0 );srand(v0); int v2 = rand();stream = fopen("./code" ,"rb" ); fseek(stream, 0 , 2 ); size = ftell(stream); fseek(stream, 0 , 0 ); printf ("%s\n" ,stream);printf ("size:%d\n" ,size);ptr = malloc (size); if ( fread(ptr, size, 1 , stream) != 1 ) { fwrite("Error reading file code\n" , 1 , 0x18 , stderr ); exit (1 ); } printf ("在外面的输出:%x\n" ,&ptr); printf ("ptr:%p\n" ,ptr);printf ("&ptr:%p\n" ,&ptr); size_4 = hunxiao(v2,size,ptr); return 0 ;}
运行代码,得到key的处理代码
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 key ^= dword_202020[0 ]; key=~key; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key ^= dword_202020[3 ]; key = key - (key & 7 ) - (key & 7 ) + 7 ; key=~key; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key += 1256252113 ; key ^= dword_202020[6 ]; key += 636006435 ; key += 0 ; key ^= dword_202020[7 ]; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= dword_202020[10 ]; key=~key; key += 1908961232 ; key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= dword_202020[12 ]; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key += 0 ; key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= dword_202020[14 ]; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= dword_202020[1 ]; key += 3791846473 ; key=~key; key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key = key - (key & 7 ) - (key & 7 ) + 7 ; key=~key; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key += 3767795326 ; key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= dword_202020[7 ]; key += 371756133 ; key=~key; key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= dword_202020[8 ]; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key = key - (key & 7 ) - (key & 7 ) + 7 ;
编写key计算代码 需要替换y值
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 #include <stdio.h> unsigned int dword_202020[16 ] = {0x24DD20CF , 0x3E4F0354 , 0x18B2E85F , 0x2F2CAFB8 , 0x5810ADCB , 0x42F7FF85 , 0x36E0D6C2 , 0x5F3EF93F ,0x7F46E74A , 0x44DDC864 , 0x64959795 , 0x39413451 , 0x5DC36C45 , 0x62037E7E , 0x5AEA541F , 0x153F8FAC };int calc (unsigned int key) { key ^= dword_202020[0 ]; key = ~key; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key ^= dword_202020[3 ]; key = key - (key & 7 ) - (key & 7 ) + 7 ; key = ~key; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key += 1256252113 ; key ^= dword_202020[6 ]; key += 636006435 ; key += 0 ; key ^= dword_202020[7 ]; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= dword_202020[10 ]; key = ~key; key += 1908961232 ; key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= dword_202020[12 ]; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key += 0 ; key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= dword_202020[14 ]; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= dword_202020[1 ]; key += 3791846473 ; key = ~key; key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key = key - (key & 7 ) - (key & 7 ) + 7 ; key = ~key; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key += 3767795326 ; key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= dword_202020[7 ]; key += 371756133 ; key = ~key; key = key - (key & 7 ) - (key & 7 ) + 7 ; key ^= dword_202020[8 ]; key ^= (((key << 16 ) ^ key) >> 16 ) ^ (key << 16 ); key = key - (key & 7 ) - (key & 7 ) + 7 ; return key;} int main (void ) {unsigned int x = 0 ;unsigned int y = 0 ;scanf ("%u" , &x);y = calc(x); if (y == 730447668 ) { printf ("right\n" ); } else { printf ("wrong\n" ); } }
gcc -o f fk.c 得到f二进制文件
angr脚本,得到随机值 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 import angrimport claripyimport sysdef main (): print (" solving :" , sys.argv[1 ]) p = angr.Project(sys.argv[1 ], load_options={"auto_load_libs" : True }) state = p.factory.entry_state() sm = p.factory.simgr(state) def good (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return 'right' .encode() in stdout_output def bad (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return 'wrong' .encode() in stdout_output sm.explore(find=good, avoid=bad) if sm.found: print (sm.found[0 ].posix.dumps(sys.stdin.fileno())) else : print ("Not found" ) print ("Done" ) if __name__ == '__main__' : main()
参考资料 (一开始做了好久,硬逆向,发现确实不太好弄…本来想放弃了…搜了搜有原题..就参考了(抄)了一下)
https://www.52pojie.cn/thread-1717425-1-1.html
https://docs.angr.io