ollydbg 中汇编指令基本原理

本阶段目标:

1 加深阅读调试器中的汇编代码的能力

2 加深对Windows底层理解

3 解决下层bug的能力.

 

 

1 第一个实例:

  int main(int argc, char* argv[])

{

    return 0;

}

 反汇编后:

00401010 >|>  55            PUSH EBP

00401011  |.  8BEC          MOV EBP,ESP

00401013  |.  83EC 40        SUB ESP,40

00401016  |.  53            PUSH EBX

00401017  |.  56            PUSH ESI

00401018  |.  57            PUSH EDI

00401019  |.  8D7D C0       LEA EDI,DWORD PTR SS:[EBP-40]

0040101C  |.  B9 10000000   MOV ECX,10

00401021  |.  B8 CCCCCCCC   MOV EAX,CCCCCCCC

00401026  |.  F3:AB         REP STOS DWORD PTR ES:[EDI]

00401028  |.  33C0          XOR EAX,EAX

0040102A  |.  5F            POP EDI

0040102B  |.  5E            POP ESI

0040102C  |.  5B            POP EBX

0040102D  |.  8BE5          MOV ESP,EBP

0040102F  |.  5D            POP EBP

00401030  \.  C3            RETN

 

指令说明:

STOS 是串存储指令, STOS DWORD PTR ES:[EDI]的功能是将EAX的值放入EDI中,同时EDI自动加4个字节. REP 重复执行ECX所指的数.

 

2 使用LIST_ENTRY

      LIST_ENTRY是一个双向链表结构,它总是在使用的时候被插入到已有的数据结构中.

例:保存文件的文件名和长度

typedef struct _FILE_INFOR{

LIST_ENTRY list_object;

PFILE_OBJECT file_object;

UNICODE_STRING file_name;

LARGE_INTEGER file_length;

}MY_FILE_INFOR, *PMY_FILE_INFOR;

//如果作为链表头,使用时必须初始化

LIST_ENTRY my_list_head;

void MyFileInforInit()

{

         InitializeListHead(&my_list_head);

}

typedef struct _FILE_INFOR{

LIST_ENTRY list_object;

PFILE_OBJECT file_object;

UNICODE_STRING file_name;

LARGE_INTEGER file_length;

}MY_FILE_INFOR, *PMY_FILE_INFOR;

NTSTATUS MyFileInforAppendNode(PFILE_OBJECT file_object,

PUNICODE_STRING file_name,

PLARGE_INTEGER file_length)

{

         PMY_FILE_INFOR my_file_infor = (PMY_FILE_INFOR)ExAllocatePoolWithTag(

PagedPool, sizeof(PMY_FILE_INFOR), MEM_TAG);

if(MY_FILE_INFOR == NULL)

 return STATUS_INSUFFICIENT_RESOURES;

//填写数据

my_file_infor -> file_object = file_object;

my_file_infor -> file_name = file_name;

my_file_infor -> file_length = file_length;

InsertheadList(&my_file_infor, (PLIST_ENTRY)& my_file_infor);

return STATUS_SUCCESS;

}

3 打开文件

  //文件句柄

  HANDLE file_handle = NULL;

  NTSTATUS status;

  //初始化含有文件路径的OBJECT_ATTRIBUTES

  OBJECT_ATTRIBUTES object_attributes;

  UNICODE_STRING ufile_name = RTL_CONST_STRING(L”\\??\\C:\\a.dat”);

  InitializeObjectAttributes(&object_attributes,

& ufile_name,

OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,

NULL,

NULL);

  //以OPEN_IF方式打开文件

  status = ZwCreateFile(&file_handle, GENRIC_READ|GENERIC_WRITE,

&object_attributes, & io_status, NULL, FILE_ATTRIBUTE_NORMAL,

FILE_SHARE_READ,FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE, NULL,

0);

4 单条指令的组成

前缀

(1个字节)

(可选)

操作数

 

MOD-REG-R/M

(可选)

SIB

(可选)

地址偏移

(可选)

立即数

(可选)

(1)前缀:普通前缀

          * 重复前缀

            与锁同样的类型,f2h 条件: 当0标记为1  && rec!= 0重复

                           f3h 条件: 当0标记为0  && rec!= 0重复

          * 锁前缀 f0h

            用来锁总线的

          * 超前前缀

       指示性前缀

       重复前缀也可以是指示性前缀用于SSE

          * 寄存器超越前缀 66h

            如果在32位模式下使用16位寄存器,那么这条指令将出现66h前缀

            如果在32位模式下使用16位地址,那么将出现67h前缀

          * 地址超越前缀 67h

       64 位扩展前缀REX

          REX前缀只在64位模式下有效,这个前缀必须紧靠操作码,不然将被忽略,REX的范围40h~4Fh.

7~4位

3位

2位

1位

0位

REX,必须为0100h

W(Width)

R(Register)

用于扩展MOD-REG-R/M

X(indeX)

扩展SIB索引字节

B(Base)

扩展MOD-REG-R/M或者SIB基域

(2)操作码被分成1-2个字节,一般在一个字节内,被分为几个部分.(不是全部操作码都是如此)

7~2位,操作码

D

W

 W = 1,传送多少个字节由寄存器前缀决定,W=0,传送都是1个字节.

 D: 传输的方向 D = 1时数据从R/M传输到REG,

               D =0 时数据将从REG传输到R/M

(3) MOD – REG – R/M的组成

这个字节的作用则是确定指令是否需要SIB字节,地址偏移和立即数

7~6位MOD

5~3位REG

2~0位R/M

MOD模式

MOD的值

意义

00

没有使用地址偏移

01

使用8位的地址偏移

10

使用16位或32位的地址偏移当前模式决定是16位还是32位,可以被超前模式改

11

仅仅使用通用寄存器,此时R/M一定是寄存器,此外的情况是地址

REG-R/M:当R/M表示寄存器的时候,和REG表示寄存器部分表示相同都是3个位,表示0~7的时候分别表示了8种寄存器.

 

REG或R/M

字节

双字

SSE

MMX

000

a1

ax

eax

ss0

mm0

001

c1

cx

ecx

ss1

mm1

010

dl

dx

edx

ss2

mm2

011

bl

bx

ebx

ss3

mm3

100

ah

sp

esp

ss4

mm4

101

ch

bp

ebp

ss5

mm5

110

dh

si

esi

ss6

mm6

111

ah

di

edi

ss7

mm7

考虑64位扩展前缀时的情况,REX.R为1,MOD-REG-R/M的REG域扩展成了4位,可以索引16个寄存器.如果REX.B 为1,MOD-REG-R/M扩展成了4位,同样可以索引16个寄存器.

  当R/M是内存地址时,需要知道的是表示那个地址.

 

R/M的值

表示的地址

000

ds:[eax]

001

ds:[ecx]

010

ds:[edx]

011

ds:[ebx]

100

使用SIB字节

101

ss:[ebp]使用特殊寻址

110

ds:[esi]

111

ds:[esi]

当MOD为00并且R/M为5时(表示ebp)时表示MOD-REG-R/M字节后还有地址偏移,即[ebp+00]

当R/M为5时,也可以使用指令访问内存地址.mov [0x12345678],ebx

64位模式下,使用mov [rip + 0x12345678],ebx

(4)SIB意义Scale – index – Base占用一个字节

7~6位

5~3位

2~0位

Scale

Index

Base

SIB是可选的,在MOD-REG-R/M后面用于R/M部分使用比例变址寻址方式,SIB表示一个地址.计算方法:

地址 = Base(表示寄存器内容) + Index(表示寄存器内容)*(2^Scale)

REX.X为1时,Index被扩展为4个位

REX.B为1时Base扩展为4个位,此时表示16位寄存器

 (5)地址偏移:是否使用依赖于MOD-REG-R/M,可能是 1. 2. 4字节如果存在,则位于MOD-REG-R/M或者SIB后面

 (6)立即数:取决于寻址方式的需要,为有符号数可能1,2,4个字节

 

5 分页内存不可执行保护

31 – 12位地址

11 – 6位保留

D

A

PCD

PWT

U位

W位

P位

P位: 表示这个页是否存在.

W位:表示是否可写.

U位: 是一个用户自定义的属性,如果为1,表示这是用户项.可以被R0和R3级的代码访问,如果为0,是系统页,只能被R0级别的代码访问.

页面只有读写的控制,并没有可执行的控制.可以通过简单的跳转指令,跳转到某一个地址.

注意;Windows留下了关闭NX保护开关,在boot.ini中,windows内核启动参数增加/EXECUTE即可关闭此功能.

 

6 sysenter虽然在R3下执行,但是跳转到目的地址却一定要在R0下制定,换句话说就是,可以再R3下调用sysenter,却无法控制跳转的地址.

跳转的地址是由windows预先设置的,地址的设置方法,这些地址保存子啊3个特殊的寄存器中.

寄存器

功能

代号

SYSENTER_CS_MSR

保存跳转之后cs的内容

174h

SYSENTER_CS_ESP

保存跳转之后esp的内容

175h

SYSENTER_CS_EIP

保存目标地址

176h

CS : 选择一个GDT中段描述符,R3不能修改

esp:堆栈指针.

eip: 跳转之后执行的第一条指令的目标地址,这个被windows固定死了.

设置方法:调用wrmsr指令,这是特权指令.

mov ecx, 0x8                        ; windowsxp下代码段选择子

mov dword ptr eds:[eax], 0x8???????    ;固定跳到某个内核地址

wrmsr                             ;写入

值得注意的是:SYSENTER_CS_MSR总是被设置为8,windows中所有的内核代码段都共用这个描述符-实际计算的段基地址总是0,这是为何windows下虚拟地址总是等于线性地址的原因

此外进入sysenter执行后的堆栈段,以及sysexit返回后的代码段和堆栈段也需要确定段选择字.只需设置一个SYSENTER_CS_MSR就可以

段选择子

功能

SYSENTER_CS_MSR值

用于sysenter执行后代码段

SYSENTER_CS_MSR值+8

用于sysenter执行后堆栈段

SYSENTER_CS_MSR值+16

用于sysexit执行后代码段

SYSENTER_CS_MSR值+24

用于sysexit执行后堆栈段

从上表可以看出,既然内核代码段的选择子总是8h,那么堆栈段的选择子只能是10h而R3代码段的选择子就只能是1bh

总结:sysenter

(1)装载SYSENTER_CS_MSR到cs寄存器

(2)装载SYSENTER_EIP_MSR到eip寄存器

(3)装载SYSENTER_CS_MSR+8到ss寄存器

(4)装载SYSENTER_ESP_MSR到esp寄存器

(5)切换到R0层

(6)清除eflags寄存器中vm标志

(7)执行eip开始的R0例程.

sysexit

(1) SYSENTER_CS_MSR值+16装载到cs寄存器

(2)将edx的值送入eip

(3) SYSENTER_CS_MSR值+24装载到ss寄存器

(4)将ecx的送入ESP

(5)切换回 R3

(6)执行eip处的R3指令

 

 

经验:

1. 如果使用RtlCopyUncicodeString拷贝字符串,必须保证拷贝的字符串有足够的空间,如果没有调用RtlinitEmptyString让字符串为空,可能会失败.

2. DbgPrint 应用中, #define KdPrint DbgPrint,由于KdPrint只接受1个参数,使用时必须将参数用括号抱起来.

3.

posted @ 2012-12-07 09:26  小金马  阅读(1175)  评论(0编辑  收藏  举报