어셈블리어 튜토리얼 (14) 64비트

4. 64비트

4.1. 32비트와 달라진점

레지스터들이 64bit(8byte)이다. 기존 32비트 레지스터와 구분하기위해 명칭이 달라졌다. e대신 r로 시작한다. rax, rbx, rcx, rdx, rbp, rsp

기존의 e로 시작하는(eax등)의 레지스터도 그대로 사용한다. 예를들어 rax하위 32비트eax이다.

r8, r9, r10, r11, r12, r13, r14, r15 8개의 레지스터가 추가되었다. 하위 32비트는 끝에 d를 붙인다. r8의 하위 32비트 레지스터는 r8d 이다.

이것에 맞춰 64비트 데이터형인 qword가 생겼다.

메모리주소가 32bit에서 64bit가 되었다. 기존 32비트에서는 주소도 dword형, int도 dword형 대부분 dword로 사용했었는데 이제는 qword과 구분해줘야하는 불편함이 생겼다. (여전히 int형을 자주쓰므로)

함수 호출방식이 달라졌다. 32비트 함수콜방식은 여러가지가 있었다. stdcall cdecl fastcall등으로 난립해서 사용되었다. 이것이 fastcall로 통일 되었다.

fastcall은 32비트에서는 거의사용하지 않았던 방식이다. (명시적으로 선언하는경우말곤 본적이 없는듯하다) 파라메터 전달을 스택이 아닌 레지스터로 전달해 빠른처리에 유용해서 fastcall이라는거같다.

모든 파라메터를 레지스터로 전달하진 않는다. 파라메터 4개까지는 레지스터를 이용하고 5번째 파라메터부터 스택을 이용한다. 그리고 cdecl과 비슷하게 파라메터로 사용한 스택을 호출한쪽에서 정리한다.

예를 들어 파라메터 5개를 받는 myfunc 함수가 있다고하면

sub rsp, 40 ; 8byte * 5
mov qword ptr [rsp], 5
push qword ptr [rsp]
mov r9, 4
mov r8, 3
mov rdx, 2
mov rcx, 1
call myfunc
add rsp, 40 ; 호출한쪽에서 정리한다.

파라메터가 5개이므로 스택에서 40byte를 확보한다. 이제는 단위가 8byte기준이다.(64bit) 사실 여기서 4개에 해당하는 영역은 사용하지 않는다. 그래도 항상 확보해두게되는데 이 영역을 shadow space라고 한다. 별로 큰의미는 없는거같다. (그냥 함수내에서 활용하는 용도?)

이렇다보니 이제는 32비트의 stdcall의 함수처럼 시작부분이

mov edi,edi
push ebp
mov ebp,esp

이렇게 정형화되어 시작하지 않는다. 이런 구문없이 바로 시작하기때문에 api hooking하기 까다로워졌다.

파라메터 4개는 순서대로 rcx, rdx, r8, r9가 담당한다. 반환값 rax와 더불어 경우에따라 활용할수있는 레지스터가 늘었다.

마지막으로 add rsp로 호출한쪽에서 스택을 정리한다. 예전의 stdcall에서는 myfunc 함수 끝에서 ret 40 이렇게 파라메터가 정리되었다.

설치

앞서말했지만 masm32는 64bit를 지원하지 않는다. MS에서 제공하는 ml64 를 이용하면 코딩은 가능하긴한데 masm32에서 쓰던 많은 지시어들을 사용할 수 없다.

이참에 nasm를 써볼까하는 찰나에 masm32포럼에서 호환이 되는 컴파일러를 찾았다.

JWasm이라는 컴파일러이다. masm32 문법을 거의 대부분 호환한다. (개인이 만든거 같은데 대단하다. ㄷㄷ) 링커는 선택이 다양하다. MS에서 제공하는걸 써도 되고 마찬가지로 JWasm를 만든곳에서 제공하는 JWlink를 써도 된다. 여기서는 MS에서 제공하는 link를 사용하겠다.

4.2. 설치

  1. Platform SDK를 설치한다.

    컴파일러와 링커가 있고(ml64,link) 64bit용 windows api 라이브러리(lib파일)가 있다.

    https://www.microsoft.com/en-us/download/details.aspx?id=6510

    설치하면 아래의 경로에 64bit용 도구가 있다.

    C:\Program Files\Microsoft Platform SDK\Bin\win64\x86\AMD64
    
  2. JWasm 를 설치한다.

    위에서 설명한 64bit용 masm32라고 할 수 있는 컴파일러이다.

    홈페이지 http://japheth.de/ <- 현재 운영되지 않는 상태이다.

    archive.org 를 통해 들어갈수 있다. http://web.archive.org/web/20140909003608/http://japheth.de/

    다운로드 링크 http://web.archive.org/web/20140818035340/http://www.japheth.de/Download/JWasm/JWasm211bw.zip

    github에서 소스도 공개하고 있다.
    https://github.com/JWasm/JWasm

  3. 64bit용 include를 받는다.

    기존의 windows.inc를 64bit으로 컨버팅한 include모임이다. https://sourceforge.net/projects/wininc/files/WinInc208.zip/download

  4. pellesc의 link를 이용해도 된다.

    http://www.smorgasbordet.com/pellesc

windows 프로그램

win_64.asm

32비트 예제와 크게 다른점은 없다.

option casemap:none

include D:\WinInc208\Include\windows.inc

includelib kernel32.lib
includelib user32.lib
includelib gdi32.lib

.data
szClass         byte "hello",0
szCaption       byte "hello world",0

.data?

.code
start:
invoke GetModuleHandle, NULL
mov rcx, rax
xor rdx, rdx
invoke GetCommandLine
mov r8, rax
xor r9, r9
call WinMain
invoke ExitProcess, 0

WinMain proc hInstance:HINSTANCE, hPrevInst:HINSTANCE, cmdLine:LPSTR, cmdShow:UINT
    local wc:WNDCLASSEX
    local msg:MSG
    local hwnd:HWND

    mov hInstance, rcx

    mov wc.cbSize, sizeof WNDCLASSEX
    mov wc.style, CS_HREDRAW or CS_VREDRAW
    lea rax, WndProc
    mov wc.lpfnWndProc, rax
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    invoke LoadIcon, NULL, IDI_APPLICATION
    mov wc.hIcon, rax
    invoke LoadCursor, NULL, IDC_ARROW
    mov wc.hCursor, rax
    mov wc.hbrBackground, COLOR_WINDOW+1
    mov wc.lpszMenuName, NULL
    lea rax, szClass
    mov wc.lpszClassName, rax
    mov wc.hIconSm, NULL

    invoke RegisterClassEx, addr wc

    invoke CreateWindowEx, NULL, \
        addr szClass, \
        addr szCaption, \
        WS_OVERLAPPEDWINDOW, \
        0, 0, 320, 240, \
        NULL, \
        NULL, \
        hInstance, \
        NULL

    mov hwnd, rax
    mov rcx, hwnd
    mov edx, SW_SHOWNORMAL
    call ShowWindow

    mov rcx, hwnd
    call UpdateWindow

    .while TRUE
        invoke GetMessage, addr msg, 0, 0, 0
        .break .if eax==0
        invoke DispatchMessage, addr msg
    .endw

    mov rax, msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    local hdc:HDC
    local ps:PAINTSTRUCT

    mov hWnd, rcx
    mov uMsg, edx
    mov wParam, r8
    mov lParam, r9

    .if uMsg==WM_CREATE
        invoke ShowWindow, hWnd, SW_NORMAL
        invoke UpdateWindow, hWnd
    .elseif uMsg==WM_DESTROY
        invoke PostQuitMessage, 0
    .else
        invoke DefWindowProc, rcx, edx, r8, r9
        ret
    .endif

    xor rax,rax
    ret
WndProc endp

end start

대부분이 기존의 32bit 소스와 동일하다.

include파일을 d:\WinInc208 에 넣어두었다.

곳곳에 64bit 레지스터와 qword형이 보인다.

WndProc함수를 보면 시작부분에서 파라메터에 값을 복사한다.

mov hWnd, rcx
mov uMsg, edx
mov wParam, r8
mov lParam, r9

위에서 잠깐 언급했던 shadow space이다. 파라메터 4개까지는 레지스터로 넘어오기때문에 이렇게 직접 스택에 복사해준다.

컴파일러가 자동으로 해주지 않는다. 자동으로해주면 fastcall을 쓰는 장점이 사라질것이다.

왠지 옵션으로 제공해줄 것 같기도 한데 찾아보진 않았다.

빌드

JWasm 는 d:\JWasm211bw 에 압축을 풀었다. link는 ms에서 제공하는것을 이용한다.

make_helloworld_64.bat

D:\JWasm211bw\jwasm -c -win64 -Zp8 helloworld_64.asm
"C:\Program Files\Microsoft Platform SDK\Bin\win64\x86\AMD64\link" /libpath:"C:\Program Files\Microsoft Platform SDK\Lib\amd64" /subsystem:console helloworld_64
@pause

목차 이전글 어셈블리어 튜토리얼 (13) post sniffing