**
phase1phase_1phase1
**
密码:All your base are belong to us.

通过观察phase1phase_1phase1的汇编代码可以发现,stringsnotequalstrings_not_equalstringsnotequal函数应该为比较输入密码和标准密码的。所以前一个lealealea操作应该为将标准密码取出放到寄存器%rsi中。这样大致思路就为在gdbgdbgdb中的phase1phase_1phase1位置设置一个断点,然后用sisisi操作单步执行到callqcallqcallq操作时,将寄存器%rsi取出即为正确密码。取出值得过程可以先用inforegisterrsiinfo register rsiinforegisterrsi查看寄存器的地址和存储的值,再用x/sx/sx/s 地址将寄存器中的值以字符串的形式输出出来。

通过phase1phase_1phase1已经基本熟悉了逆向的过程。
**
phase2phase_2phase2
**
密码:0 1 1 2 3 5

+9+9+9行的汇编语言中,可以看出读入了一个输入的666位密码,接下来就应当是比较的过程。
+14+14+14行中首先将000与栈顶指针所存的数进行比较,如果不一样则炸弹爆炸,可以判断出第一位密码应当是000。
+20+20+20接着比较了111与栈顶的第二个元素,相同跳转,不同就会直接爆炸,所以可以推测111是第二位的密码。
接着程序跳转到了+32+32+32,将栈顶指针保存到了4%rbx中,又操作了一步之后跳到了中,又操作了一步之后跳到了中,又操作了一步之后跳到了+50$。
+50+50+50的操作是将当前栈顶的第二个元素也就是1赋给eax。
+53+53+53是将当前栈顶元素也就是000加到eaxeaxeax上,随后再将eaxeaxeax与标准密码进行比较,之后再不断重复这个循环。
可以找到规律,初始两位密码为000 111,随后的第i位密码就是i−1i-1i−1和i−2i-2i−2位的和,所以算出密码为:0 1 1 2 3 5
**
phase3phase_3phase3
**
密码:
1 471
2 735
3 349
4 469
5 942
6 192
7 551
其中的任意一组



+26+26+26那一行可以看出访问了一个sscanfsscanfsscanf函数,通过第三张截图中访问的第一个而第二个寄存器可以看出,第一个参数保存的是读入的一串字符串,第二个参数是%d %d可见程序希望读入两个整数,也就是第三个密码应该为两个整数。然后%rdx %rcx两个寄存器的值就是存这两个整数的。
+31+31+31的比较函数要求读入的返回值大于一,也就是要读入至少两个数,与上文的意思相符。
+36+36+36比较了7和读入的第一个数的大小,和下面的超过跳转,可以看出是要求第一位密码的范围应当是小于等于777的整数。
再向下一直到+61+61+61之前一直是处理%rax中的值,然后就是+61+61+61的条件跳转。可以发现这个条件用到了%rax中的值,调用了%rax*4的偏移量,可以发现是根据读入的第一个数来跳转的。
接着下面有7组差不多的命令,都是先赋值以后跳到+136+136+136,可见这些加上+61+61+61的间接跳转应该是一个switchswitchswitch语句。用第一个密码来选择。
+136+136+136比较了%eax中的值与第二位密码是否相等,而%eax中的值从switchswitchswitch中被赋过值了,所以switchswitchswitch语句中也就蕴含着与第一位密码相应的答案。共有777组。
**
phase4phase_4phase4
**
密码:3 10


根据phase3phase_3phase3的做法,先输出sscanfsscanfsscanf的%rsi,发现phase4phase_4phase4的密码也是要输入两个整数。接下来就分别用x,yx,yx,y表示两位密码。
+34+34+34和+41+41+41可以看出x应该是一个小于等于141414的整数。
+62+62+62行中代码进入到了了个函数中,同时分别将(x,0,14)(x,0,14)(x,0,14)作为参数传入到了函数当中。
第二个截图为func4func4func4函数的指令。接下来用(a,b,c)(a,b,c)(a,b,c)分别表示传入的三个参数。
+1+1+1和+3+3+3将c−bc-bc−b保存到了%eax中。+5+5+5将%eax的数存到了%ebx中。
+7+7+7这个操作将%ebx中的数逻辑右移了313131位。这个操作在%ebx中的数位正数的情况下,就等价与清零操作,可以先将其看作清零操作。
接下来就是将%eax的值赋给%ebx,然后将%ebx中的值除以222,再加上bbb。
接下来将%ebx中的值与aaa进行比较,如果大于aaa,就将ccc赋成%ebx中的数−1-1−1,继续执行func4func4func4函数进行递归。如果小于aaa就将bbb赋成%ebx中的数+1+1+1继续进行func4func4func4函数进行递归。递归结束后将%ebx的数加上递归的返回值然后作为这一层的返回值。如果aaa等于%ebx中的值,就直接返回%ebx中的值。
递归完之后回到phase4phase_4phase4中去,+67+67+67要求返回值为101010,+72+72+72要求yyy与返回值101010相等,所以可以得到第二个密码值为101010,而第一个密码为递归函数中的第一个参数,根据描述,写出相应的递归程序后如下:


通过试验测得,当dfsdfsdfs的第一个参数为333时,返回值为101010,所以密码为333 101010.
**
phase5phase_5phase5
**
密码:jdoefgjdoefgjdoefg

+13+13+13这一行可以看出读入的面貌是一个666位字符串。+18+18+18将%eax清零。
然后通过观察整段程序,发现程序可以分成两个部分,第一部分是+30+30+30到+53+53+53之间,是一个循环。第二段是+55+55+55到+72+72+72是判断部分。
由于第一部分比较复杂,所以先看第二部分,第二部分是将地址为0x9(0x9(%rsp)0x9(开始的字符串与%rsi中的进行比较。可以看到%rsi中的字符串为如下的666位字符。鱼就是目标字符串为oilersoilersoilers

接下来看循环中的部分。
+23+23+23首先将一个常数赋给了%rcx,通过和面的地址可以看到%rcx中存的:

+45+45+45到+53+53+53可以看出来这个循环是变量%rax从000到555的一个循环。
+30+30+30和+34+34+34这两行进行的操作为将%rbx的数加上%rax的和作为地址,读取其中的数赋给%edx。而%rbx是我们读入的字符串,%rbx中存的地址就是字符串开头字母的地址,又因为%rax是从000到555的,所以在每次循环中分别取出了输入字符串的第000到555位赋给了%eax。然后+34+34+34取出了%eax的后四位。
+37+37+37行访问了%rcx和%rdx保存的地址的和作为地址的地址。因为%rcx中存储的也为字符串,所以可以看成是以%rcx存储的字符串的首字符作为基地址,%rdx存的量为偏移量来访问后面的第%rdx个字符。
+41+41+41行将%dl中的值存储到对应的+60+60+60行要比较的字符串的第%rax位。
通过上面的分析可以看出,第i次循环要得到oilersoilersoilers中的第iii个字符,而这个字符是通过访问%rcx字符串中的字符找到的,可以计算出,每次循环+37+37+37行访问时的偏移量分别为a,4,f,5,6,7a,4,f,5,6,7a,4,f,5,6,7这样分别代表了mmm后面的第aaa个字符ooo,第444个iii以此类推。

而第iii个偏移量的获得是通过度日的字符串的第iii位对应的ASCIIASCIIASCII码取后四位得到的。所以可以得到输入的666位字符串的后四位分别为a,4,f,5,6,7a,4,f,5,6,7a,4,f,5,6,7。然后在补上高位666,即获得了正确密码。
phase6phase_6phase6
密码:5 6 2 1 4 3



+0+0+0到+18+18+18是读入部分,通过输出%r12中存的数可以发现,0x30(0x30(%rsp)0x30(中保存的是读入的第一个数的地址。输入的六位密码就用a[i]来表示。
接下来程序可以分为以下几个部分:+29+29+29到+82+82+82;+84+84+84到+152+152+152; +154+154+154到最后。
首先+29+29+29到+82+82+82是一个单层循环,%r13d中使循环变量,从111到666遍历了读入的六个元素,判断每个元素的是是否都小于等于666且互不相等。这也就要求了这个六位密码是1−61-61−6的全排列中的一个。
+84+84+84到+152+152+152是一个两层循环,%rsi中存的是一个外层循环的循环变量,这一层循环从111到666遍历了一遍读入的六个数据。%eax中保存了第222重循环的循环变量。可以发现第二层循环的次数位第i位密码的值。而第二陈循环中主要涉及了%rdx和%rsp这两个寄存器。首先来看一下%rdx。

这个是%rdx的初始化,是从内存中读取了一个常数。接下来对于外层的第i次循环,内层的循环进行了¥a[i]次如下操作:因为次如下操作:

因为次如下操作:因为%rdx中存的是一个地址,所以可以看到中存的是一个地址,所以可以看到中存的是一个地址,所以可以看到%rdx向后偏移了向后偏移了向后偏移了a[i]$次,也就是说程序中保存了666个常数,对于第i次外层循环,内层循环取出了第a[i]a[i]a[i]个常数。

这条语句依次将取出的元素保存在了栈中。
然后是最后一个部分,首先进行了一个这样的操作:

这段代码将栈上一部分保存在栈中的元素建立了一个链表,顺序就是,第一个放入的元素指向下一个放入的元素(这一段开始比较难以理解)。

这是程序的最后阶段了,这一段在刚刚建好的链表中从前到后的比较了量表中相邻两个元素的大小。也就是第一个元素小于第二个元素想,第二个小于第三个,以此类推,也就是这六个元素要是从小到大排列好的。
现在这个程序的作用也就比较清晰了,程序按照我们输入的六个数的顺序依次取出了系统中保存的六个常数,要求取出的这六个常数要按照从小到大的顺序排好,也就是说我们输入的六位密码的第i位代表的应该是,六个常数中排名第iii小的元素是第几个。那只需要看一下这六个元素依次是什么,这个问题就解决了。


输出程序中的六个常数的大小,即可得到密码:5 6 2 1 4 3
**
secret−phasesecret-phasesecret−phase
**
密码:22

程序中只有phase_defused这个函数还没有看过内容,所以隐藏关可能就宝运载整函数中。函数中开始就有一个比较,通过在程序中跟踪比较的那个地址发现,那个地址就是破解的炸弹的个数,也就是说,只有破解了六个炸弹后才能进入下面的隐藏部分。

通过这个值发现,要输入两个整数和一个字符串来进入隐藏关,而+37+37+37中首先传入了一个参数,通过输出发现正好是第四个炸弹的密码,所以字符串应该是跟在第四个炸弹后面的。
+88+88+88中调用了字符串是否相等的函数,那么根据传入的那个字符串的地址,就可以知道进入隐藏关的密码了。


进入到隐藏关后,发现隐藏关的密码是通过read_line读入的,也就是一个字符串。+19+19+19中调用了一个系统函数strtolstrtolstrtol,通过查询,发现这个代码的功能是将字符串转换成相应的数字。所以+24+24+24的%rax总就保存的将读入字符串传化为数字后的值。接下来的两行限制了这个数字的大小。+24+24+24到+35+35+35这段代码限制了隐藏关的密码要小于0x3e80x3e80x3e8。
再向下看发现+46+46+46中调用了函数fun7fun7fun7,假设传入的两个变量为(x,y)(x,y)(x,y),yyy就是我们读入的隐藏关的密码,xxx是一个指针,通过访问内存地址,发现xxx的值为:

通过fun7fun7fun7的代码,发现fun7fun7fun7是一个递归函数。
首先当x=0x=0x=0的时候,函数返回−1-1−1。然后递归函数先在+11+11+11分成了两个部分,第一个部分是x>yx>yx>y的情况,第二个是x<=yx<=yx<=y的情况。
当x>yx>yx>y时,跳转到了+29+29+29,将下一次递归的指针x指向的地址加了0x80x80x8后进入到了下一层递归,然后将返回值乘222作为这一层递归的返回值。
当x<=yx<=yx<=y的时候,又分成了两个部分,x=yx=yx=y和x<yx<yx<y。
当x=yx=yx=y的时候,程序不再进行下一层递归,直接返回000。
当x<yx<yx<y的时候,跳转到了+42+42+42,将下一次递归的指针xxx指向的地址增加了0x100x100x10后进入到下一层递归,然后将返回值乘222加111作为这一层的返回值。
观察secre_phase的+51+51+51和+54+54+54发现,隐藏关要求函数fun7fun7fun7的返回值为222,所以回想整个递归过程,最深层的递归返回值一定为000,如果想得到222,需要将000先乘222加111变成111,再乘222变成222,也就是说最开始应该先执行x>yx>yx>y,在下一层执行x<yx<yx<y,最深的一层为x=yx=yx=y,这样就可以让fun7fun7fun7的返回值为222。那么我们输入的密码就应该和第二层的xxx值相等,通过访问内存地址,就可以得到隐藏关的密码:
也就是222222。
解析了五个阶段的密码挑战,展示了如何通过逆向工程找到每个阶段的密码,包括字符串比较、数学序列、数值条件和字符串处理。
1295

被折叠的 条评论
为什么被折叠?



