逆向练习6

每日逆向的理解和积累。

汇编阅读练习

练习1-1

1.汇编代码及解释如下:

  • main函数

2.对应c语言源码如下:

3.该程序未开启canary保护机制。

练习1-2

1.汇编代码及解释如下:

  • main函数


2.对应c语言源码如下:

3.该程序开启了canary保护机制。该程序的实现是为了解决下述问题:有n个人围成一圈,顺序排号。从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位。

练习2

1.汇编代码及解释如下:

  • main函数



2.对应c++语言源码如下:

3.该程序中几个值得注意的知识点如下:

  • 关于输入输出函数调用的理解——

对于上图所示代码的理解:输入的全部是cin>>xx是我们定义的变量,然后>>在其它地方不是输入流的意思。在这个stl库里面通过用operator对重定义,让他和stl库连用的时候是输入流,从而实现对于输入函数的调用。这里要注意,ida中如上图显示的应该就是C++系统库中的函数,分析时可以根据名称查询其具体功能。(C++中的cin和cout分别相当于c语言中的scanf和printf,operator在这里的作用是重定义操作符>>。)

  • 对于VS的安全检查函数__RTC_CheckEsp(见图中注释信息)和CheckStackVars(见下图及描述)的理解——

具体理解参考下图(深入C/C++之基于CheckStackVars的安全检查(VS2008))——

练习3

1.汇编代码及解释如下:

  • main函数





  • Stack::push函数

  • Stack::pop函数

2.对应c++语言源码如下:

  • main.cpp


  • stack.h

3.该程序中几个值得注意的知识点如下:

  • 对于程序中switch-case(以及跳转表)在汇编层面上的表现形式的理解

  • 对于c++中的cin.get()(无参数形式)的理解——如下图c++程序所示

  • 关于用VS编译的C++程序中的各种保护机制在汇编层面上的体现——

(1)Security_Cookie(GS)机制

首先看红色的一句指令。是将___security_cookie变量的值给取出来。然后蓝色的指令就是将取出来的Cookie全局变量与当前EBP的值进行异或运算。与EBP异或有如下作用。

  1. 可以增加随机性,尽可能使不同函数的安全Cookie都不同。

  2. 可以检查EBP是否被破坏,因为在函数结束检查Cookie时,还会将Cookie变量值再次与EBP异或,如果EBP的值没有变化,那么就能恢复成原来的___security_cookie值。

上面绿色的指令便是关键的一步,它是将异或后的值存入[ebp-4]中。当前的EBP就是最开始压入EBP后ESP的值,也就是压入的EBP的地址。减4就刚好挨着一进函数时压入的EBP的地址减4。此时Cookie变量已经在栈帧中了。

接下来看最后的检查部分,粉色的部分前两句指令是将Cookie变量的值重新取出来并异或还原并保存到ECX中。第三句粉色的CALL就是调用__security_check_cookie 函数。

备注:在以下情况中,编译器不会对易受攻击的参数提供安全保护:

  • 函数不包含缓冲区;
  • 如果未启用优化 ( /O 选项(优化代码));
  • 函数具有可变参数列表 (…);
  • 函数标记为 naked (C++);
  • 函数的第一行语句包含内联程序集代码;
  • 如果仅通过在缓冲区溢出事件中不太可能利用的方式使用参数。

参考链接——

(1)深入C/C++之基于Cookie的安全检查(VS2005);
(2)运行时错误检查(/RTC)编译选项及实现原理;

练习4

1.汇编代码及解释如下:

  • main函数(对于红框中代码的具体作用还不太清楚)



  • fish函数

2.对应c语言源码如下:

问题描述——C语言递归解决分鱼问题:A、B、C、D、E这5个人合伙夜间捕鱼,凌晨时都已经疲惫不堪,于是各自在河边的树丛中找地方睡着了。第二天日上三竿时,A第一个醒来,他将鱼平分为5份,把多余的一条扔回河中,然后拿着自己的一份回家去了;B第二个醒来,但不知道A已经拿走了一份鱼,于是他将剩下的鱼平分为5份,扔掉多余的一条,然后只拿走了自己的一份;接着C、D、E依次醒来,也都按同样的办法分鱼。问这5人至少合伙捕到多少条鱼?每个人醒来后所看到的鱼是多少条?

具体编程实现如下——

注意:此处c语言源码是有问题的,在main函数中定义的数组和递归函数fish的名字相同,会导致main函数中的所有fish都被解释成为数组,并不会调用递归函数fish,在汇编层面我们也可以看出这个问题。

3.该程序中几个值得注意的知识点如下:

  • 关于对于如下图所示中的DWARF的理解——

在用ida加载用CodeBlocks编译生成的exe文件时,分析完成之后ida会弹出上图所示对话框,对于其中的DWARF信息的官方说明如下:

DWARF: Store file/line number information in IDB (only if requested, since it comes with a performance penalty)

参考链接——

(1)How to prevent IDA Pro from loading DWARF debug info in batch mode?;
(2)DWARF 中的 Debug Info 格式;

  • 对于如下指令的理解——

在main函数起始处有如下代码。

push    ebp
mov     ebp, esp
and     esp, 0FFFFFFF0h    //0xFFFFFFF0 = 11111111 11111111 11111111 11110000
                           //相当于把esp的最后4个bit位清零, 也就是把stack往低地址对齐使其能够被16整除...
                           //这样对32位或者64位CPU来说,所有的的stack上的基本变量都可以一次访问到(如果地址没有对齐,那么有可能CPU需要访问两次内存来获得一个8 byte的变量) -- 
                           //这样看来,实际上将stack地址在8 byte上对齐对32位甚至64位的CPU都足够了...
                           //但是问题是对于一些 SIMD (single instruction, multiple data) 的指令*规定*访问的地址必须向16 byte对齐 (也就是必须被16为整除 -- 这是从指令效率的方面来设计的).

参考链接——

https://bbs.pediy.com/thread-179449-1.htm

练习5

1.汇编代码及解释如下:

  • main函数


  • TravelChessBoard函数



  • nextxy函数

2.对应c语言源码如下:

问题描述——C语言马踏棋盘:国际象棋的棋盘为8×8的方格棋盘。现将“马”放在任意指定的方格中,按照“马”走棋的规则将“马”进行移动。要求每个方格只能进入一次,最终使得“马”走遍棋盘的64个方格。编写一个C程序,实现马踏棋盘操作,要求用1〜64这64个数字标注马移动的路径,也就是按照求出的行走路线,将数字1,2,……64依次填入棋盘的方格中,并输出。

问题分析如下——

算法设计如下——

具体编程实现如下——



3.该程序中值得注意的知识点如下:

  • lea指令——

对于寄存器来说:第二个操作数是寄存器必须要加[],这里lea就是取[寄存器]的值,如:

mov eax,2
lea ebx,[eax]         ;执行后ebx=2
mov ebx,eax           ;等同于上句
lea ebx,eax           ;编译器报错: error A2070: invalid instruction operands

对于变量来说加不加[]都是一样的效果,都是取变量的地址,相当于指针,如:

num dword 2
lea ebx,num
lea eax,[num]         ; eax为num的地址,如eax=4206598,随程序不同不同,这时ebx==eax
  • mov指令——

对于寄存器来说如:

mov ebx,eax;ebx==2
mov ecx,[eax]          ;可能会报错,因为这里翻译成汇编是mov ecx,DS:[eax]

对于变量来说如:

num dword 2
mov eax,2
mov ebx,num
mov ecx,[num]             ;执行完ebx==ecx==2
  • 总结——加不加中括号[]的区别

lea对变量没有影响是取地址,对寄存器来说加[]时取值,第二操作数不加[]非法;
mov对变量来说没有影响是取值,对寄存器来说是加[]时取地址,第二操作数不加[]是取值。

参考链接——

汇编中中括号的作用以及lea和mov指令的区别