第一章 汇编开发环境

目标

  • 搭建windows下32位汇编开发环境

  • 熟悉几个常用反汇编工具

参考书籍

  • 《 WindowsPE权威指南 》 第一章

  • 《 Windows 环境下32位汇编语言程序设计》 第二章

目录


1、开发环境选择

1、MASM32

Masm32 是不同工具软件的集合,使用汇编编译器MASM软件包中的ML.exe 资源编译器和32位连接器使用的是VS 的Rc.exe 和 Link.exe Lib.exe Dumppe.exe等

其中 win32编程 和win PE 都使用此汇编软件

  • 下载 masm32v11r.zip
  • 如果64位环境下无法安装 可在32位下安装后直接copy使用

2、NASM

跨平台 Linux 和 windows都可使用 更灵活

<<x86汇编语言-从实模式到保护模式>>使用NASM

3、EditPlus

可以使用 EditPlus、 sublimeText、 NotePad++ 、 Vscode 等进行代码编辑

  • 配置 EditPlus

  • 汇编高亮

    添加asm.stx文件

  • 添加用户工具

ctrl+2 直接调出cmd窗口 执行Makefile 即可

2、开发环境选择使用

1、编辑 Helloworld

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff Hello.asm
; Link /subsystem:windows Hello.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        .386
        .model flat,stdcall
        option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include     windows.inc
include     user32.inc
includelib  user32.lib
include     kernel32.inc
includelib  kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        .data
szCaption   db  'A MessageBox !',0
szText      db  'Hello, World !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        .code
start:
        invoke  MessageBox,NULL,offset szText,offset szCaption,MB_OK
        invoke  ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        end start

解析:

    .386
        .model flat,stdcall
        option casemap:none
  • 这些指令定义了程序使用的指令集、工作模式和格式
  • Win32工作在80386及其以上的处理器中
  • .model 内存模式[,语言模式][,其他模式]
  • option casemap:none定义程序大小写敏感

2、命令行编译

; ml /c /coff Hello.asm
; Link /subsystem:windows Hello.obj

3、使用Makefile编译

使用 nmake 或下列命令进行编译和链接:

NAME = Hello
OBJS = $(NAME).obj

LINK_FLAG = /subsystem:windows #宏定义
ML_FLAG = /c /coff

$(NAME).exe: $(OBJS)#显示规则
    Link $(LINK_FLAG) $(OBJS)
.asm.obj:  #隐式规则
    ml $(ML_FLAG) $<

clean:
    del /f /q *.obj *.exe
  • $@ 全路径的目标文件
  • $* 除去扩展名的全路径目标文件
  • $? 所有原文件名
  • $< 源文件名(只能在隐式规则中使用)
  • 在EditPlus Ctrl+2 调出cmd 执行nmake 即可

4、关于资源编译器

rc文件的编写是PE程序开发的标准步骤,没有汇编格式的资源文件和 C语言格式的资源文件之分,
所以汇编中的资源编译器实际上就是C中的资源编译器
为了开发方便,实际上可用修改VS生成的.rc文件 再生成 .res  


利用VS 生成资源文件.rc  ,删掉其中 头文件  并讲 resource.h中 定义的资源copy到 rc 即可  直接编译此rc 生成 res

3、逆向常用工具

  • OllyDBG

  • IDA

  • Win32DSM

  • WinHex & FlexHex

第三章 经典反汇编工具

列举常用的黑客工具,自己学习体验后再做详细补充
  • 调试器

    SoftICE 依然过期,目前只能在虚拟机中装XP玩了

    WinDbg 取代了SoftICE

    OllyDbg 调试应用程序

    Syser 中国人研发的内核级调试器 目前仍死实验性的

    初学就以 OllyDbg入门,715-356-9946

  • 反汇编器

    IDA Pro 是唯一适合专业人员使用的反汇编器

    W32Dsm 轻量级静态反汇编器

    Hack Disassember Engine 以源代码形式发布,可用来完成黑客工具开发,比如捕获函数,执行自动解包,生成多态代码等

  • 反编译器

    所谓反编译是指从二进制程序中提取源代码的过程,达到完全反编译是不可能的,编译毕竟是个不可逆转的单向过程,必然有数据损耗,但是反编译仍有自己的价值。

    Delphi 可以用DeDE反编译器

    VB 可用 VB Decompiler、 VB RezQ 、Spices.Decompiler等工具

    当然最受关注的是专门为安装工具程序开发的反编译器

    最流行的 安装工具 Install Shield 有InstallShield Unpacker、 Windows InstallShield Decompiler、InstallShield Decompiler 等

    Java 和 .NET 和平台相关 IDA Pro 很适合处理他们,也可使用专门工具

  • 十六进制编辑器

    HIEW WinHex HexWorkshop HT Editor 他们各有优点,使用时体验吧

  • 解包器

    PEID 能侦探 是什么打包器,而 要想真正一劳永逸 只有手动解包

  • 转储器

    保存正在运行的程序的转储内容是解包所采用的通用方法
    Load PE

    保存转储内容后,至少要恢复导入表,有时还需要恢复重定位元素和资分区,

    Import PE Constructor 、ReloX、Resource Binder 等可使用

  • 资源编辑器

    Restorator Resource Editor 黑客专用资源编辑器

    XN Resource Editor 提供源代码Delphi 可参考

  • Spy

    窥测Windows 消息与API

  • 监视器

    File Monitor

    Registry Monitor

  • 修正器

第二章 熟悉Win32汇编的程序结构

目标

  • 熟悉汇编语言基本知识

参考

  • 《 Windows 环境下32位汇编语言程序设计》 第三章

目录


一、汇编源程序结构

1、模式定义

        .386
        .model flat,stdcall
        option casemap:none

2、段的定义

        <include 语句>
        .stack [堆栈段大小]  
        .data
        <初始化过的字符串 变量定义>
        .data?
        <初始化过的字符串 变量定义>
        .const
        <一些常量定义>
        .code
            <代码>
            <开始标记>
                <其他语句>
        end 开始标记

3、注释和换行

汇编中用 ”;

    call PrintChar ;这里是注释

换行符用\

    invoke MessageBox ,NULL,offset szText,offset szCaption,MB_OK

    可写为

    invoke Message,\
    NULL,\
    offset szText,\
    offset szCaption,\
    MB_OK

makefile中注释用 # 且换行符后面不能有其他任意字符

二、API调用

1、API

所有API定义参考 《Microsoft Win32 Programmer’s Reference》

此为help文件格式 win10等可能要解决hlp文件查看问题

2、Invoke 代替 call

原型

int MessageBox{
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption
UINT    uType
};

汇编格式

MessageBox Proto hWnd:dword,lpText:dword,lpCaption:dword,uType:dword

用invoke调用

invoke MessageBox ,NULL,offset szText,offset szCaption,MB_OK

代替call

push uType
push lpCaption
push lpText
push hWnd
call MessageBox

3、 返回值

汇编程序返回值只要一种类型 即:dword 永远放在eax中

或者eax 指向的一片内存区域

4、声明

函数名 proto [距离] [语言] [参数1]:参数类型,[参数2]:数据类型,......

proto 是函数声明 伪指令

距离可是FAR NEAR 等 win32 是平坦模式 可省略
语言类型 即 .model 那些类型

MessageBox Proto hWnd:dword,lpText:dword,lpCaption:dword,uType:dword

等价于

MessageBox Proto :dword,:dword,:dword,:dword

5、include 语句

所以API 必须先声明 比较麻烦 为了简化操作 可以采用各种语言通用的解决方法 把所以声明放在一个文件中 用到的时候用include语句包含进来

include user32.inc
include kernel32.inc

6、includelib

DOS汇编中 中断调用系统功能不必声明,处理器自己知道中断地址 但Win32汇编中使用API函数,程序必须知道API位于哪个DLL中 每个DLL中的定位信息又导入库lib文件来实现 为了告诉链接程序使用哪个导入库,使用语句:

includelib  库文件名

MessageBox和ExitProcess 分别在User32.dll 和 Kernel.dll中 源程序使用:

includelib  user32.lib
includelib  kernel.lib

7、API函数中的预定义(等值定义)

windows.inc

如果这个文件中没有,只能自己在C++的SDK中找到手动添加上去

三、标号、变量和数据结构

1、标号的定义

标号名:        目的指令        ;作用域为当前子程序
标号名::   目的指令        ;作用域为整个程序

2、@@

汇编中跳转指令太多故而标号太多,而标号其实并没用太多实际意义,所以可以使用@@替代标号, 用@F和@B来引用,@F表示本指令后面的第一个@@标号 @B表示本指令前的第一个@@标号

3、全局变量

字节          Byte    db
字           word    dw
双字          dword   dd
四字          qword   qd
有符号字节   sword   

只有定义全局变量时 才可使用缩写

.data
wHour       dw      ?
wMinute     dw      10
_hWnd       dd      ?
word_buffer dw      100 dup (1,2)
szBuffer    byte    1024 dup (?)
szText      db      'Hello,world!

byte类型变量定义中,可以使用引号定义字符串和数值定义的方法混用

szText      db      ‘Hello,world!’,0dh,0ah,'hello again',0dh,0ah,0

4、局部变量

    local   loc1[1024]:byte
    local   loc2
    local   loc3:WNDCLASS

5、数据结构

结构体定义

WNDCLASSA STRUCT
  style             DWORD      ?
  lpfnWndProc       DWORD      ?
  cbClsExtra        DWORD      ?
  cbWndExtra        DWORD      ?
  hInstance         DWORD      ?
  hIcon             DWORD      ?
  hCursor           DWORD      ?
  hbrBackground     DWORD      ?
  lpszMenuName      DWORD      ?
  lpszClassName     DWORD      ?
WNDCLASSA ENDS

.data?
stWndClass  WNDCLASS    <>

.data
szWndClass WNDCLASS     <1,1,1,1,1,1,1,1,1,1>

结构体引用

最直接方式

mov eax,stWndClass.lpfnWndProc

常规方式

mov esi,offset stWndClass
mov eax,[esi+WNDCLASS.lpfnWndProc]

伪指令

mov esi,offset stWndClass
assume esi:ptr WNDCLASS
mov eax,[esi].lpfnWndProc
....
assume esi:nothing

结构体嵌套

嵌套定义

NEW_WNDCALSS    struct
dwOption    dword   ?
oldWndClass WNDCLASS    <>
NEW_WNDCLASS    ends

嵌套引用

mov eax,[esi].oldWndClass.lpfnWndProc

6、变量的使用

  • 以不同类型访问变量

    即相当于高级语言的类型转换

    szBuffer db     1024 dup (?)
    

    直接使用 下面指令 编译器报错

    mov ax ,szBuffer
    

    MASM中,如果要用指定类型之外的长度访问变量,必须显示地指出要访问的长度

    类型 ptr  变量名
    

    类型可是byte,word,dword,fword,qword等

    mov ax,word ptr szBuffer
    mov eax,dword ptr szBuffer
    

    例子:

    .data
    
    bTest1  db  12h
    wTest2  dw  1234h
    dwTest3 dd  12345678h
    
    .code
    
    mov al,bTest1
    mov ax,word ptr bTest1
    mov eax,dword ptr bTest1
    

    问题:各寄存器中值为多少?

    解析:

    .data段中的变量 是按顺序从低地址忘高地址排列

    超过1个字节的数据 80386中数据排列顺序是地位数据在低地址

    所以答案为:12h 3412h 78123412h

    总结:

    汇编中用ptr强制覆盖变量长度时,实际上编译器并不会考虑定界的问题,只是使用当前地址操作而已

  • 扩展指令实现

    如果想实现类似C语言中强制转换, 把单字节扩展到双字节 高位保持为0 而不是越界存取到其他变量 可以使用80386提供的扩展指令实现

    movzx   ax,bTest1
    movzx   eax,bTest1
    movzx   eax,cl
    movzx   eax,ax
    
  • 变量的尺寸和数量

    在源程序中用变量的尺寸和数量的时候,可以用sizeof和lengthof伪指令实现

    sizeof      变量名、数据类型、数据结构名
    lengthof    变量名、数据类型、数据结构名
    

    sizeof 伪指令可以取得变量、数据类型或者数据结构以字节为单位的长度

    lengthof伪指令可以取得变量中数据的项数

  • 获取变量地址

    全局变量和局部变量获取地址的方式不同

    全局变量地址在编译时由编译器确定

    mov 寄存器,offset 变量名
    

    offset 是取变量地址的伪操作符

    而局部变量是ebp指针操作的

    lea 可以用来取指针的地址

    lea eax,[ebp-4]
    

    invoke伪指令的参数中用到一个局部变量地址可用addr

    addr  局部变量名和全局变量名
    

    lea后跟全局变量时,编译器自动安装offset使用 lea后跟局部变量时,编译器自动用lea指令先把地址取到eax中,然后用eax代替变量地址使用

    故而

    mov eax,addr 局部变量名  ;错误操作    
    

    lea无法带到mov语句中执行

    invoke Test,eax,addr szHello ;错误操作
    

    addr会用到eax,eax 被覆盖

四、、子程序

  • 子程序的定义

    子程序 proc [距离][语言类型][可视区域][USER寄存器列表][,      参数:类型]....[VARARG]
    
            local 局部变量表
    
            指令
    
    子程序名 endp
    
  • 参数传递和堆栈平衡

                入栈顺序   清除堆栈者    允许使用VARARG
    
    C           右           调用者         是
    SysCall     右           子程序         是
    StdCall     右           子程序         是
    BASIC       左           子程序         否
    FORTARN     左           子程序         否
    PASCAL      左           子程序         否
    

    C是调用者在call指令完成后,自行改变esp 保持堆栈平衡 win32约定的类型是StdCall 所以调用子程序或者API后不必自己来平衡堆栈

五、高级语法

    WMSAM中引入一系列伪指令,涉及条件测试、分支和循环实现了和高级语言一样的结构

六、代码风格