Day 6 《修改EIP》
(一)调试
1.调试的基础概念:
断点,就是让CPU一条一条的执行指令,这样方便我们观察寄存器的变化;
EPI:就是我们下次要调到的地址;
注意:mov无法修改EIP的值;
2.JMP指令:修改EIP的值
格式:MOV EIP,寄存器/立即数
#include<stdio.h>
int main()
{
__asm {
mov eax,0xBBBBBBBB
add eax,1
jmp eax
}
//因为有保护模式,在vs上是不允许这么做的
return 0;
}
JMP:多数人理解是跳转,其实是错误的,它没有跳转,只是对EIP进行了修改。它只影响EIP;
MOV EIP,寄存器/立即数 => JMP 寄存器/立即数
3.CALL指令:将当前EIP入栈,并修改EIP的值
格式:CALL 地址A/寄存器
不同的CPU可能有不同的规定。下面只说常见的简单CPU的指令。
常见的CPU的CALL指令(“调用”指令)的功能,就是以下两点:
(1)将下一条指令的所在地址(即当时程序计数器PC的内容)入栈
(2)并将子程序的起始地址送入PC(于是CPU的下一条指令就会转去执行子程序)。
而子程序结尾处通常都要编写一条RET指令(“返回”指令)
RET指令的功能就是一条:从栈中取出一条数据送入PC。
4.练习:
1. CALL执行时堆栈有什么变化?EIP有变化吗?
堆栈会存入一个地址,并且esp会-4,eip会改变数值。
- RET执行堆栈有什么变化?EIP有变化吗?
RET执行,堆栈会pop出,esp+4,eip的值改变。
2017/5/15 20:26:03
Day 7 《堆栈图》
(一)简单堆栈图
源程序:
#include<stdio.h>
void add(int x, int y)
{
x = x + y;
}
int main()
{
add(1, 2);
return 0;
}
汇编代码:
执行堆栈图:
说明:
push ebp这里就是保留栈底
mov ebp,esp
sub esp,4 保留栈底的作用就是提升栈顶。我们程序执行的时候需要用到内存push ebx
push esi
push edi 保存现场,把有可能用的寄存器的值放到内存中去lea edi,[ebp-0xC0]
mov ecx,30h
mov eax,cccccccc 这个作用是我们分配的内存不能放置垃圾数据
2017/5/16 20:47:36
Day 8 《函数》
(一)函数
1.C的函数:
计算机的函数,是一个固定的一个程序段,或称其为一个子程序,它可以实现固定运算功能的同时,还带有一个入口和一个出口;
所谓入口,就是函数所带的各个参数,我们可以通过这个入口,把函数的参数值代入子程序,供计算机处理;
所谓出口,就是指函数的计算结果,也称为返回值,再计算机求得之后,由此口带回给调用它的程序。
2.汇编中的函数:
push ebp //提升堆栈,为函数执行提供空间
mov ebp,esp
sub esp,0C0h
push ebx //保留现场:函数在执行的时候会用到一些寄存器,但这些寄存器中的值很可能会程序用到,所以要先存储到内存中
push esi
push edi
lea edi,[ebp-0C0h] //向分配的空间填充数据
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
mov eax,dword ptr [x] //该函数的功能;在这里是x=x+y;
add eax,dword ptr [y]
mov dword ptr [x],eax
pop edi //恢复现场:将之前保留的寄存器的值恢复
pop esi
pop ebx
mov esp,ebp //降低堆栈
pop ebp //恢复栈底
ret //函数执行完毕,返回到调用处
注意:Debug 和 Release 是有区别的
3.函数的入口:
push 2 //函数的参数
push 1
call add (0134128Ah) //调用函数
call 就是调用一个函数,逆向中经常提到的找Call
4.windows堆栈的特点:
1. 先进后出
2. 向低地址扩展
5.堆栈平衡:
windows中的堆栈,是一块普通的内存,主要用来存储一些临时的数据和参数等。当函数再执行的时候,会用到堆栈,把一些数据存到里面,但用完的时候一定要把栈中的数据弹出清除,否则就会乱,这个就是堆栈平衡;
判断函数的参数,push的个数不是100%准确的。具体的要往里面看。
(二)绘制堆栈图,在函数中又调用一个函数:
源C代码:
#include<stdio.h>
void Fun_1(int a, int b)
{
}
void Fun_2(int a, int b, int c)
{
Fun_1(a, b);
}
int main()
{
Fun_2(0x10, 0x20,0x30);
return 0;
}
汇编代码:
_main:
push ebp
mov ebp,esp
sub esp,0C0h
push ebx
push esi
push edi
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
push 30h //此处开始传入参数调用函数
push 20h
push 10h
call _Fun_2 (0D110Eh) //此处的CS:[EIP] 000D1744
add esp,0Ch //此处的CS:[EIP] 000D1749 将刚刚入栈的 30h、20h、10h数据出栈
xor eax,eax
pop edi
pop esi
pop ebx
add esp,0C0h
cmp ebp,esp
call __RTC_CheckEsp (0D1113h)
mov esp,ebp
pop ebp
ret
(0D110Eh)jmp Fun_2 (0D16C0h)
(0D16C0h)_Fun_2:
push ebp
mov ebp,esp
sub esp,0C0h
push ebx
push esi
push edi
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
mov eax,dword ptr [b]
push eax
mov ecx,dword ptr [a]
push ecx
call _Fun_1 (0D1109h)
add esp,8
pop edi
pop esi
pop ebx
add esp,0C0h
cmp ebp,esp
call __RTC_CheckEsp (0D1113h)
mov esp,ebp
pop ebp
ret
堆栈图:
2017/5/17 20:21:03
Day 9 《绘制堆栈图2》
1.C程序代码:
#include<stdio.h>
int add2(int i, int j)
{
return i + j;
}
int add3(int i, int j,int k)
{
return i + j + k;
}
int main()
{
int a = add2(1, 2);
int b = add3(3, 4, 5);
return 0;
}
2.汇编代码:
main
push ebp
mov ebp,esp
sub esp,0D8h
push ebx
push esi
push edi
lea edi,[ebp-0D8h]
mov ecx,36h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
push 2 //参数入栈 2 1
push 1
call add2 (0811343h) //调用函数add2
add esp,8 //堆栈平衡
mov dword ptr [a],eax
push 5 //参数入栈 5 4 3
push 4
push 3
call add3 (0811348h) //调用函数add3
add esp,0Ch //堆栈平衡
mov dword ptr [b],eax
xor eax,eax
pop edi
pop esi
pop ebx
add esp,0D8h
cmp ebp,esp
call __RTC_CheckEsp (0811109h)
mov esp,ebp
pop ebp
ret
add2
push ebp //开栈
mov ebp,esp
sub esp,0C0h
push ebx //保存现场
push esi
push edi
lea edi,[ebp-0C0h] //给缓冲区填充数据
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
mov eax,dword ptr [i] //函数操作
add eax,dword ptr [j]
pop edi //恢复现场
pop esi
pop ebx
mov esp,ebp //降低堆栈
pop ebp //恢复调用前基地址寄存器的值
ret //函数返回
add3
push ebp //开栈
mov ebp,esp
sub esp,0C0h
push ebx //保存现场
push esi
push edi
lea edi,[ebp-0C0h] //给缓冲区填充数据
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
mov eax,dword ptr [i] //函数操作
add eax,dword ptr [j]
add eax,dword ptr [k]
pop edi //恢复现场
pop esi
pop ebx
mov esp,ebp //降低堆栈
pop ebp //恢复调用前基地址寄存器的值
ret //函数返回
3.堆栈图:
此时调用add2函数:
调用add3函数同上操作;
2017/5/18 10:47:22
Day 10 《裸函数及其执行》
(一)裸函数
1.什么是裸函数:
在试用编译器的时候,其编译器和链接器会为我们生成许多东西。如下图:
1. 普通函数调用:
2. 裸函数调用:
3. _declspec(naked)修饰可以生成一个“裸”函数, 使用后C编译器将生成不含函数框架的纯汇编代码,裸函数中什么都没有,所以也不能使用局部变量,只能全部用内嵌汇编实现。就是告诉编译器,在编译的时候,不要优化代码,通俗的说就是没代码,完全要自己写;
4. 使用__declspec(naked)关键字定义函数:
a.使用 naked 关键字必须自己构建 EBP 指针 (如果用到了的话);
b.必须自己使用 RET 或 RET n 指令返回 (除非你不返回);
_delcspec(naked)用在驱动编写,C语言内嵌汇编完成一些特定功能。
2.练习:
用__declspec(naked)裸函数实现下面的功能:
int plus(int x,int y,int z)
{
int a = 2;
int b = 3;
int c = 4;
return x+y+z+a+b+c;
}
练习目的:
(1)熟悉堆栈结构
(2)参数、局部变量的位置
(3)返回值存储的位置
#include<stdio.h>
void __declspec(naked) plus(int x,int y,int z)
{
__asm {
//保留调用前的栈底
push ebp
//提升堆栈
mov ebp,esp
sub esp,0x40
//保留现场
push ebx
push esi
push edi
//填充缓冲区
mov eax,0xcccccccc
mov ecx,0x10
lea edi,dword ptr ds:[ebp - 0x40]
rep stosd
//函数的核心功能
mov eax,dword ptr ds:[ebp+0x8]
add eax,dword ptr ds:[ebp+0xC]
add eax,dword ptr ds:[ebp+0x10]
//恢复现场
pop edi
pop esi
pop ebx
//降低堆栈
mov esp,ebp
pop ebp
ret
}
}
int main()
{
plus(1,2,3);
return 0;
}
(二)调用约定
1.常见的几种约定:
2.函数入口:
调用方式是自下向上调用的,真正的入口是mainCRTStartup函数;
mainCRTStartup()函数帮我们做了很多的初始化工作;
Main函数实际是传递了三个参数,在OD中,使用控制台程序找到程序入口,就先找到那几个函数然后查看谁push了3次后面调用的就是main。
(三)逆向你的基础C语言
1.数据类型
学习数据类型的三个要素:
1. 存储数据的宽度;
2. 存储数据的格式;
3. 作用范围(作用域);
2.整数类型:char short int long
计算机底层不关心符号,计算机有无符号,是使用者定义的。计算机会根据指定的有无符号来生成对应的汇编语句;
3.浮点数:
如果是负数,只需要在符号位处0改为1即可,例如上图的16.44,如果变成-16.44,其在内存中的存储(16进制表示)为:1F 85 83 C1
再举个例子:
2017/5/19 21:20:13