zoukankan      html  css  js  c++  java
  • 天书夜读:从汇编语言到Windows内核编程笔记(1)

     汇编部分

    0、call、ret、push、pop、add、sub都可以操作堆栈,栈顶比栈底的地址小,由esp寄存器指向;
    1、call 的本质相当于push+jmp,ret的本质相当于pop+jmp。

      call用来调用函数,先将下一条指令的地址压栈,再跳转到调用函数的开始处;ret弹出返回指令,再跳转;

    2、数据传送指令:

      mov dst,src                    :数据移动指令;

      xor  eax,eax                   :快捷的清零指令;

      lea edi,[ebp-0cch]            :load effect address,方括号表示存储器,即ebp-0cch地址处得内容,该指令取得该内容的地址--ebp-0cch;

      stos dword ptr es:[edi]   :串存储指令,从es:[edi]开始,将eax中的内容以双字存储ecx次;

    3、Windows中,不管哪种调用方式都是返回值放在eax中,然后返回。外部从eax中得到值。

    4、Ebp总是被我们用来保存这个函数执行之前的esp的值,即当前函数的栈底;

    5、把局部变量区域初始化成全0cccccccch,0cch实际是int 3 指令的机器码,这是一个断点中断指令。

    6、C语言循环反汇编:

      for:

         mov  <循环变量>,<初始值>                 ;给循环变量赋初值

        jmp B                                                ;跳到第一次循环处

        A: (循环改动变量)                            ;修改循环变量

          ……

        B: cmp  <循环变量>,<限制变量>         ;检查循环条件

          jge  跳出循环

          (循环体)

          jmp A                                           ;跳回去修改循环变量

      if:

        cmp  <条件>                                      

        jle <下一个分支>

    7、任何一段中间不加任何跳转,连续的mov和加减乘除指令一般都可以还原为一个C表达式。

      如果有下面的代码段,说明可能是含有数组或结构体

        Mov eax,<数组下标>

        Inul eax,eax,<结构大小>

        Mov ecx, <结构数组开始的地址>

        Mov eax, dword ptr [ecx + eax]

      若要访问结构内部变量的时候,最后面的一个指令还会加上一个数字:

        mov  eax,dword ptr[ecx+eax+0ch]

    8、分析发行版汇编指令时:(看书,待练习

      与堆栈操作相关的,call,ret等相关指令,我们叫做函数调用([函数])指令:  F指令

      流程控制代码,涉及对循环变量的操作指令,判断和跳转指令:C指令

      数据处理指令,其它一般为数据处理指令:D指令

     

    内核基础

    1、基本概念
       首先需要安装DDK (Device Driver Kit),这里我选择Microsoft Windows Server 2003 SP1 DDK。
       Windows 驱动分成两类,一类是不支持即插即用的NT式驱动,一类是支持即插即用的WDM((Windows Driver Model))驱动。NT式驱动的安装是基于服务的,可以通过修改注册表进行,也可以直接通过服务函数,如CreateService进行安装;但WDM 式驱动不同,它安装的时候需要通过编写一个inf文件进行控制。
       Driver.h头文件中包含了开发NT式驱动所需要的NTDDK.h,此外还定义了几个标志来指明函数和变量分配在分页内存还是非分页内存中。Windows驱动程序的入口函数是DriverEntry函数。
      有两种编译驱动的办法,一种是用DDK环境来编译,需要在源代码所在目录下创建两个文件makefile和Sources,功能是引入DDK的bin目录下 的makefile.def文件,然后在开始菜单中选择“Windows XP Checked Build Environment”编译环境,进入需要编译的目录,输入”build“命令就可以;第二种编译方式是使用VC++进行编译。[1]]
    为了调试方便,最好安装一个虚拟机。[2]
      Windows的驱动模型概念,本来是就驱动程序的行为而言的。比如WDM驱动,必须要满足提供n种被要求的特性(如电源管理、即插即用)才被称为WDM驱动。如果不提供这些功能,那么统一称为NT式驱动。同样的,WDF驱动也有它的一系列规范。
      WDF(Windows Driver Foundation)驱动是可以调用传统型驱动所调用的内核API的,WDF可以视为传统型的升级版。[3]
    WDK = DDK (Driver Development Kit) + HCT Kit (Hardware Compatibility Test) + WDF (Windows Driver Foundation) + DTM (Driver Test Manager) + WDF Driver Verification Tools + IFS Kit (Installable File Systems Kit) + Free ISO image download - Visual Studio 2005 out of the box integration[4]

     

    2、基本语法[5]


    (1)字符串
      在驱动开发中四处可见的是Unicode字符串。因此可以说:Windows的内核是使用Uincode编码的。ANSI_STRING(单字节)仅仅在某些碰到窄字符的场合使用。而且这种场合非常罕见。一个定义如下:


    typedef struct _UNICODE_STRING {
      USHORT Length; // 字符串的长度(字节数)
      USHORT MaximumLength; // 字符串缓冲区的长度(字节数)
      PWSTR Buffer; // 字符串缓冲区指针
    } UNICODE_STRING, *PUNICODE_STRING;


      UNICODE_STRING并不保证Buffer中的字符串是以空结束的。因此,类似下面的做法都是错误的,可能会会导致内核崩溃:


    UNICODE_STRING str;

    len = wcslen(str.Buffer); // 试图求长度。
    DbgPrint(“%ws”,str.Buffer); // 试图打印str.Buffer。


      如果要用以上的方法,必须在编码中保证Buffer始终是以空结束。但这又是一个麻烦的问题。所以,使用微软提供的Rtl系列函数来操作字符串,才是正确的方法。[6]

     

      字符串的初始化: 宏定义初始化, UNICODE_STRING sty = RTL_CONSTANT_STRING(L"my first string");     //ntdef.h中定义的宏

                 函数初始化, RtlInitUnicodeString(&str,L"my first string!");

      字符串拷贝: 使用UNICODE_STRING要确保其缓冲区的长度

          RtlInitEmptyString(dst, dst_buf,256*sizeof(WCHAR));

          RtlCopyUnicodeString(&dst,&src);

      字符串连接:    RtlAppendUnicode ToString(&dst,L"my second string.")

             RtlAppendUnicodeStringToString(&dst,&src)        //将src连接到dst上

    (2)内存与链表
      
    内存分配:

      ExAllocatePoolWithTag(NonpagedPool/PagedPool, length, 自定义的内存标记32位数); //NonpagedPool永远存在于物理内存中,不会被交换到硬盘上
      ExFreePool(dst.Buffer)    //释放内存指针即可,不能释放栈空间,必须保证和ExAllocatePoolWithTag的成对关系


    LIST_ENTRY中的数据成员Flink指向下一个LIST_ENTRY。windows内核中使用LIST_ENTRY作为链表,这个结构几乎随处可见。
      整个链表中的最后一个LIST_ENTRY的Flink不是空。而是指向头节点。得到LIST_ENTRY之后,要用CONTAINING_RECORD来得到链表节点中的数据。
      锁一般不会定义成局部变量。可以使用静态变量、全局变量,或者分配在堆中。

      LIST_ENTRY如果是作为链表的头,在使用之前,必须调用InitializeListHead来初始化。

     

      LIST_ENTRY插入到MY_FILE_INFOR结构(或者其他自定义结构)头部的好处:一个MY_FILE_INFOR看起来就像一个LIST_ENTRY。否则,就可以用下面的宏来获得链表节点。

      CONTAINING_RECORD(address, type, field) :  宏定义,作用是通过一个address结构的指针(指向type结构中的field),找到该指针指向的field所在结构(type类型)的指针。
    文件操作
      在内核中不能调用用户层的Win32 API函数来操作文件。在这里必须改用一系列与之对应的内核函数。


    VOID InitializeObjectAttributes(
      OUT POBJECT_ATTRIBUTES InitializedAttributes,   //OBJECT_ATTRIBUTES结构的指针
      IN PUNICODE_STRING ObjectName,                    //对象名字字符串,即文件路径
      IN ULONG Attributes,               //可以是OBJ_KERNEL_HANDLE等
      IN HANDLE RootDirectory,                                 //用于相对打开的情况,ObjectName为绝对路径的话,一般为NULL
      IN PSECURITY_DESCRIPTOR SecurityDescriptor   //用于设置安全描述符

    );


      Windows内核中,无论是打开文件,还是注册表,设备等,都会先调用初始化一个OUT POBJECT_ATTRIBUTES


      OBJ_KERNEL_HANDLE表明打开的文件句柄一个“内核句柄”。内核文件句柄比应用层句柄使用更方便,可以不受线程和进程的限制。在任何线程中都可以读写。同时打开内核文件句柄不需要顾及当前进程是否有权限访问该文件的问题(如果是有安全权限限制的文件系统)。


      路径并不是像应用层一样直接写“C:\\a.dat”,而是写成了“\\??\\C:\\a.dat”。这是因为ZwCreateFile使用的是对象路径。“C:”是一个符号链接对象。符号链接对象一般都在“\\??\\”路径下。


    注册表[5]
      应用编程中对应的子键 驱动编程中的路径写法


    HKEY_LOCAL_MACHINE \Registry\Machine
    HKEY_USERS \Registry\User
    HKEY_CLASSES_ROOT 没有对应的路径
    HKEY_CURRENT_USER 没有简单的对应路径,但是可以求得
    ZwOpenKey
    ZwQueryValueKey
    ZwSetValueKey


    时间和定时器

    获得实际的毫秒数

    void MyGetTickCount (PULONG msec)
    {
      LARGE_INTEGER tick_count;
      ULONG myinc = KeQueryTimeIncrement();         //获得一个“滴答”的100纳秒数
      KeQueryTickCount(&tick_count);                      //获得“滴答”数
      tick_count.QuadPart *= myinc;
      tick_count.QuadPart /= 10000;                        //1毫秒=1000000纳秒
      *msec = tick_count.LowPart;
    }

    获得计算机的时间,并打印年月日

      PWCHAR MyCurTimeStr()

      {

        LARGE_INTEGER snow,now;

        TIME_FIELDS now_fields;

        static WCHAR time_str[32] = {0};

        KeQuerySystemTime(&snow);                             //获取标准时间,格林威治时间

        ExSystemTimeToLocalTime(&snow,&now);           //转换为当地时间

        RtlTimeToTimeFields(&now,&now_fields);       //转换为人们可以理解的时间要素

        RtlStringCchPrintfW(                                        //打印到字符串中

          time_str,

          32*2,

          L"%4d-%2d-%2d %2d-%2d-%2d",

          now_fields.Year,now_fields.Month,now_fields.Day,

          now_fields.Hour,now_fields.Minute,now_fields.Second);

        return time_str;

      }


    KeSetTimer
    内核的代码始终运行在某个“中断级”上。Dispatch > APC > Passive


    参考
    [1] http://www.cnblogs.com/phinecos/archive/2009/02/19/1393803.html
    [2] http://www.cnblogs.com/qsilence/archive/2009/06/11/1501511.html
    [3 http://msdn.microsoft.com/en-us/library/ff557565%28VS.85%29.aspx
    [4] http://www.cnblogs.com/wanghao111/archive/2009/05/25/1489041.html
    [5] Windows驱动编程基础教程.doc
    [6] Windows DDK

     

  • 相关阅读:
    微软软件
    绘图软件安装出错解决方法
    Windows平台 Faster-RCNN 制作自己的数据集
    POJ2456 Agressive Cows
    P1030 求先序排列
    Luogu P2015二叉苹果树
    P2234 [HNOI2002]营业额统计
    Luogu P1347排序
    Luogu P1038神经网络
    Luogu P1006传纸条
  • 原文地址:https://www.cnblogs.com/forlina/p/2100336.html
Copyright © 2011-2022 走看看