介绍
https://hansimov.gitbook.io/csapp/labs/bomb-lab
年轻人的第一道逆向题目,目标是通过逆向分析出正确的输入从而让程序正确运行,拆除炸弹!
由于已经有过简单的linux逆向经验,这里我们不再过多强调要求,直接扔进IDA开搞。其实实验给出了main函数的源码文件作为提示,但是我们其实并不需要(本身main函数也没有什么复杂的逻辑,是一个线性的)
内容:
运行程序,随便输入点什么直接爆了,扔进IDA进行分析。给出的程序把符号表留下了,函数名都留着就很方便。能看到程序允许加一个文件参数然后依据文件内容自动执行,我们有pwntools所以不用管。程序每次调用一个readline然后有六个关卡(隐藏关最后再说)。
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 initialize_bomb(); puts ("Welcome to my fiendish little bomb. You have 6 phases with" );puts ("which to blow yourself up. Have a nice day!" );line = (char *)read_line(); phase_1(line); phase_defused(); puts ("Phase 1 defused. How about the next one?" );v4 = read_line(); phase_2((__int64)v4); phase_defused(); puts ("That's number 2. Keep going!" );v5 = read_line(); phase_3((__int64)v5); phase_defused(); puts ("Halfway there!" );v6 = read_line(); phase_4((__int64)v6); phase_defused(); puts ("So you got that one. Try this one." );v7 = read_line(); phase_5((__int64)v7); phase_defused(); puts ("Good work! On to the next..." );v8 = read_line(); phase_6((__int64)v8); phase_defused(); return 0 ;
phase1
跟进phase1,发现就是一个字符串比较。由于函数命名保留了直接这关就过了。输入就是这个串。
1 2 3 4 result = strings_not_equal(a1, "Border relations with Canada have never been better." ); if ( (_DWORD)result ) explode_bomb(); return result;
我们也可以跟进一下strings_not_equal()
,确实是一个字符串比较函数。这里也附上手动实现的stringlength。
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 __int64 __fastcall string_length (_BYTE *a1) { _BYTE *v1; __int64 result; if ( !*a1 ) return 0LL ; v1 = a1; do result = (unsigned int )((_DWORD)++v1 - (_DWORD)a1); while ( *v1 ); return result; } __int64 __fastcall strings_not_equal (_BYTE *a1, _BYTE *a2) { _BYTE *char_ptr_input; _BYTE *char_ptr_key; int input_len; int key_len; unsigned int v6; char_ptr_input = a1; char_ptr_key = a2; input_len = string_length(a1); key_len = string_length(a2); v6 = 1 ; if ( input_len == key_len ) { if ( *a1 ) { if ( *a1 == *a2 ) { do { ++char_ptr_input; ++char_ptr_key; if ( !*char_ptr_input ) return 0 ; } while ( *char_ptr_input == *char_ptr_key ); return 1 ; } else return 1 ; } else return 0 ; } return v6; }
phase2
phase2是读六个数然后比较,IDA优化下数据类型后就是下面这样。要求读入六个数以空格分割,然后第一个数是1,并且后一个数是前一个的两倍,即输入1 2 4 8 16 32.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 __int64 __fastcall phase_2 (__int64 src) { __int64 result; int *ptr; int arr[6 ]; char end; read_six_numbers(src, arr); if ( arr[0 ] != 1 ) ((void (__fastcall __noreturn *)(__int64, int *))explode_bomb)(src, arr); ptr = &arr[1 ]; do { result = (unsigned int )(2 * *(ptr - 1 )); if ( *ptr != (_DWORD)result ) ((void (__fastcall __noreturn *)(__int64, int *))explode_bomb)(src, arr); ++ptr; } while ( ptr != (int *)&end ); return result; }
直接看汇编也可以:
1 2 3 4 5 6 7 ... mov eax, [rbx-4] add eax, eax ;二倍后比较 cmp [rbx], eax jz short loc_400F25 call explode_bomb ...
其实我们也可以看下read_six_numbers
,这玩意是用sscanf实现的绝对安全输入:
1 2 3 4 5 6 7 8 __int64 __fastcall read_six_numbers (__int64 src, int *a2) { __int64 result; result = __isoc99_sscanf(src, "%d %d %d %d %d %d" , a2, a2 + 1 , a2 + 2 , a2 + 3 , a2 + 4 , a2 + 5 ); if ( (int )result <= 5 ) explode_bomb(); return result; }
phase3
强大的IDA!
这关是一个switch跳表,但是IDA直接识别了switch结构直接也是一眼顶针了。输入第一个数选择switch分支,然后只要第二个数和分支的result匹配即可。我选择输入0 207
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 if ( (int )__isoc99_sscanf(a1, "%d %d" , &v2, &v3) <= 1 ) explode_bomb(); switch ( v2 ) { 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(); } if ( (_DWORD)result != v3 ) explode_bomb();
phase4
这里开始就没有那么简单了。看看phase4的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 __int64 __fastcall phase_4 (__int64 a1) { __int64 result; unsigned int v2; int v3; if ( (unsigned int )__isoc99_sscanf(a1, "%d %d" , &v2, &v3) != 2 || v2 > 0xE ) explode_bomb(); result = func4(v2, 0LL , 14LL ); if ( (_DWORD)result || v3 ) explode_bomb(); return result; }
传入两个数,第二个要求是0,第一个作为参数传入func4并要求func4最终返回0。我们看看func4是什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 __int64 __fastcall func4 (int a1, int a2, int a3) { int v3; __int64 result; v3 = (a3 - a2) / 2 + a2; if ( v3 > a1 ) return 2 * (unsigned int )func4(a1, a2, v3 - 1 ); result = 0LL ; if ( v3 < a1 ) return 2 * (unsigned int )func4(a1, v3 + 1 , a3) + 1 ; return result; }
func4是一个递归函数,三个参数。我们自己传入的参数1会在每次递归时保留不变,a2和a3分别初值传入0和14.然后再每次递归的时候,会以当前的a2和a3取一个“中间值”,然后将中间值和a1比较,根据比较结果继续递归。我们发现每次判断v3大于a1继续递归,v3小于a1也继续递归,但是没有给出v3=a1的逻辑分支,也就是说递归终点一定是v3=a1,然后从0返回。
俗话说三分逆向七分猜,直接看肯定是看不出正确输入的。这里大概的感觉是a2和a3不断的向a1夹逼。这里其实就可以猜测了,我们拿python写个一样的脚本进行尝试,结果输入0直接返回就是0,因此这关输入0 0即可。
其实仔细分析,我们只要保证一直维持在v3>a1的分支,最后返回的结果一定是0(因为没有任何从0到1的步骤)。因此0并不是唯一解。尝试了下0,1,3都可以。
phase5
代换密码。输入长度要求是6,最后代换过的字符串是"flyers"即可。对数据结构和数据类型修复之后得到如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 unsigned __int64 __fastcall phase_5 (char *a1) { __int64 i; char v3[8 ]; unsigned __int64 v4; v4 = __readfsqword(0x28 u); if ( (unsigned int )string_length(a1) != 6 ) explode_bomb(); for ( i = 0LL ; i != 6 ; ++i ) v3[i] = sbox[a1[i] & 0xF ]; v3[6 ] = 0 ; if ( (unsigned int )strings_not_equal(v3, "flyers" ) ) explode_bomb(); return __readfsqword(0x28 u) ^ v4; }
对于输入的字符,我们只取低4字节0-15用来在sbox中索引代换目标。数据段的sbox如下(非数组形式会显示对应的ascii字符):
1 2 3 4 .rodata:00000000004024B 0 ; _BYTE sbox[16 ] .rodata:00000000004024B 0 sbox db 6 Dh, 61 h, 64 h, 75 h, 69 h, 65 h, 72 h, 73 h, 6 Eh, 66 h, 6F h .rodata:00000000004024B 0 ; DATA XREF: phase_5+37 ↑r .rodata:00000000004024B B db 74 h, 76 h, 62 h, 79 h, 6 Ch
查表可得我们需要的flyers是 9 15 14 5 6 7,因此看ASCII表,找低4位对应的字母输入即可。这里选择输入IONEFG(0x49,0x4F,0x4E,0x45,0x46,0x47)。这里用字母可以确定是ASCII,用标点符号的话可能会由于编码问题导致失败。
phase6
最难的一集。1打5说是。下面的IDA代码都是手动修复过变量类型、名字和结构体的,可放心食用。
输入检查
打开函数,逻辑稍有一点复杂,一点点看。首先还是读6个数到栈上,第一个循环内检查所有的输入,每个数都要小于等于6并且通过嵌套循环检查是不是六个不同的数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 nums_ptr = input_nums; read_six_numbers(input_line, input_nums); idx = 0 ; while ( 1 ) { if ( (unsigned int )(*nums_ptr - 1 ) > 5 ) explode_bomb(); if ( ++idx == 6 ) break ; idx_cpy = idx; do { if ( *nums_ptr == input_nums[idx_cpy] ) explode_bomb(); ++idx_cpy; } while ( idx_cpy <= 5 ); ++nums_ptr; }
输入处理
接下来,将所有的输入对7取补
1 2 3 4 5 6 7 arr_ptr = input_nums; do { *arr_ptr = 7 - *arr_ptr; ++arr_ptr; } while ( arr_ptr != (int *)&arr_end );
链表指针数组初始化
下一块注意到从数据段读东西了,名字还叫node,跟进看下结果好家伙,数据段存了六个链表的节点。(node1->node2->node3->node4->node5->node6->0x0)
IDA数据类型改qword自动识别出偏移了,而且这六个是连续存储的。
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 .data:00000000006032 D0 public node1 .data:00000000006032 D0 node1 dd 332 ; data .data:00000000006032 D0 ; DATA XREF: phase_6:loc_401183↑o .data:00000000006032 D0 ; phase_6+B0↑o .data:00000000006032 D4 db 1 , 3 dup(0 ) .data:00000000006032 D8 dq 6032E0 h ; next .data:00000000006032E0 public node2 .data:00000000006032E0 node2 dd 168 ; data .data:00000000006032E4 db 2 , 3 dup(0 ) .data:00000000006032E8 dq 6032F 0h ; next .data:00000000006032F 0 public node3 .data:00000000006032F 0 node3 dd 924 ; data .data:00000000006032F 4 db 3 , 3 dup(0 ) .data:00000000006032F 8 dq 603300 h ; next .data:0000000000603300 public node4 .data:0000000000603300 node4 dd 691 ; data .data:0000000000603304 db 4 , 3 dup(0 ) .data:0000000000603308 dq 603310 h ; next .data:0000000000603310 public node5 .data:0000000000603310 node5 dd 477 ; data .data:0000000000603314 db 5 , 3 dup(0 ) .data:0000000000603318 dq 603320 h ; next .data:0000000000603320 public node6 .data:0000000000603320 node6 dd 443 ; data .data:0000000000603324 db 6 , 3 dup(0 ) .data:0000000000603328 dq 0 ; next
然后看代码,这里是对结构体和指针做了优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 for ( i = 0LL ; i != 6 ; ++i ) { num = input_nums[i]; if ( num <= 1 ) { cur1 = &node1; } else { idx1 = 1 ; cur1 = &node1; do { cur1 = (node *)cur1->next; ++idx1; } while ( idx1 != num ); } nodeptr_arr[i] = cur1; }
对7取补后根据输入的数,从data段选对应的node放在栈上的结构体指针数组里面
链表连接
下面这段由于反汇编f5的误差问题看了好久,它在一个关键的下标位置出错了,动调辅助理解的。不能全信IDA。
这段开始由于IDA加自己的问题没看懂,于是起gdb动调了一下。结果发现这里是按照我们栈上的顺序,将链表结点的next域刷新一遍。
1 2 3 4 5 6 7 8 9 10 cur = nodeptr_arr[0 ]; next2 = &nodeptr_arr[1 ]; for ( node2 = nodeptr_arr[0 ]; ; node2 = tmp_node ){ tmp_node = *next2; node2->next = (__int64)*next2++; if ( next2 == (node **)&endarr ) break ; } tmp_node->next = 0LL ;
gdb动调输入123456后,发现data段原本的链接1->2->3…->6被改成了6->…->1,结合这段代码可以知道,这里是按我们输入的顺序将next域刷了一遍。
最后判断:
1 2 3 4 5 6 7 8 9 do { next_node = *(unsigned int *)cur->next; if ( cur->data < (int )next_node ) explode_bomb(); cur = (node *)cur->next; --cnt_to_5; } while ( cnt_to_5 );
对我们搭建的链表进行判断,我们需要这个链表前一项的data一直比后一项大(注意这里的(int)next_node
实质上是取了下一个node的data域)。而且这里有一个小陷阱,比较的是int类型,是四字节的数据,而data段的node中存的是qword,按4字节小端读一下之后node1~6依次是332,168,924,691,477,443。也就是说我们要从大到小的话需要按照3->4->5->6->1->2的顺序连接,结合前面的对7取补,因此我们最后要输入4 3 2 1 6 5。
secret phase
触发条件
每次拆除结束后,都会调用一个phase_defused()
函数,跟进发现有一个secret phase。看一下触发条件,需要input_strings这个量是6且用sscanf对一个莫名其妙的地址读两个数字加一个字符串,然后字符串要是"DrEvil"。
1 2 3 4 5 6 7 8 9 10 11 if ( num_input_strings == 6 ){ if ( (unsigned int )__isoc99_sscanf(&file_input_603870, "%d %d %s" , &v1, &v2, v3) == 3 && !(unsigned int )strings_not_equal(v3, "DrEvil" ) ) { puts ("Curses, you've found the secret phase!" ); puts ("But finding it and solving it are quite different..." ); secret_phase(); } puts ("Congratulations! You've defused the bomb!" ); }
先交叉引用num_input_strings,定位read_line函数。仔细检查发现了真正的读取函数skip()
,这个函数内部会从预定的输入流(stdin或者文件)循环读取80字节放入bss。而0x603780这个地址在加240后恰好就是上面secret_phase要取内容的地址。
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 char *skip () { char *v0; char *v1; do { v0 = fgets((char *)(80LL * num_input_strings + 0x603780 ), 80 , infile); v1 = v0; } while ( v0 && (unsigned int )blank_line(v0) ); return v1; } const char *read_line () { int v0; const char *file_content; unsigned __int64 content_len; int v3; __int64 v4; if ( !(__int64)skip() ) { if ( infile == (FILE *)stdin ) { puts ("Error: Premature EOF on stdin" ); exit (8 ); } if ( getenv("GRADE_BOMB" ) ) exit (0 ); infile = (FILE *)stdin ; if ( !(__int64)skip() ) { puts ("Error: Premature EOF on stdin" ); exit (0 ); } } v0 = num_input_strings; file_content = (const char *)(80LL * num_input_strings + 0x603780 ); content_len = strlen (file_content) + 1 ; if ( (int )content_len - 1 > 78 ) { puts ("Error: Input line too long" ); v3 = num_input_strings++; v4 = 10LL * v3; input_strings[v4] = 0x636E7572742A2A2A LL; qword_603788[v4] = 0x2A2A2A64657461 LL; explode_bomb(); } *((_BYTE *)&input_strings[10 * num_input_strings - 1 ] + (int )content_len + 6 ) = 0 ; num_input_strings = v0 + 1 ; return file_content; }
gdb动调一下确定num_input_strings代表输入的次数(动态确定会比较方便),并且正常跑一趟流程发现0x603870存的是0 0,也就是我们phase4的输入。因此确定触发secret_phase的条件是在phase4输入"0 0 DrEvil"
解决
看看是个什么问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 unsigned __int64 secret_phase () { const char *line; int v1; line = read_line(); v1 = strtol(line, 0LL , 10 ); if ( (unsigned int )(v1 - 1 ) > 1000 ) explode_bomb(); if ( (unsigned int )fun7((struct jmp_num *)&n1, v1) != 2 ) explode_bomb(); puts ("Wow! You've defused the secret stage!" ); return phase_defused(); }
接着读一次输入,我们要输入一个1000以内的十进制数,然后传入func7并且要求func7返回2。func7又有一个设定好的参数。都跟进看看。
首先看这个n1,取qword一看,又是结构体。从连续存储的数据段看,这是一个二叉树:
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 .data:00000000006030F 0 public n1 .data:00000000006030F 0 n1 dd 36 ; DATA XREF: secret_phase+2 C↑o .data:00000000006030F 4 db 0 .data:00000000006030F 5 db 0 .data:00000000006030F 6 db 0 .data:00000000006030F 7 db 0 .data:00000000006030F 8 dq offset n21 .data:0000000000603100 dq offset n22 .data:0000000000603108 dq 0 .data:0000000000603110 public n21 .data:0000000000603110 n21 dd 8 ; DATA XREF: .data:00000000006030F 8↑o .data:0000000000603114 db 0 .data:0000000000603115 db 0 .data:0000000000603116 db 0 .data:0000000000603117 db 0 .data:0000000000603118 dq offset n31 .data:0000000000603120 dq offset n32 .data:0000000000603128 dq 0 .data:0000000000603130 public n22 .data:0000000000603130 n22 dd 50 ; DATA XREF: .data:0000000000603100 ↑o .data:0000000000603134 db 0 .data:0000000000603135 db 0 .data:0000000000603136 db 0 .data:0000000000603137 db 0 .data:0000000000603138 dq offset n33 .data:0000000000603140 dq offset n34 .data:0000000000603148 dq 0 .data:0000000000603150 public n32 .data:0000000000603150 n32 dd 22 ; DATA XREF: .data:0000000000603120 ↑o .data:0000000000603154 db 0 .data:0000000000603155 db 0 .data:0000000000603156 db 0 .data:0000000000603157 db 0 .data:0000000000603158 dq offset n43 .data:0000000000603160 dq offset n44 .data:0000000000603168 dq 0 .data:0000000000603170 public n33 .data:0000000000603170 n33 dd 45 ; DATA XREF: .data:0000000000603138 ↑o .data:0000000000603174 db 0 .data:0000000000603175 db 0 .data:0000000000603176 db 0 .data:0000000000603177 db 0 .data:0000000000603178 dq offset n45 .data:0000000000603180 dq offset n46 .data:0000000000603188 dq 0 .data:0000000000603190 public n31 .data:0000000000603190 n31 dd 6 ; DATA XREF: .data:0000000000603118 ↑o .data:0000000000603194 db 0 .data:0000000000603195 db 0 .data:0000000000603196 db 0 .data:0000000000603197 db 0 .data:0000000000603198 dq offset n41 .data:00000000006031 A0 dq offset n42 .data:00000000006031 A8 dq 0 .data:00000000006031B 0 public n34 .data:00000000006031B 0 n34 dd 107 ; DATA XREF: .data:0000000000603140 ↑o .data:00000000006031B 4 db 0 .data:00000000006031B 5 db 0 .data:00000000006031B 6 db 0 .data:00000000006031B 7 db 0 .data:00000000006031B 8 dq offset n47 .data:00000000006031 C0 dq offset n48 .data:00000000006031 C8 dq 0 ...
后面的就不放了,nxy似乎能理解成第几层的第几个节点。然后看func7:
1 2 3 4 5 6 7 8 9 10 11 12 13 __int64 __fastcall fun7 (struct jmp_num *a1, int a2) { __int64 result; if ( !a1 ) return 0xFFFFFFFF LL; if ( a1->data > a2 ) return 2 * (unsigned int )fun7(a1->next1, a2); result = 0LL ; if ( a1->data != a2 ) return 2 * (unsigned int )fun7(a1->next2, a2) + 1 ; return result; }
又是递归。从根节点开始,如果我们输的数比节点存的数小就以2*func的形式递归,然后如果不等于的话就以2*func+1的形式递归。同样找递归出口,最后是要通过两个if判断然后返回0.
关键来了,要想pass第二个if,这就意味着我们的输入在最后一次递归一定等于某个节点存储的数据。因此正确结果一定就是二叉树中的某一个节点的data。一共也就15个数,排除最后一个1001,我们直接进行爆破拆弹()最后得到正确结果是22.
输出:
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 # b'BOOM!!!\n' # 36 # [*] Stopped process './bomb' (pid 166) # [+] Starting local process './bomb': pid 168 # b'BOOM!!!\n' # 8 # [*] Stopped process './bomb' (pid 168) # [+] Starting local process './bomb': pid 170 # b'BOOM!!!\n' # 50 # [*] Stopped process './bomb' (pid 170) # [+] Starting local process './bomb': pid 172 # b"Congratulations! You've defused the bomb!\n" # 22 # [*] Stopped process './bomb' (pid 172) # [+] Starting local process './bomb': pid 174 # b'BOOM!!!\n' # 45 # [*] Stopped process './bomb' (pid 174) # [+] Starting local process './bomb': pid 176 # b'BOOM!!!\n' # 6 # [*] Stopped process './bomb' (pid 176) # [+] Starting local process './bomb': pid 178 # b'BOOM!!!\n' # 107 # [*] Stopped process './bomb' (pid 178) # [+] Starting local process './bomb': pid 180 # b'BOOM!!!\n' # 40 # [*] Stopped process './bomb' (pid 180) # [+] Starting local process './bomb': pid 182 # b'BOOM!!!\n' # 1 # [*] Stopped process './bomb' (pid 182) # [+] Starting local process './bomb': pid 184 # b'BOOM!!!\n' # 99 # [*] Stopped process './bomb' (pid 184) # [+] Starting local process './bomb': pid 186 # b'BOOM!!!\n' # 23 # [*] Stopped process './bomb' (pid 186) # [+] Starting local process './bomb': pid 188 # b'BOOM!!!\n' # 7 # [*] Stopped process './bomb' (pid 188) # [+] Starting local process './bomb': pid 190 # b'BOOM!!!\n' # 14 # [*] Stopped process './bomb' (pid 190) # [+] Starting local process './bomb': pid 192 # b'BOOM!!!\n' # 47 # [*] Stopped process './bomb' (pid 192)
总结
年轻人的第一道逆向题目。这道题虽然有linux逆向经验的话并不困难,但是通过这个lab我使用IDA分析结构体的水平极大提升了。
附EXP和i64文件
反汇编结果
bomb.i64
secret.txt
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 from pwn import *context.log_level = 'info' context.terminal=["cmd.exe" ,"/c" , "start" , "cmd.exe" , "/c" , "wsl.exe" , "-e" ] def phase_1 (): p.recv() p.sendline("Border relations with Canada have never been better." ) def phase_2 (): p.recv() p.sendline("1 2 4 8 16 32" ) def phase_3 (): p.recv() p.sendline("0 207" ) def phase_4 (): p.recv() p.sendline("0 0 DrEvil" ) def phase_5 (): p.recv() p.sendline("IONEFG" ) def phase_6 (): p.recv() p.sendline("4 3 2 1 6 5" ) def secret_phase (num ): num=str (num) p.recv() p.sendline(num) num_list=[36 ,8 ,50 ,22 ,45 ,6 ,107 ,40 ,1 ,99 ,23 ,7 ,14 ,47 ] for i in range (14 ): p=process("./bomb" ) phase_1() phase_2() phase_3() phase_4() phase_5() phase_6() secret_phase(num_list[i]) p.recvline() result=p.recvline() print (result) print (f"{num_list[i]} " ) p.close()