【原创】Windows X64汇编入门(2)

我不是女神ヾ 2022-09-18 05:59 303阅读 0赞

标 题: 【原创】Windows X64汇编入门(2)
作 者: tankaiha
时 间: 2007-05-07,22:51:59
链 接: http://bbs.pediy.com/showthread.php?t=44078

tankaiha

五一长假就要结束了,总算有时间好好睡了几个懒觉。今天醒来后想到的第一件事就是,该写第二篇了。
64位技术现在还不成熟,没有好调试器,但是我们搞技术的总是对新东西充满了好奇和热情。这个理由就足够我们现在开始学习64位汇编了!OK,Let’s go on。

  1. 再说Calling convention
    关于API的调用方式,在入门(1)中说了一些,不过感觉有必要再讲两点。一是在调用API时椎栈的框架,也就是Stack Frame,二是利用反汇编64位C/C++程序来研究calling convention。
    先说Stack Frame。图1是一个通用的椎栈框架。
    名称:  1.jpg查看次数: 905文件大小:  8.9 KB

    在一个使用STDCALL的32位程序中,stack frame的四项工作:
    (1) 传入参数的调用;
    (2) 在返回caller时,callee要负责平衡椎栈;
    (3) 给局部变量提供空间;
    (4) 保证ebx、esi、edi和ebp四个寄存器的值不变(这种寄存器被称为non-volatile)。
    在64位环境中,少了一个平衡椎栈的任务,因为平衡椎栈的工作由caller负责了,因此callee的stack frame只剩下三项工作:
    (1) 将寄存器传入的参数和其它超过4个以上的参数在椎栈上保存(入栈);
    (2) 给局部变量提供空间;
    (3) 保证non-volatile寄存器的值不变,包括ebp、ebx、rdi、rsi、r12到r15,xmm6到xmm15。

    所以,在一个函数的开始往往有如下代码:

    MOV [RSP+8h],RCX
    MOV [RSP+10h],RDX
    MOV [RSP+18h],R8
    MOV [RSP+20h],R9
    PUSH RBP
    MOV RBP,RSP

    而在返回时会有如下代码:
    LEA RSP,[RBP]
    POP RBP
    RET

    图2摘自GoASM的帮助文档,上文描述的情况在图中一目了然。
    名称:  2.jpg查看次数: 929文件大小:  38.6 KB

    如果能在VC中编译64位C/C++程序,再用IDA反汇编,不是挺好的吗?正确,这正是我们玩儿逆向工程的人喜欢的方法。Visual Studio 2005的64位开发环境设置网上有,这里不多说了。以一个C/C++的代码为例:

代码:

  1. // Message handler for about box.
  2. INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
  3. {
  4. UNREFERENCED_PARAMETER(lParam);
  5. switch (message)
  6. {
  7. case WM_INITDIALOG:
  8. return (INT_PTR)TRUE;
  9. case WM_COMMAND:
  10. if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
  11. {
  12. EndDialog(hDlg, LOWORD(wParam));
  13. return (INT_PTR)TRUE;
  14. }
  15. break;
  16. }
  17. return (INT_PTR)FALSE;
  18. }

这段代码是一个地球人都知道的窗口消息处理代码,在编译为64位程序后,用ida64看一下它的反汇编。这样,熟悉而又有点陌生的64位汇编代码就出来了,包括消息的判断,EndDialog的调用等,确实很方便。
点击图片以查看大图图片名称:    4.jpg查看次数:    1000文件大小:    81.7 KB文件 ID :    5622

  1. 第二个汇编例子:SMC
    在入门(1)中我们写了第一个64位的汇编程序,这里我们开始写第二个。当然,代码本身还是有点意思的,这就是Self Modify Code。让我们试一试SMC在64位下进行的如何?这还牵涉到vista的特性。代码来自修改过的参考资料《About RIP relative addressing》。

DATA SECTION
testzero db ‘eax值为0’, 0
testnonzero db ‘eax值不为0!’, 0
testtitle db ‘测试or eax,eax指令’, 0
oldprotect dd ?

CODE SECTION
Start:
;改变当前内存页的保护为可写
sub rsp,28h
lea r9, oldprotect ; R9 = lpflOldProtect
mov r8d, 40h ; R8D = flNewProtect
mov rdx, 1 ; RDX = dwSize
lea rcx, modifyhere ; RCX = lpAddress
call VirtualProtect
add rsp,28h

lea rax,modifyhere
inc B[rax]

xor eax,eax
or eax,eax
;如果eax为0,则or指令会使jz跳转

lea rax, testzero
modifyhere:
jz >.skip ;这个是GoASM的语法,>号表示后面代码中的label
lea rax, testnonzero

.skip ;GoASM中的label这样定义

; 显示结果
sub rsp,28h
mov r9d, 0 ; R9D = UINT uType
lea r8, testtitle ; R8 = LPCTSTR lpCaption
mov rdx, rax ; RDX = LPCSTR lpText
mov rcx, 0 ; RCX = HWND hWnd
call MessageBoxA
mov ecx, eax ; ECX = UINT uExitCode
call ExitProcess
add rsp,28h

ret

代码的执行流程如下:将eax赋0,然后进行or eax,eax,如果不修改代码,则jz处会跳转,结果会显示“eax值为0”。我们的任务就是把jz改为jnz。jz的十六进制编码为74,jnz为75。
编译一下:
GoASM /x64 “2.1.asm”
GoLink “2.1.obj” kernel32.dll user32.dll
因为我们加入了下面两句代码:
lea rax,modifyhere
inc B[rax]
所以jz为in为jnz了。结果显示如下图所示。注意,GoASM中byte ptr简写为B。当然,你可以把上面两句指令删除,那出来的就完全是另一个结果了。
名称:  5.jpg查看次数: 902文件大小:  5.8 KB

  1. 资源文件
    本文的最后一节来讲下带资源的程序编译。由于GoASM有自己的编译器GoRC,而visual studio中是rc,因此我们将分别用两种语法编写,看一下两个编译器中的相同与不同。
    先按下面的代码建立MainDlg.rc,这个rc文件是两个例子通用的,代码来自RadASM的32位默认模板代码,其实就是一个对话框,没有添加任何控件:

代码:

  1. #define IDD_DLG1 1000
  2. IDD_DLG1 DIALOGEX 6,6,194,106
  3. CAPTION "我的第一个DialogBox"
  4. FONT 8,"MS Sans Serif"
  5. STYLE 0x10CF0000
  6. EXSTYLE 0x00000000
  7. BEGIN
  8. END

来看一下GoASM语法的文件,其中用了很多GoASM的宏语法,不熟悉的可以看下帮助文件。我们把它保存为2.2.asm。
;##################################################################
; DIALOGAPP
;##################################################################

;暂时没有完整的include文件,我们把要用的自己添加进来
#Define WM_INITDIALOG 00110H
#Define WM_DESTROY 00002H
#Define WM_COMMAND 00111H
#Define WM_CLOSE 00010H

#IFNDEF FALSE
#Define FALSE 0
#ENDIF

#IFNDEF TRUE
#Define TRUE 1
#ENDIF

CONST SECTION
IDD_DLG1 equ 1000

DATA SECTION
hInstance DQ ?

CODE SECTION

Start:
;invoke是GoASM调用API的宏,用不着我们自己进行stack frame了
invoke GetModuleHandleA, 0
mov [hInstance],rax
invoke InitCommonControls
invoke DialogBoxParamA,[hInstance],IDD_DLG1,0,ADDR DlgProc,0
invoke ExitProcess,0

;FRAME是GoASM的宏
DlgProc FRAME hwnd,uMsg,wParam,lParam
mov eax,[uMsg]
cmp eax,WM_INITDIALOG
jne >.WMCOMMAND

jmp >.EXIT

.WMCOMMAND
cmp eax,WM_COMMAND
jne >.WMCLOSE

jmp >.EXIT

.WMCLOSE
cmp eax,WM_CLOSE
jne >.DEFPROC
INVOKE EndDialog,[hwnd],0

.DEFPROC
mov eax,FALSE
ret

.EXIT

mov eax, TRUE
ret
ENDF

编译时有个很奇怪的问题,就是要把资源文件编译成.obj格式才能顺利链接。命令行如下:
GoRC /machine x64 /o maindlg.rc
GoASM /x64 2.2.asm
GoLink “2.2.obj” maindlg.obj kernel32.dll user32.dll comctl32.dll
生成了2.2.exe后,运行,如下图所示:

名称:  6.jpg查看次数: 899文件大小:  6.6 KB

下面,看一下ml64的编译过程。rc文件不变,把下面的代码保存为2.3.asm。
;##################################################################
; DIALOGAPP
;##################################################################

extrn GetModuleHandleA : proc
extrn MessageBoxA : proc
extrn InitCommonControls : proc
extrn DialogBoxParamA : proc
extrn DestroyWindow : proc
extrn ExitProcess : proc
extrn EndDialog : proc

.const
WM_INITDIALOG equ 00110H
WM_DESTROY equ 00002H
WM_COMMAND equ 00111H
WM_CLOSE equ 00010H
FALSE equ 0
TRUE equ 1

IDD_DLG1 equ 1000

.data?
hInstance qword ?

.code

Main proc
;invode是GoASM调用API的宏,用不着我们自己进行stack frame了
sub rsp,30h
xor rcx,rcx
call GetModuleHandleA
mov [hInstance],rax
call InitCommonControls

mov rcx,[hInstance]
mov rdx,IDD_DLG1
xor r8,r8
lea r9,DlgProc
push 0
call DialogBoxParamA

xor rcx,rcx
call ExitProcess
add rsp,30h
ret
Main endp

;DlgProc FRAME hwnd,uMsg,wParam,lParam
DlgProc:
mov [rsp+8],rcx
mov [rsp+10h],rdx
mov [rsp+18h],r8
mov [rsp+20h],r9
push rbp
mov rbp,rsp

mov eax,edx
cmp eax,WM_INITDIALOG
jne WMCOMMAND

jmp EXIT

WMCOMMAND:
cmp eax,WM_COMMAND
jne WMCLOSE

jmp EXIT

WMCLOSE:
cmp eax,WM_CLOSE
jne DEFPROC

sub rsp,18h
xor rdx,rdx
;注意,这里第一个参数rcx未变。按理应该是由rbp定位
mov rcx,[rbp+10h]
call EndDialog
add rsp,18h

DEFPROC:
pop rbp
mov eax,FALSE
ret

EXIT:
pop rbp
mov eax, TRUE
ret
end

编译命令行为:
rc maindlg.rc
ml64 2.3.asm /link /subsystem:windows /entry:Main kernel32.lib user32.lib comctl32.lib maindlg.res
如果你编译正确了,应该和2.2.exe的运行结果一样。
和GoASM的宏比起来,ml64的语法显得更低级,也更基础。不过要注意的是,在2.3.asm中,很多语法我并没有写得很规范,要想知道最规范的代码,便是逆向高级语言或GoASM 生成的exe文件。

OK,第二篇就到这里了。五一长假将在今晚结束,明天上班喽。

发表评论

表情:
评论列表 (有 0 条评论,303人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Windows X64汇编入门(2)

    tankaiha     五一长假就要结束了,总算有时间好好睡了几个懒觉。今天醒来后想到的第一件事就是,该写第二篇了。     64位技术现在还不成熟,没有好调试

    相关 Windows X64汇编入门

    Windows X64汇编入门(1) tankaiha     最近断断续续接触了些64位汇编的知识,这里小结一下,一是阶段学习的回顾,二是希望对64位汇编新手有

    相关 Windows数据类型(FantasiaX)

    由微软Windows操作系统所支持的各种数据类型是用来定义函数的返回值、函数和消息的参数以及结构体成员(因为Win32程序是用C语言来编写,所以没有“类”这个概念)的。这些数据