0x01 前言 最近在学习csapp时,发现第三章作业是一个程序叫bomb,要求破解六个步骤拆除炸弹。bomb是一个elf文件,我们用ida64分析,然后打开kali虚拟机远程调试,准备工作就做好了。
0x02 前五个部分 炸弹的前五个部分十分简单。第一部分就是比较一个输入的字符串,而这个字符点开加密函数phase_1就可以找到。
1 Border relations with Canada have never been better.
第二部分是输入6个数字,然后数字前后直接要满足后面一个是前面一个两倍的关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 __int64 __fastcall phase_2 (__int64 a1) { __int64 result; char *v2; int v3; char v4; char v5; read_six_numbers(a1, &v3); if ( v3 != 1 ) explode_bomb(a1, &v3); v2 = &v4; do { result = (2 * *(v2 - 1 )); if ( *v2 != result ) explode_bomb(a1, &v3); v2 += 4 ; } while ( v2 != &v5 ); return result; }
输入首项为1,公比为2的等比数列前六项即可。
第三部分是输入两个数字,然后第一个数字作为switch的索引来查找一个数字,再把这个数字和输入的第二个数字比较,相同即可。
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 __int64 __fastcall phase_3 (__int64 a1) { __int64 v1; __int64 v2; __int64 result; int v4; int v5; if ( __isoc99_sscanf(a1, "%d %d" , &v4, &v5) <= 1 ) explode_bomb(a1, "%d %d" , v1, v2); switch ( v4 ) { case 0 : result = 207LL ; break ; case 1 : result = 311LL ; break ; case 2 : result = 707LL ; break ; case 3 : result = 256LL ; break ; case 4 : result = 389LL ; break ; case 5 : result = 206LL ; break ; case 6 : result = 682LL ; break ; case 7 : result = 327LL ; break ; default : explode_bomb(a1, "%d %d" , v1, v2); } if ( result != v5 ) explode_bomb(a1, "%d %d" ); return result; }
我这里随便找一个组合输入
第四部分是一个二分查找的递归函数,满足的条件是return 0,这里发现只要a1小于一次中值v3,result就会被赋值0,之后递归回溯时就会把result=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 __int64 __fastcall phase_4 (__int64 a1) { __int64 v1; __int64 result; unsigned int v3; int v4; if ( __isoc99_sscanf(a1, "%d %d" , &v3, &v4) != 2 || v3 > 0xE ) explode_bomb(a1, "%d %d" ); v1 = v3; result = func4(v3, 0LL , 14LL ); if ( result || v4 ) explode_bomb(v1, 0LL ); return result; } __int64 __fastcall func4 (__int64 a1, __int64 a2, __int64 a3) { int v3; __int64 result; v3 = (a3 - a2) / 2 + a2; if ( v3 > a1 ) return 2 * func4(a1, a2, (v3 - 1 )); result = 0LL ; if ( v3 < a1 ) return 2 * func4(a1, (v3 + 1 ), a3) + 1 ; return result; }
这里需要输入两个值,第一个值v4必为0,第二个值v3我为了保险起见,输入了一个0,让上述二分查找里a1,也就是v3传进去的值能一直小于中值,然后result赋值为0接着返回。
第五部分是个查找表的加密,输入长度为6的字符串,然后取每个字符的二进制前四位掩码,作为数组array_3449的下标,依次和字符串flyers比较。这里通过找表发现下标顺序是9 F E 5 6 7,只要依次输入低四位是这些数字的字符就好了。
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 unsigned __int64 __fastcall phase_5 (__int64 a1, const char *a2) { __int64 i; char v4[8 ]; unsigned __int64 v5; v5 = __readfsqword(0x28 u); if ( string_length() != 6 ) explode_bomb(a1, a2); for ( i = 0LL ; i != 6 ; ++i ) v4[i] = array_3449[*(a1 + i) & 0xF ]; v4[6 ] = 0 ; if ( strings_not_equal(v4, "flyers" ) ) explode_bomb(v4, "flyers" ); return __readfsqword(0x28 u) ^ v5; } .rodata:00000000004024B 0 ; char array_3449[16 ] .rodata:00000000004024B 0 6 D array_3449 db 6 Dh ; DATA XREF: phase_5+37 ↑r .rodata:00000000004024B 1 61 db 61 h ; a .rodata:00000000004024B 2 64 db 64 h ; d .rodata:00000000004024B 3 75 db 75 h ; u .rodata:00000000004024B 4 69 db 69 h ; i .rodata:00000000004024B 5 65 db 65 h ; e .rodata:00000000004024B 6 72 db 72 h ; r .rodata:00000000004024B 7 73 db 73 h ; s .rodata:00000000004024B 8 6 E db 6 Eh ; n .rodata:00000000004024B 9 66 db 66 h ; f .rodata:00000000004024B A 6F db 6F h ; o .rodata:00000000004024B B 74 db 74 h ; t .rodata:00000000004024B C 76 db 76 h ; v .rodata:00000000004024B D 62 db 62 h ; b .rodata:00000000004024B E 79 db 79 h ; y .rodata:00000000004024B F 6 C db 6 Ch ; l
我这里输入 69 6F 6E 65 66 67
0x03 最后一部分 最后一部分调了我一个傍晚,发现是个链表加密
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 __int64 __fastcall phase_6 (__int64 a1) { int *v1; int v2; int v3; char *v4; unsigned __int64 i; _QWORD *v6; int v7; int v8; __int64 v9; char *v10; __int64 j; __int64 v12; int v13; __int64 result; int num[6 ]; char v16; __int64 v17; char v18; char v19[40 ]; v1 = num; read_six_numbers(a1, num); v2 = 0 ; while ( 1 ) { if ( (*v1 - 1 ) > 5 ) explode_bomb(a1, num); if ( ++v2 == 6 ) break ; v3 = v2; do { if ( *v1 == num[v3] ) explode_bomb(a1, num); ++v3; } while ( v3 <= 5 ); ++v1; } v4 = num; do { *v4 = 7 - *v4; v4 += 4 ; } while ( v4 != &v16 ); for ( i = 0LL ; i != 24 ; i += 4LL ) { v8 = num[i / 4 ]; if ( v8 <= 1 ) { v6 = &node1; } else { v7 = 1 ; v6 = &node1; do { v6 = v6[1 ]; ++v7; } while ( v7 != v8 ); } *(&v17 + 2 * i) = v6; } v9 = v17; v10 = &v18; for ( j = v17; ; j = v12 ) { v12 = *v10; *(j + 8 ) = *v10; v10 += 8 ; if ( v10 == v19 ) break ; } *(v12 + 8 ) = 0LL ; v13 = 5 ; do { result = **(v9 + 8 ); if ( *v9 < result ) explode_bomb(a1, v19); v9 = *(v9 + 8 ); --v13; } while ( v13 ); return result; }
这坨式主要的逻辑就是,输入1到6的数字,然后取关于3.5的对称数,接着根据1到6的代表节点1到6,来重置链表的顺序,最后进行一个某节点与下一个节点的比较,若小于则程序退出,于是我们就要使得链表的顺序重置为从大到小排列。(其实是非常简单的逻辑,第一次见链表式存储的逆向给我卡了,调试后才发现)
根据逆向原理,我们得先找到排序然后再取数的对称。找到6个节点各自的值
1 2 3 4 5 6 1 2 3 4 5 6 014c a8 039c 02b3 01dd 01bb //排序 3 4 6 5 2 1 //取对称 4 3 2 1 6 5
这样就把炸弹拆除了。
0x04 后续 其实这个程序,我感觉作者的本意应该是让我们在机器语言或者汇编语言层面调试分析,奈何ida工具太强大了。