《老码识途:从机器码到框架的系统观逆向修炼之路》- 第1章 - 总结
本章学到了什么
- 调试技巧:在VS中断点调试,查看反汇编代码,step into进行步进调试,运行过程中查看寄存器、内存地址、变量值变化等。
- 机器码构造能力:使用C/C++中的直接在C代码里写汇编语言的功能(_asm)。学会了常见的汇编指令,接触了几个带有循环、跳转的汇编语言代码。
- 指针机制:对C/C++中的指针机制有了更深的了解。
- 函数调用机制:函数调用过程中栈的变化。函数调用约定的大致了解。
- 数组模型
- 结构体模型
- 对齐:搞清楚了为什么对齐这么重要。
- switch分析:switch的汇编实现原理。
- 加载期重定位:二进制编译和加载过程中的重定位机制究竟是怎么回事。
学习感想
- “猜测 - 实证 - 构建”的学习方法。
在学习底层相关的知识时,总会遇到很多问题。那么遇到问题,一定要追根究底,不能马马虎虎就过去了。不能“大概明白了”,而是要真的搞明白。这个具体的过程就是,在学习的过程中,提出问题,猜想这个问题的答案可能是什么,根据自己的猜想去写代码或者调试求证。证实了之后,提炼出这个问题包含的知识点,再构建例子与验证它。这个完整的过程搞明白了,对于一个知识点才算是真正地理解了。
反观我自己之前的学习方法,看资料的时候,往往一带而过,缺乏追根问底的精神,所以感觉一个东西好像搞明白了,其实根本就没有。在平日学习中遇到的问题,很多时候也是上网搜一下知道怎么回事就算了,却没有自己动手做一做,下一次一遇到的时候,还是不会。
- 体系化的学习可遇而不可求,要学会零散式地学习。
学习底层知识时,大部分知识点都是零散的,所以我们不应该好高骛远,追求一蹴而就。在学习的过程中,我们会遇到很多实际问题,然后发现自己这也不会那也不会,感觉好像无从下手。我认为,应该就事论事,遇到问题,就去搜索对应的解决方案,发现了一个问题解决一个问题,每次解决一个小的知识点,整个知识网络就在解决这些小知识点的过程中慢慢构建起来了。打个比喻,我们的学习一开始全是漏洞,补都不补过来,但是不要失去信心,每次解决一个小问题,慢慢地零散的知识就结成网了,慢慢变得滴水不漏。
学习笔记
- 猜测 - 实证 - 构建
- 使用VS2008的反汇编、监视窗口、内存窗口、单步、断点、全局变量赋值的反汇编。
- C语言中的指针大小为4,只存放了地址,那么类型信息有什么用呢?类型信息决定了在该地址处理数据的大小,即赋值/读取时写/读多少字节。例如int *p,那么对应汇编指令会使用dword,就是4字节。
- 指针强制转换的影响不是发生在转换时(因为地址都是4字节),而是在转换后赋值的时候,访问内存的字节大小。要保证指针强制转换是安全的,必须保证转换后的指针指向的数据类型大小小于原数据类型大小。
- 对于一个补码形式的负数求其正数值,就是求反加1。
- x86系列CPU的call指令寻址方式为:用与call指令相关的偏移量定位到跳转的地址。
偏移量计算:偏移量 = 跳转到的地址 - call指令后一条指令的起始地址。 - call指令将返回地址保存在内存中,而且ESP寄存器指向了该内存。实际上这块内存就是栈——ESP指向栈的栈顶。每次压栈,栈顶地址变小,即ESP的值变小。
实际上call指令相当于两条指令的组合:
push 返回地址
jmp 函数入口地址 - C语言的参数传递是从右往左压栈传递参数。
- ESP的存在是为了指明栈顶的位置,那么EBP存在是为了什么呢?为了每一次调用函数时,能够顺利找到压到栈中的参数位置。怎么找?利用一个确定的基址加上偏移。这个基址就是EBP。为了确保EBP的正确,每个函数调用时都要有保存和恢复EBP的过程。
push ebp
…
pop ebp
在被调用的函数里执行时,EBP用来作为基址获取参数的值。
在函数内分配局部变量的时候,要使用栈更低地址的空间,也就是继续压栈。
所以,在使用EBP寻址的函数中,EBP+偏移量就是参数的地址(要回溯寻找),EBP-偏移量就是局部变量的地址。 - ret指令将栈顶保存的地址值弹入寄存器EIP,即pop eip。
编译器必须保证执行ret时,ESP正好指向call指令压栈保存返回地址的那段内存。 - 编译器习惯上使用eax作为存储返回值的寄存器,被调用方在ret前设置eax,返回后,调用方从eax获取到该值。
- lea eax, [ebp + 10h] 即eax = ebp + 10h
- 平衡栈/清栈的两种方式:①调用方清栈,call返回后,执行add esp, x指令。②被调用方清栈:执行ret x指令。前者用空间代价换取了变参功能。
- 调用惯例calling convention
(1)是寄存器还是栈传递参数
(2)栈传递时,参数是从右往左还是从左往右压栈
(3)谁来清栈,是调用方还是被调用方
例如,C语言的调用方式是栈传参,参数从右往左压栈,调用方清栈。
_stdcall是微软系统调用采用的惯例,除清栈是被调用方外,其他同C语言方式。
_fastcall是寄存器传递。 - 函数指针
函数指针包括入口地址和函数原型(函数参数表、返回类型、调用惯例)两方面的信息。
函数指针赋值的原则是:只能将与指针原型匹配的函数的入口地址赋值给它。
大多数函数指针强制类型转换都会出错,所以不要进行函数指针的强制类型转换。
函数指针可用于虚函数调用。 - 基本数据(如单字节、双字节、四字节整数)存放处的地址必须能被自己数据类型的大小整除。
对齐的规律:首先选定一个盒子,然后依序将字段往盒子中放,当盒子放不下后,又用下一个盒子存放,直至所有字段都存放完毕。
盒子长度 = min{max{sizeof(成员变量)}, 对齐长度}
字段放入盒子的可放置位置如下:
离盒子头部偏移字节数 = n * sizeof(成员变量)(n=0, 1, 2, …)
在编程的时候,遇到结构体,要注意是否有对齐问题。 - switch不能处理浮点数的原因是,它会将该数映射为数组的索引。
实际上是一种跳转地址表的方法,计算复杂度不因分支的增加而增加,在大部分情况下比if-else要快。 - 在CPU保护模式下,每个执行进程(程序的一个实例)都拥有自己独立的线性地址空间,这种机制叫虚存系统。用户态程序无法直接访问物理内存。
每个进程都有自己独立的0~4GB的线性地址空间。
编译的时候就知道全局变量地址。这是因为编译器就能确定所有全局变量相对头部的偏移量,只要程序加载到编译器希望加载的地址,则所有全局变量地址在编译器都可以计算出。
全局变量地址 = 程序头部加载地址b + 全局变量相对程序头部的偏移量a
程序中要存储这个希望加载的地址,称为image base(基址)。
如果不得不加载到一个不是希望加载的地址,那么就要进行重定位(显然这种情况经常出现)。如何确保修改成功呢?只要所有变量与基址的相对位置的偏移量是确定的,那么就没问题了。这样不需要去理解指令类型。
relocAddr = actualBase + a
*relocAddr = *relocAddr(重定位前) + actualBase - expectedAddr
整个程序中有好多偏移量,所以程序中有一个表来存储这些偏移量,称为重定位表。
Windows中的真实重定位是这样的:为了节省重定位项的空间,不是用4字节表示到程序头部的偏移量,而是将需要重定位的部分划分成一个个区(section)
总偏移 = 区起始的偏移 + 2字节表示的区中的偏移 - 动态链接库中的重定位
3个重要的API:LoadLibrary,将DLL从硬盘加载到内存;GetProcAddress,接收函数名作为输入,返回该函数入口地址。FreeLibrary,当获得函数地址后,调用此API卸载已加载库。
在Win7之前,为了达到让所有程序共享同一DLL代码的目的,系统会将所有DLL加载到同一地址,就可以共享代码段进而节省空间。但是系统DLL固定加载基址这个特性,会被Windows下的溢出攻击利用。该个性如果不存在就几乎可以消除Windows下的溢出攻击利用。Windows 7中引入了dll随机加载的选项。(其实就是ASLR) - 利用RTL学习汇编:
(1)首先用汇编实现开发环境所带的运行时库(Run Time Library, RTL)中的函数,如C语言的RTL包括strlen、strcpy等。
(2)然后分析系统库实现的这些函数,因为它们调用频繁,所以要求有很高的效率,基本都用汇编撰写。
(3)再做性能实验,测试自己版本与系统版本的差异,并分析修改(可利用指令级分析工具Vtune)。
(4)最后分析不同库实现的异同和好坏,如VC、C++ Builder、Delphi、GCC。 - 还有一种融汇底层知识的方法:通过编写攻防软件,将操作系统、汇编、编译原理、网络等知识在极为幽微处(如字节层次)贯通起来。
《老码识途:从机器码到框架的系统观逆向修炼之路》- 第1章 - 总结相关推荐
- 老码识途:从机器码到框架的系统观逆向修炼之路 pdf电子书
重要提示尊敬的用户您好,由于老码识途:从机器码到框架的系统观逆向修炼之路pdf书受百度网盘影响无法做公共分享,只能私密分享,有不到之处请多多谅解! 百度网盘链接: http://pan.baidu.c ...
- 《老码识途》读书笔记:第一章(上)
<老码识途>读书笔记:第一章--欲向码途问大道,锵锵bit是吾刀(上) 1.赋值语句 对于全局变量赋值语句,例如下面这句: 1 int gi; 2 void main(int argc ...
- 老码识途学习笔记(一)
第一章 全局变量引发的故事 1 程序存储区 程序存储区一般有下列几段: 程序代码区(SECTION.txt ): 用来存放可执行文件的操作指令(二进制),也就是说是它是可执行程序在内存中的镜像.代码段 ...
- 老码识途读书笔记 1
知识点记录: 1.int 或指针类型的全局变量默认初始化为0,局部变量则为0xcccccccc.(win7 + vs2008 ) 2.内存溢出攻击即使用6个字节空间改变程序执行流程达到某种目的.话说当 ...
- 老码识途之对象函数调用
上一期,我们讨论了普通函数的调用过程,如果没弄明白,看这里 今天所要讲的将是对象调用函数. class C{public:int a;int b;int c;void f(int t){a = t;} ...
- 老码识途——在堆中构建mov和jmp指令
// asmjmp.cpp : 定义控制台应用程序的入口点. // #include <stdio.h> #include <malloc.h>int gi; void * a ...
- 老码识途之构造函数和析构函数
对象初始化过程就是先父类构造函数,再子类构造函数.,那么我们从汇编角度去探索这个过程是怎么样的 class P{public:int a ;P(){a = 1;}~P(){a = 4;} };clas ...
- 读书 --- 老码识途
上周在图书馆借了这本书,这个周末细看了下,是本好书.作者应该是个大学教授叫韩宏.书中讲的很底层,一开始就告诉大家如何debug一段程序,在VS2008里面查看内存.寄存器.反汇编.通过这些来认识汇编. ...
- 老码识途1之函数调用和局部变量
无论在编程中,还是在面试中,都会遇见调用函数这个东东,但是,要是让你说函数是怎么调用的,你能回答上来吗,接下来就让我们一起探索函数如何在汇编层次上实现调用的 在接下来,我们将有几个问题要去解决 函数调 ...
最新文章
- MySQL复习资料——用于突击考试总结
- mysql+create+table+index_mysql------基础及常见SQL技巧
- 参加kaggle比赛
- Android ViewPager + Fragment的布局
- 遇到IIS configuration error错误的可以看看,不一定是权限问题
- 你真的了解 MySQL 数据库的运行状况吗?
- OpenJDK8 272在MIPS上的编译修改记录
- Android 布局优化之include与merge
- RS-274drillIPC-2581三种PCB Gerber文件的观察软件
- 用iPhone打造个人的GTD(Get Things Done)实践
- 【示波器专题】示波器探头的原理和分类
- Linux技术简历项目经验示例(二)
- svn图标不显示的解决方案
- 计算机单位厘米 像素,像素厘米转换器
- HDFS文件访问权限
- 中e管家如何投资理财收益最大化
- vue 调起浏览器打印
- iOS和Android和H5交互WebViewJavascriptBridge
- java计算机毕业设计钢材出入库管理系统(附源码、数据库)
- 客服回复话术100句