操作系统_学习日志_AT&T汇编

版权声明:我的码云地址:https://gitee.com/ZzYangHao https://blog.csdn.net/qq_21049875/article/details/81113981

  玩了半个月,该学习了=_=
  在网上找到了一个计算机系统的教学:https://wdxtub.com/2016/04/16/thin-csapp-2/,写的挺好的,简化了很多《深入理解计算机系统》中的知识。
  个人觉得可以先去看上面网址的教程,再去决定要不要去看《深入理解计算机系统》这本书,比较书太厚了,想要快速了解可能不太适合。


汇编

寄存器介绍 :
  寄存器(%rax, %rbx, %rcx, %rdx, %rsi, %rdi)称为通用寄存器,有其『特定』的用途,而 %rsp(%esp) 和 %rbp(%ebp) 则是作为栈指针和基指针来使用的:
    %rax(%eax) 用于做累加  //返回值
    %rcx(%ecx) 用于计数  //第4个参数
    %rdx(%edx) 用于保存数据  //第3个参数
    %rbx(%ebx) 用于做内存查找的基础地址 //被调用者保存
    %rsi(%esi) 用于保存源索引值  //第2个参数
    %rdi(%edi) 用于保存目标索引值 //第1个参数
  movl指令以寄存器为目的时,他会把寄存器的高位4字节设置为0.(mov后面的字母中:b为传送字节,w为传送字(2个字节),l为传送4个字节,q为传送8个字节),其中X86-64架构处理器不能让两个操作数(源操作与目的操作)都指向内存位置,需要两条指令实现,将内存的值传送给寄存器,然后第二条指令将寄存器的数据传给内存,以及正确使用mov的各种版本,需要具体看目的操作数和源操作数的位数或者说是范围,如 movl %eax (%rsp),rsp是64位的,而eax是32位的,也就是rsp=eax,rsp高位4字节设置为0,使用movl就足够了。(或者看源操作数与目的操作数中,若两者中有一个是立即数或者是内存,则看另一个寄存器来决定mov的size)
   在阅读《深入理解计算机系统》的时候,看到练习题3.3中有一个题:
      movb $0xF, (%ebx) //书上给这个操作是错误的解释是ebx不能用来当地址寄存器,感觉挺懵的,为什么不能当呢?去查一下stackoverflow,也没有相关的解释=_=
    下面是一个fib函数,以及相应的汇编代码。

代码

#include"apue.h"
#include<stdio.h>

int fib(int n){
    if(n==1&&n==2){
        return 1;
    }
    return fib(n-1)+fib(n-2);
}

汇编以及个人的一些注释解释

    .file   "test2.c"
    .text
    .globl  fib
    .type   fib, @function
fib:
.LFB0:
    .cfi_startproc
    pushq   %rbp   //把基指针(地址)push到栈中,可用来找传入的参数(被调用者保存寄存器)
    //执行完pushq %ebp,SP与CFA偏了16字节(rbq占8,返回或者回退地址占8)
    .cfi_def_cfa_offset 16  .cfi_offset 6, -16
    movq    %rsp, %rbp   // 把rsp的数据存入rbp,或者是rbp=rsp (指针)
    .cfi_def_cfa_register 6
    pushq   %rbx //用于存储后面递归调用fib(n-1)+fib(n-2)存储值(被调用者保存寄存器)
    subq    $24, %rsp //为局部变量分配地址
    .cfi_offset 3, -24
    movl    %edi, -20(%rbp)//把edi赋值给n
    cmpl    $1, -20(%rbp) //比较 n和1
    jne .L2 //如果n!=1跳转到L2
    cmpl    $2, -20(%rbp) 
    jne .L2
    movl    $1, %eax
    jmp .L3
.L2:
    movl    -20(%rbp), %eax //参数传给eax
    subl    $1, %eax //eax-1  ===  n-1
    movl    %eax, %edi //把n-1传给fib的第一个参数
    call    fib
    movl    %eax, %ebx //ebx存放结果
    movl    -20(%rbp), %eax //eax取参数n的值
    subl    $2, %eax //n-2
    movl    %eax, %edi
    call    fib
    addl    %ebx, %eax //fib(n-1)结果+fib(n-2)结果
.L3:
    addq    $24, %rsp
    popq    %rbx
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   fib, .-fib
    .section    .rodata

gcc -Og -S (-Og告诉编译器按照原始C代码结构的机器代码优化等级生成汇编代码):

        .file   "test2.c"
        .text
        .globl  fib
        .type   fib, @function
fib:
.LFB31:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        pushq   %rbx
        .cfi_def_cfa_offset 24
        .cfi_offset 3, -24
        subq    $8, %rsp
        .cfi_def_cfa_offset 32
        movl    %edi, %ebx
        leal    -1(%rdi), %edi
        call    fib
        movl    %eax, %ebp
        leal    -2(%rbx), %edi
        call    fib
        addl    %ebp, %eax
        addq    $8, %rsp
        .cfi_def_cfa_offset 24
        popq    %rbx
        .cfi_def_cfa_offset 16
        popq    %rbp
        .cfi_def_cfa_offset 8
        ret
        .cfi_endproc
.LFE31:
        .size   fib, .-fib
        .globl  m_sum
        .type   m_sum, @function

  可以看到经过编译器生成的汇编代码比我们所编写的代码要长出许多,且我们编写的fib函数是一个递归函数。

  

   跳转指令表:
  这里写图片描述
  
  

拆除bomb实验

  该实验是在《深入理解计算机系统》官网下的lab。
  
  

第一关

  从官网上到的bomb.tar文件解压得到以下文件:
  这里写图片描述
  既然是拆除bomb,我们就看看里面bomb里面有什么吧。
  在终端输入objdump -t bomb|less
这里写图片描述
  为了方便看,我们搜索与bomb相关的内容,输入/bomb,于是看到相关的内容有这几个。

    1、bomb.c
    2、initialize_bomb_solve
    3、explode_bomb
    4、bomb_id
    5、initialize_bomb

  可以在符号表那里看到2、3、5都是函数,而3是使bomb爆炸的函数,然后我们在看看该文件反汇编后的汇编代码。
   在终端输入objdump -d bomb|less
  
  bomb反汇编代码:
这里写图片描述
   我们可以大概看出400e19 地址那里是调用initialize_bomb,然后接着往下看
这里写图片描述
  后面有调用phase_1,phase_2之类的内容,我们看看phase_1是什么吧
这里写图片描述
  可以看到在phase_1中,在call_strings_not_equal后,test %eax %eax, 如果相等就跳转到400ef7,也就是避过了调用explode_bomb,算是炸弹拆除成功。
  其实在此之前,我们去看看bomb是怎么expload。
这里写图片描述
  可以看到,程序需要我们输入字符来继续程序,随便输入一个后。
这里写图片描述
  可以看到,爆炸啦~,所以综合上面,我们的拆弹工作开始了。
  首先综合上面,我们要关注的点是expload_bomb和第一关phase_1 .
  即:
这里写图片描述
  然后gdb直接运行到第一个断点处(run/r),然后就到了输入字符处:
这里写图片描述
  可以看到程序运行到了我们打的断点上,phase_1,然后查看该处的汇编代码。
这里写图片描述
  箭头处是$rip,也就是程序计数器所运行的指令。
  这时候我们并看不出什么,我们需要知道寄存器中存储着什么值.
这里写图片描述
  这时候我们看到%rax存储着一个值,于是查看一下,发现正是我们输入的值”hello”
这里写图片描述
  我们在一步步运行,看看指令在执行到call_strings_equal之前的寄存器存储的状态。
这里写图片描述
  此时可以看到%rsi的值与上图中的不同 ,可以看出%rdi存储的是我们输入的字符串,也就是该函数的第一个参数,而rsi可能就是第二个参数或者是一个局部变量,然后我们查看 %esi中保存的是什么值,这个值也许就是解除bomb的线索。
这里写图片描述
  接下来再步进运行,可以看到eax中的值是1,也就是两个字符串不相等,后面就不用截图了,接下来的结果就会执行到 call explode_bomb,触发爆炸,所以我们可以得出Border relations with Canada have never been better.,就是我们的解。
这里写图片描述
  测试我们得出的解是否正确。
这里写图片描述
  正确,进入下一关~

  
  

扫描二维码关注公众号,回复: 3016847 查看本文章

第二关

  第二关难度增大了,需要对寄存器有一些了解。
  推荐去看一下这篇关注如何查看寄存器内容:http://blog.chinaunix.net/uid-20593827-id-1918499.html
  因为是我们存入的数据一般都是4字节或者以下,所以使用 x/4wd的方式去查看寄存器或者内存地址。
  开始第二关,日常设置断点,以及输入我们的猜测值,hello
  这里写图片描述
  
  phase_2反汇编代码:
  这里写图片描述
  拆炸弹可以看到其中call的函数,我们先去read_six_numbers函数看看,如下图,可以看到运行到最后会因为%eax中的值小于5而导致expload,这是因为sscanf在格式化字符串(偏移量+36那里,%esi被赋值为”%d %d %d %d %d %d",用x/s去查看%esi得知的)失败返回的值为0,从这里我们也看出了,我们所需要输入到phase_2中的key应该是6个数字的字符串,且每个数字中间需要有空格。
  
  read_six_numbers反汇编代码:
  这里写图片描述
  上面是分析read_six_numbers函数,现在我们再来看看phase_2,从上面我们知道可能会产生6个变量用于存储我们的字符数字,当程序顺利执行 read_six_numbers且第一个数字变量可以看出必定是1,否则就会call explode_bomb,接下来跳转到+52的位置,赋值给把%rsp+0X4位置的值赋给%rbx,这个值可能就是第二个变量(不考虑压栈顺序,一共6个变量),然后把%rsp+0x18位置的值给%rbp,看到这里的时候我有一些疑惑,因为这些知识我就匆匆忙忙的扫一眼,不太清楚+0X18能到哪,如果换算成10进制,就是移动24个字节,假设一个变量占4字节,那么就是移动到最后一位,也就是最后一个变量,然后就跳转到+27位置,这时候就相当于判断第一个变量(数)与后一个的值,因为 eax=rbx-0X4,而rbx=rsp+0x4,然后执行je判断值是否相等(2*eax==rbx),我们就可以猜出这是一个等比数列,也就是第二关的解是“1 2 4 8 16 32”
  当然,我们上面只是假设,而且根据判断,上面的答案90%是对的,我们现在需要验证的是,%rsp指向的地址存放的值,是不是我们所猜想的那样,也就是是否有6个变量压入栈中。
  这里写图片描述
  (这里是解释+57位置0x18(%rsp)的含义:上面图中的拆弹实验初始输入的是“1 2 3 4 5 6” ,查看了%rsp到其后3个单位(0x4一个单位)的值,可以看到%rsp当前地址存放的值为1,而移动0x4位后就到2,以此类推,而上面移动0x18位的解释是移动的地址满16进位,然后再移动8位,也就是6)
  第二关的解是“1 2 4 8 16 32”
  到此,第二关通关啦~

  
  
=============================2017.7.21更新=============================

  
  

第三关

  有了前面两个练习的基础,后面的关卡分析就知道怎么去拆除bomb了。
  第三关开始,日常打断点,反汇编,题目已经告诉我们要输入两个数字了,所以就尝试性的输入1 2试试。
  这里写图片描述
  
  phase_3反汇编代码:
  这里写图片描述
  从上面的代码可以分析出,%eax值必须大于1(输入的数字数量大于1),以及0x8(%rsp)的值小于7,然后一路到+50的位置,jmpq跳转到内存地址为*0x402470+8*%rax 的位置,我们可以用查内存的,查处该地址存放的是哪条指令。
  
  代码执行到+39位置时,%rsp状态:
   这里写图片描述
  *0x402470+8*%rax 的内存地址存放的指令(%rax存放的值为1)
  这里写图片描述
  从上图可以看到跳转后的地址为0x00400fb9,也就是+118的位置。
  此时接下去运行就是判断%eax(0x137)与0xc(%rsp)(2)值是否相等,不相等就expload ,所以我们就可以得出,我们应该输入的数字是 1 311
  这时候我们就很容易看出,其实这就是一个switch语句,switch(第一个数字),然后看case中的数字是否和我们输入的第二个数字是否相等,否则就expload,如我们输入的是“1 2” ,即switch(1),然后if(2==311)。
  所以,第三关,通关啦~

  
  
  

第四关

  这关算是加上了前几关的一些内容,如调用函数求值,与检测字符
  开始闯关,日常打断点,反汇编。~_~(有点后悔之前不直接把控制台信息复制过来,截图容易崩掉上传图片的一个控件=_=)
  此关需要了解一下算数左/右移与逻辑左/右移的区别,c语言中的移动好像都是逻辑移动。
  
  控制台信息如下:

Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2.  Keep going!
1 311
Halfway there!
14 13

Breakpoint 1, 0x000000000040100c in phase_4 ()
(gdb) disas
Dump of assembler code for function phase_4:
=> 0x000000000040100c <+0>: sub    $0x18,%rsp
   0x0000000000401010 <+4>: lea    0xc(%rsp),%rcx
   0x0000000000401015 <+9>: lea    0x8(%rsp),%rdx
   0x000000000040101a <+14>:    mov    $0x4025cf,%esi
   0x000000000040101f <+19>:    mov    $0x0,%eax
   0x0000000000401024 <+24>:    callq  0x400bf0 <__isoc99_sscanf@plt>
   0x0000000000401029 <+29>:    cmp    $0x2,%eax
   0x000000000040102c <+32>:    jne    0x401035 <phase_4+41>
   0x000000000040102e <+34>:    cmpl   $0xe,0x8(%rsp)
   0x0000000000401033 <+39>:    jbe    0x40103a <phase_4+46>
   0x0000000000401035 <+41>:    callq  0x40143a <explode_bomb>
   0x000000000040103a <+46>:    mov    $0xe,%edx
   0x000000000040103f <+51>:    mov    $0x0,%esi
   0x0000000000401044 <+56>:    mov    0x8(%rsp),%edi
   0x0000000000401048 <+60>:    callq  0x400fce <func4>
   0x000000000040104d <+65>:    test   %eax,%eax
   0x000000000040104f <+67>:    jne    0x401058 <phase_4+76>
   0x0000000000401051 <+69>:    cmpl   $0x0,0xc(%rsp)
   0x0000000000401056 <+74>:    je     0x40105d <phase_4+81>
   0x0000000000401058 <+76>:    callq  0x40143a <explode_bomb>
   0x000000000040105d <+81>:    add    $0x18,%rsp
   0x0000000000401061 <+85>:    retq   

  我们可以看到前面内容和第3关差不多,检测字符,然后判断数字个数是否等于2,再然后就判断第一个参数是否小于14,接下来的内容,我们可以看出,第一个参数的值是小于14的,而+69位置可以判断第二个参数为0,所以我们就看+60位置调用func4传入的参数,以及其反汇编代码。
  从+51位置看起,可以知道调用func4的第二个参数为0,第一个参数为我们传入phase_4的第一个数字或者说是字符(因为传字符进来转化为数字,用变量存着了),当调用完func4函数后,查看%eax & %eax的值是否为0,只有为0才能运行,也就是说func4的返回值必须为0,才能拆除bomb!
  那我们就来看看func4的反汇编吧:

Dump of assembler code for function func4:
=> 0x0000000000400fce <+0>: sub    $0x8,%rsp
   0x0000000000400fd2 <+4>: mov    %edx,%eax
   0x0000000000400fd4 <+6>: sub    %esi,%eax
   0x0000000000400fd6 <+8>: mov    %eax,%ecx
   0x0000000000400fd8 <+10>:    shr    $0x1f,%ecx
   0x0000000000400fdb <+13>:    add    %ecx,%eax
   0x0000000000400fdd <+15>:    sar    %eax
   0x0000000000400fdf <+17>:    lea    (%rax,%rsi,1),%ecx
   0x0000000000400fe2 <+20>:    cmp    %edi,%ecx
   0x0000000000400fe4 <+22>:    jle    0x400ff2 <func4+36>
   0x0000000000400fe6 <+24>:    lea    -0x1(%rcx),%edx
   0x0000000000400fe9 <+27>:    callq  0x400fce <func4>
   0x0000000000400fee <+32>:    add    %eax,%eax
   0x0000000000400ff0 <+34>:    jmp    0x401007 <func4+57>
   0x0000000000400ff2 <+36>:    mov    $0x0,%eax
   0x0000000000400ff7 <+41>:    cmp    %edi,%ecx
   0x0000000000400ff9 <+43>:    jge    0x401007 <func4+57>
   0x0000000000400ffb <+45>:    lea    0x1(%rcx),%esi
   0x0000000000400ffe <+48>:    callq  0x400fce <func4>
   0x0000000000401003 <+53>:    lea    0x1(%rax,%rax,1),%eax
   0x0000000000401007 <+57>:    add    $0x8,%rsp
   0x000000000040100b <+61>:    retq   

  我们可以看到该汇编中含有调用自身的指令,所以这是一个递归过程的函数,我看了挺久没分析出来,觉得这类问题,最好能还原出原代码才容易去分析,当然这个函数还算好还原,所以比较容易的按照上面的汇编代码一步一步还原出来。
  
  还原代码:

int f(int x, int y) {
    int ret = x - y;
    int c = ret > 0 ? 1 : 0;
    ret = (ret + c) >> 1;
    c = ret + y;
    if (c <= x) {
        if (c == x) {
            return ret;
        }
        ret = 2 * f(x, c + 1) + 1;
    }
    else {
        ret = 2 * f(c - 1, y);
    }
    return ret;
}

  从代码中很容易看出,当第一次传入f的参数x为0的时候,才会返回0,我也测试了一下0-14之间,只有当x=0才符合。
  所以第四关的解是 0 0.
  
  
  

第五关

  第五关的难度就大大增大了,代码量也挺多的。
  为了简短一些,直接展示其汇编代码吧,本关卡我一开始输入的字符串是“hellow”。

Start it from the beginning? (y or n) y
Starting program: /root/Desktop/bomb/bomb 
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2.  Keep going!
1 311
Halfway there!
0 0
So you got that one.  Try this one.
hellow

  
  phase_5反汇编:

Dump of assembler code for function phase_5:
=> 0x0000000000401062 <+0>: push   %rbx
   0x0000000000401063 <+1>: sub    $0x20,%rsp
   0x0000000000401067 <+5>: mov    %rdi,%rbx
   0x000000000040106a <+8>: mov    %fs:0x28,%rax
   0x0000000000401073 <+17>:    mov    %rax,0x18(%rsp)
   0x0000000000401078 <+22>:    xor    %eax,%eax
   0x000000000040107a <+24>:    callq  0x40131b <string_length>
   0x000000000040107f <+29>:    cmp    $0x6,%eax
   0x0000000000401082 <+32>:    je     0x4010d2 <phase_5+112>
   0x0000000000401084 <+34>:    callq  0x40143a <explode_bomb>
   0x0000000000401089 <+39>:    jmp    0x4010d2 <phase_5+112>
   0x000000000040108b <+41>:    movzbl (%rbx,%rax,1),%ecx
   0x000000000040108f <+45>:    mov    %cl,(%rsp)
   0x0000000000401092 <+48>:    mov    (%rsp),%rdx
   0x0000000000401096 <+52>:    and    $0xf,%edx
   0x0000000000401099 <+55>:    movzbl 0x4024b0(%rdx),%edx
   0x00000000004010a0 <+62>:    mov    %dl,0x10(%rsp,%rax,1)
   0x00000000004010a4 <+66>:    add    $0x1,%rax
   0x00000000004010a8 <+70>:    cmp    $0x6,%rax
   0x00000000004010ac <+74>:    jne    0x40108b <phase_5+41>
   0x00000000004010ae <+76>:    movb   $0x0,0x16(%rsp)
   0x00000000004010b3 <+81>:    mov    $0x40245e,%esi
   0x00000000004010b8 <+86>:    lea    0x10(%rsp),%rdi
   0x00000000004010bd <+91>:    callq  0x401338 <strings_not_equal>
   0x00000000004010c2 <+96>:    test   %eax,%eax
   0x00000000004010c4 <+98>:    je     0x4010d9 <phase_5+119>
   0x00000000004010c6 <+100>:   callq  0x40143a <explode_bomb>
   0x00000000004010cb <+105>:   nopl   0x0(%rax,%rax,1)
   0x00000000004010d0 <+110>:   jmp    0x4010d9 <phase_5+119>
   0x00000000004010d2 <+112>:   mov    $0x0,%eax
   0x00000000004010d7 <+117>:   jmp    0x40108b <phase_5+41>
   0x00000000004010d9 <+119>:   mov    0x18(%rsp),%rax
   0x00000000004010de <+124>:   xor    %fs:0x28,%rax
   0x00000000004010e7 <+133>:   je     0x4010ee <phase_5+140>
   0x00000000004010e9 <+135>:   callq  0x400b30 <__stack_chk_fail@plt>
   0x00000000004010ee <+140>:   add    $0x20,%rsp
   0x00000000004010f2 <+144>:   pop    %rbx
   0x00000000004010f3 <+145>:   retq   

  一天做两个就花了一堆时间,再看到这样的代码,还是有点无力。
  分析开始:
    首先从+24,+29行,我们可以知道,我们输入的字符串长度必须为6,然后跳转到+122位置,把%eax 设置为0后,再跳转到+41的位置。
    到+41的位置的时候要注意,使用的是movzbl(传送1字节到一个可以容纳4字节的地方,然后其他位填充0和movl用途差不多),此时就是把%rbx+%rax 位置的单字节传送给%ecx,而此时的%rax为0,也就是而%rbx保存的是输入的字符串“hellow” ,所以此时的%ecx 值为h
    接下去,就是把%cl%ecx的最小的寄存器,表示0~7位)传给%rdx
    然后到+52位置这里做了一个and运算,用%edx = %edx and 0xf ,即取0~4位,那取这个来干嘛呢?我们继续看下去,又是一个movzbl 0x4024b0(%rdx),%edx,这时候出现了一个内存地址,0x4024b0(%rdx),因为前面%edx只取后4位,所以%rdx的范围就是0~15 ,而h的ascii码值的16进制,我们可以在查找到。

(gdb) p /x $cl
$15 = 0x65

    不能用x/ 命令去内存中查,因为该寄存器就存着0x65这个值,而不是地址,可以使用命令i register去查看主要寄存器状态,然后再决定用x还是p去查看。
    所以得出了%rdx的值为0x5,然后我们再来看看,0x4024b00x4024b0+0x5的地址存放着什么。

(gdb) x/ 0x4024b0
0x4024b0 <array.3449>:  0x6d
(gdb) x/s 0x4024b0
0x4024b0 <array.3449>:  "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"

    可以看到,这个地址存放着的是个字符数组,我们的第一个命令查到的是第一个字符的ascii值,所以接下来就x/s查询,而0x4024b0+0x5我们就知道了,是从e开始的字符串,即:ersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
    所以我们就知道了,movzbl 0x4024b0(%rdx),%edx之后,%edx存放的是字符e
    接下来就是把%dl传送给%rsp+%rax+0x10位置,而%rax 会自增,增加到0x6,小于6则又跳转到+41位置,开始上面的循环,所以为了方便,也知道是循环了,就设置断点到后面的地址。

(gdb) b *0x4010ae
Breakpoint 2 at 0x4010ae

    我们打的断点在+76的位置,而+76位置的指令是给%rsp+0x16的数据设置为0,而从上面循环中,知道%rsp+0x10+(0x0~0x5) 的位置是存放字符,而这个指令会不会是添加‘\0’字符呢?我们去看看吧。
寄存器状态:

(gdb) i registers 
rax            0x6  6
rbx            0x6038c0 6305984
rcx            0x77 119
rdx            0x73 115
rsi            0x6038c0 6305984
rdi            0x6038c0 6305984
rbp            0x0  0x0
rsp            0x7fffffffdfe0   0x7fffffffdfe0
r8             0x7ffff7ff7007   140737354100743
r9             0x0  0
r10            0x6  6
r11            0x246    582
r12            0x400c90 4197520
r13            0x7fffffffe0f0   140737488347376
r14            0x0  0
r15            0x0  0
rip            0x4010b3 0x4010b3 <phase_5+81>
eflags         0x246    [ PF ZF IF ]
cs             0x33 51
ss             0x2b 43
ds             0x0  0
es             0x0  0
fs             0x0  0
gs             0x0  0

    我们要查找的%rsp地址是0x7fffffffdfe0,那么要查找保存字符的地方,就是 0x7fffffffdff0

(gdb) x/ 0x7fffffffdff0 
0x7fffffffdff0: "nevvls"

    这个字符串的构成,就是按我们输入的字符串中的每个字符的ascii码值的前4位(0~3位)的值设为x(或者说是偏移量)+0x4024b0这个字符串首地址,得到0x4024b0这个地址偏移x位的字符。

0x4024b0:"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"

  接下来的这几条指令,很容易就看出,是比较由我们输入的字符串构成的新字符串,和$0x40245e这个字符串对比,判断是否相等。

   0x00000000004010b3 <+81>:    mov    $0x40245e,%esi
   0x00000000004010b8 <+86>:    lea    0x10(%rsp),%rdi
   0x00000000004010bd <+91>:    callq  0x401338 <strings_not_equal>
   0x00000000004010c2 <+96>:    test   %eax,%eax

  下面这个就是$0x40245e的值了,一开始我以为立即数在内存里面查不了,发现还是能查的,哈哈,似乎是存在一个地址中,而那个地址,我自己猜可能那个是十进制的地址吧。

(gdb) x/s 0x40245e
0x40245e:   "flyers"
或者
(gdb) p 0x40245e
$18 = 4203614
(gdb) x/s 4203614
0x40245e:   "flyers"

  可以看到的是,我们要对比的字符串是"flyers",和我们构成的字符串"nevvls"相差甚远,但我们也知道了,去选取字符串来构成 "flyers",那就是我们输入的字符串第一个字符的ascii值的十六进制应该是(范围是0x61-0x7a,注意小写以及对应的ascii为字符):0x69,0x6f,0x6e,0x65,0x66,0x67,好像解应该只有这一组,因为判断字符相等应该也判断大小写是否相等 。
  至此,复杂的第五关通关~

[root@yanghao bomb]# ./bomb 
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2.  Keep going!
1 311
Halfway there!
0 0
So you got that one.  Try this one.
ionefg
Good work!  On to the next...

  
  
  

第六关

  这关的反汇编代码,炒鸡长…..心态爆炸!!!
  开始今天的闯关,听说还有隐藏关,我!今!天!不!做!了!
  
   phase_6反汇编代码:

Dump of assembler code for function phase_6:
=> 0x00000000004010f4 <+0>: push   %r14
   0x00000000004010f6 <+2>: push   %r13
   0x00000000004010f8 <+4>: push   %r12
   0x00000000004010fa <+6>: push   %rbp
   0x00000000004010fb <+7>: push   %rbx
   0x00000000004010fc <+8>: sub    $0x50,%rsp
   0x0000000000401100 <+12>:    mov    %rsp,%r13
   0x0000000000401103 <+15>:    mov    %rsp,%rsi
   0x0000000000401106 <+18>:    callq  0x40145c <read_six_numbers>
   0x000000000040110b <+23>:    mov    %rsp,%r14 //把rsp的地址给r14
   0x000000000040110e <+26>:    mov    $0x0,%r12d//r12d值为0
   0x0000000000401114 <+32>:    mov    %r13,%rbp 
   0x0000000000401117 <+35>:    mov    0x0(%r13),%eax //第一个元素传送给eax
   0x000000000040111b <+39>:    sub    $0x1,%eax 
   0x000000000040111e <+42>:    cmp    $0x5,%eax // 得出eax<=6
   0x0000000000401121 <+45>:    jbe    0x401128 <phase_6+52>
   0x0000000000401123 <+47>:    callq  0x40143a <explode_bomb>
   0x0000000000401128 <+52>:    add    $0x1,%r12d //r12d++
   0x000000000040112c <+56>:    cmp    $0x6,%r12d  // r12d==6
   0x0000000000401130 <+60>:    je     0x401153 <phase_6+95>
   0x0000000000401132 <+62>:    mov    %r12d,%ebx //ebx=r12d
   0x0000000000401135 <+65>:    movslq %ebx,%rax //rax=ebx
           //eax=rsp+4*rax <==>eax=rsp+4*r12d
   0x0000000000401138 <+68>:    mov    (%rsp,%rax,4),%eax 
           //比较第一个元素和第rax或者r12d个元素是否相等
   0x000000000040113b <+71>:    cmp    %eax,0x0(%rbp) 
   0x000000000040113e <+74>:    jne    0x401145 <phase_6+81>
   0x0000000000401140 <+76>:    callq  0x40143a <explode_bomb>
   0x0000000000401145 <+81>:    add    $0x1,%ebx //ebx之前等于(r12d++),ebx+=1
   0x0000000000401148 <+84>:    cmp    $0x5,%ebx //比较是否ebx<=5
           //如果jle成立,则循环判断所有元素是否相等
   0x000000000040114b <+87>:    jle    0x401135 <phase_6+65>
   0x000000000040114d <+89>:    add    $0x4,%r13 //r13=r13+0x4 第二个元素
   0x0000000000401151 <+93>:    jmp    0x401114 <phase_6+32>//循环
   0x0000000000401153 <+95>:    lea    0x18(%rsp),%rsi
   0x0000000000401158 <+100>:   mov    %r14,%rax
   0x000000000040115b <+103>:   mov    $0x7,%ecx
   0x0000000000401160 <+108>:   mov    %ecx,%edx
   0x0000000000401162 <+110>:   sub    (%rax),%edx
   0x0000000000401164 <+112>:   mov    %edx,(%rax)
   0x0000000000401166 <+114>:   add    $0x4,%rax
---Type <return> to continue, or q <return> to quit---
   0x000000000040116a <+118>:   cmp    %rsi,%rax
   0x000000000040116d <+121>:   jne    0x401160 <phase_6+108>
   0x000000000040116f <+123>:   mov    $0x0,%esi
   0x0000000000401174 <+128>:   jmp    0x401197 <phase_6+163>
   0x0000000000401176 <+130>:   mov    0x8(%rdx),%rdx
   0x000000000040117a <+134>:   add    $0x1,%eax
   0x000000000040117d <+137>:   cmp    %ecx,%eax
   0x000000000040117f <+139>:   jne    0x401176 <phase_6+130>
   0x0000000000401181 <+141>:   jmp    0x401188 <phase_6+148>
   0x0000000000401183 <+143>:   mov    $0x6032d0,%edx
   0x0000000000401188 <+148>:   mov    %rdx,0x20(%rsp,%rsi,2)
   0x000000000040118d <+153>:   add    $0x4,%rsi
   0x0000000000401191 <+157>:   cmp    $0x18,%rsi
   0x0000000000401195 <+161>:   je     0x4011ab <phase_6+183>
   0x0000000000401197 <+163>:   mov    (%rsp,%rsi,1),%ecx
   0x000000000040119a <+166>:   cmp    $0x1,%ecx
   0x000000000040119d <+169>:   jle    0x401183 <phase_6+143>
   0x000000000040119f <+171>:   mov    $0x1,%eax
   0x00000000004011a4 <+176>:   mov    $0x6032d0,%edx
   0x00000000004011a9 <+181>:   jmp    0x401176 <phase_6+130>
   0x00000000004011ab <+183>:   mov    0x20(%rsp),%rbx
   0x00000000004011b0 <+188>:   lea    0x28(%rsp),%rax
   0x00000000004011b5 <+193>:   lea    0x50(%rsp),%rsi
   0x00000000004011ba <+198>:   mov    %rbx,%rcx
   0x00000000004011bd <+201>:   mov    (%rax),%rdx
   0x00000000004011c0 <+204>:   mov    %rdx,0x8(%rcx)
   0x00000000004011c4 <+208>:   add    $0x8,%rax
   0x00000000004011c8 <+212>:   cmp    %rsi,%rax
   0x00000000004011cb <+215>:   je     0x4011d2 <phase_6+222>
   0x00000000004011cd <+217>:   mov    %rdx,%rcx
   0x00000000004011d0 <+220>:   jmp    0x4011bd <phase_6+201>
   0x00000000004011d2 <+222>:   movq   $0x0,0x8(%rdx)
   0x00000000004011da <+230>:   mov    $0x5,%ebp
   0x00000000004011df <+235>:   mov    0x8(%rbx),%rax
   0x00000000004011e3 <+239>:   mov    (%rax),%eax
   0x00000000004011e5 <+241>:   cmp    %eax,(%rbx)
   0x00000000004011e7 <+243>:   jge    0x4011ee <phase_6+250>
   0x00000000004011e9 <+245>:   callq  0x40143a <explode_bomb>
   0x00000000004011ee <+250>:   mov    0x8(%rbx),%rbx
---Type <return> to continue, or q <return> to quit---
   0x00000000004011f2 <+254>:   sub    $0x1,%ebp
   0x00000000004011f5 <+257>:   jne    0x4011df <phase_6+235>
   0x00000000004011f7 <+259>:   add    $0x50,%rsp
   0x00000000004011fb <+263>:   pop    %rbx
   0x00000000004011fc <+264>:   pop    %rbp
   0x00000000004011fd <+265>:   pop    %r12
   0x00000000004011ff <+267>:   pop    %r13
   0x0000000000401201 <+269>:   pop    %r14
   0x0000000000401203 <+271>:   retq   
End of assembler dump.

  我们可以看到其中有call read_six_numbers函数,所以知道这个的解一定是输入包含6个数字的字符串。
  分析开始:
   当程序运行到+23行的时候,查看当前寄存器状态(如下),可以看出 r13和rsp指向相同,执行到+24的时候,r14也指向rsp所指向的地址0x7fffffffdf90

(gdb) i registers 
rax            0x6  6
rbx            0x0  0
rcx            0x7fffffffdf80   140737488347008
rdx            0x7fffffffdfa4   140737488347044
rsi            0x7fffffffd970   140737488345456
rdi            0x1999999999999999   1844674407370955161
rbp            0x0  0x0
rsp            0x7fffffffdf90   0x7fffffffdf90
r8             0x7ffff7dd6060   140737351868512
r9             0x0  0
r10            0x6  6
r11            0x0  0
r12            0x400c90 4197520
r13            0x7fffffffdf90   140737488347024
r14            0x0  0
r15            0x0  0
rip            0x40110b 0x40110b <phase_6+23>
eflags         0x206    [ PF IF ]
cs             0x33 51
ss             0x2b 43
ds             0x0  0
es             0x0  0
fs             0x0  0
gs             0x0  0

  而我们可以查看%rsp的状态:

(gdb) x/10 $rsp
0x7fffffffdf90: 1   2   3   4
0x7fffffffdfa0: 5   6   0   0

  可以看到%rsp指向的数据是1,间隔0x4指向下一个数据。
  总体来看前面的代码,+65位置到+87是一个小循环, +32到+93是一个大循环,要弄清楚循环在做什么,就必须知道寄存器说存储的值以及作用。
  在+65到+87中,rax=r12d的值,而r12d在+52的位置,也就是大循环位置内,小循环外,每次执行就会自增,r12d在大循环内是从1开始每次大循环r12d++,所以小循环内执行的内容是%rsp+0x4*rax 即遍历6个变量与%rbp比较是否相等,那我们就要看看%rbp 是什么了,%rbp在+32位置,大循环内,被%r13初始化,第一次大循环的时候,%r13=%rsp,所以%rbp是第一个元素。
  所以小循环结束后,第一次小循环是判断后面5个是否与相等,结束后%r13+0x4 于是第二次大循环的时候,再执行小循环,就是第二个元素与后面三个元素是否相同,所以以此类推,可以得出这个小循环是用于判断输入的数字是否有重复的,重复则expload!
  然后我们再来看大循环,大循环跳出去的关键在于+60位置的跳转语句,我们来看看+60跳出的条件以及运行到那的顺序,首先要从+42位置判断,即判断%eax==6,而%eax==%r13%r13相当于大循环中依次遍历6个元素的变量 ,所以我们可以判断出,这个大循环的作用是,依次判断每个元素的值是否小于等于6,且执行小循环检查是否有重复元素。
  
  
===========================光分析这前几条汇编就花了好多时间T_T===================
  
  
  按照上面的分析,我们可以看出+100位置到+121位置是一个循环,+143位置到+169位置是一个循环,+130到+181是个大循环,+201到+220是一个循环,+235到+257又是一个循环!!!上面说的循环,只是可能会造成循环,也可能是一个只执行一次的跳转语句。
  这最后一关,很痛苦!一个个分析吧。
  +100到+121处,在+100处,%rax被初始化为"r14"(地址存放着第一个元素),之后给%ecx赋值为0x7,之后%rdx=%rdx-%rax 相当于%rdx=7-%rax ,而%rax=%rax+0x4,一直到+114位置,我们可以总结为假设6个元素存放在ary[6]中,以上的语句经过执行第一次循环后变成ary[i]=7-ary[i]
  之后就到了+163到mov %rsp,%rsi,1),%ecx,执行+163后%ecx值为第一个元素,然后cmp $0x1,%ecx ,如果%ecx小于等于1,则跳转至+143。
  这时候我们就要注意了,在+143的位置指令为mov $0x6032d0,%edx,而经过查询。(因为查看后发现是node1,所以猜测是一种结构体,且会给我们创建出6个节点符合上面检测6个变量的要求)。

(gdb) x/24 0x6032d0
0x6032d0 <node1>:   332 1   6304480 0
0x6032e0 <node2>:   168 2   6304496 0
0x6032f0 <node3>:   924 3   6304512 0
0x603300 <node4>:   691 4   6304528 0
0x603310 <node5>:   477 5   6304544 0
0x603320 <node6>:   443 6   0   0  

  可以看到每个节点应该有3个成员组成,第一个位置应该什么数值,第二个是应该是序号,第三个是指针地址,转换成十六进制就会发现,该链表的顺序为1->2->3->4->6,这应该就是默认方式,我们就分析出来了这个结构体大概如下:

struct node{
    int val;
    int id;
    node *next;
};

  所以查找下一个节点就可以通过首节点地址0x6032d0+0X8就可以访问到指针了,也就是下一个节点。
  所以该段代码大概意思可能猜出来做什么,接下来我们就要看目的,因为汇编代码太难看难懂了,所以我就加了一个断点到这里:

   0x00000000004011d2 <+222>:   movq   $0x0,0x8(%rdx)
   0x00000000004011da <+230>:   mov    $0x5,%ebp
   0x00000000004011df <+235>:   mov    0x8(%rbx),%rax
   0x00000000004011e3 <+239>:   mov    (%rax),%eax
   0x00000000004011e5 <+241>:   cmp    %eax,(%rbx)
=> 0x00000000004011e7 <+243>:   jge    0x4011ee <phase_6+250>

  +243处于+235到+257位置,这里面的循环就是遍历每一个节点,判断前一个节点的值是否大于后一个节点,否则就出发expload程序,所以,我们也大概明白了,这是要我们给节点设置顺序链接,判断是否是数值是降序链接。
  下面是我最先看程序汇编前半部分,输入 1 2 3 4 5 6字符的结果,那为什么是6->5->4->3->2->1呢?这就得看前面我们+100位置到+121位置的分析,因为我们输入的字符在那被处理成了,7-1 7-2 7-3 7-4 7-5 7-6,也可以查找%rsp可以看到值的情况:

十六进制版本节点情况:
(gdb) x/24wx 0x6032d0
0x6032d0 <node1>:   0x0000014c  0x00000001  0x00000000  0x00000000
0x6032e0 <node2>:   0x000000a8  0x00000002  0x006032d0  0x00000000
0x6032f0 <node3>:   0x0000039c  0x00000003  0x006032e0  0x00000000
0x603300 <node4>:   0x000002b3  0x00000004  0x006032f0  0x00000000
0x603310 <node5>:   0x000001dd  0x00000005  0x00603300  0x00000000
0x603320 <node6>:   0x000001bb  0x00000006  0x00603310  0x00000000

此时%rsp的值:

(gdb) x/6 $rsp
0x7fffffffdf90: 0x00000006  0x00000005  0x00000004  0x00000003
0x7fffffffdfa0: 0x00000002  0x00000001

  所以这关卡的解就是: 3 4 5 6 1 2,当然这不是我们要输入的数据,这是转换后的解。而我们要输入的数据就是4 3 2 1 6 5
  至此,两天通了6关,耗费了很多时间,没有去学习接下来的章节,所以隐藏关卡就暂时不考虑啦!

猜你喜欢

转载自blog.csdn.net/qq_21049875/article/details/81113981
今日推荐