您当前的位置: 首页 > 

Jave.Lin

暂无认证

  • 2浏览

    0关注

    704博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Annotated x86 Disassembly - 带注解的 x86 反汇编

Jave.Lin 发布时间:2020-05-26 13:58:58 ,浏览量:2

文章目录
  • In this article - 此文
  • Source Code - 源代码
  • Assembly Code - 编译代码
系列目录: Processor Architecture - 处理器体系架构 原文: Annotated x86 Disassembly

翻译前说明:黄色底的字是我自己添加的描述

还有翻译完此篇后,我决定不翻译系列目录的后续文章了,因为MS(Microsoft)原文的技术文档真的太烂的,看过很多的MS的文档,真心感觉他们认为反正用的人多,他们也会坚持用,我文档写得怎么样也无所谓了的心态来书写的,从翻译就可以看得出来,原文一点都不负责任的态度。所以不想继续翻译后文,我之所以坚持翻译完,就想让大家看看,这种搞技术的,写文档写成这样的,还能在MS上混着的,到底MS怎么了?肯定管理上也会有很大的问题。因为管理就是人的问题。

In this article - 此文

以下小节将带你过一篇反汇编的例子。

Source Code - 源代码

下面的代码是用于分析用的函数。

HRESULT CUserView::CloseView(void)
{
    if (m_fDestroyed) return S_OK;

    BOOL fViewObjectChanged = FALSE;
    ReleaseAndNull(&m_pdtgt);

    if (m_psv) {
        m_psb->EnableModelessSB(FALSE);
        if(m_pws) m_pws->ViewReleased();

        IShellView* psv;

        HWND hwndCapture = GetCapture();
        if (hwndCapture && hwndCapture == m_hwnd) {
            SendMessage(m_hwnd, WM_CANCELMODE, 0, 0);
        }

        m_fHandsOff = TRUE;
        m_fRecursing = TRUE;
        NotifyClients(m_psv, NOTIFY_CLOSING);
        m_fRecursing = FALSE;

        m_psv->UIActivate(SVUIA_DEACTIVATE);

        psv = m_psv;
        m_psv = NULL;

        ReleaseAndNull(&_pctView);

        if (m_pvo) {
            IAdviseSink *pSink;
            if (SUCCEEDED(m_pvo->GetAdvise(NULL, NULL, &pSink)) && pSink) {
                if (pSink == (IAdviseSink *)this)
                    m_pvo->SetAdvise(0, 0, NULL);
                pSink->Release();
            }

            fViewObjectChanged = TRUE;
            ReleaseAndNull(&m_pvo);
        }

        if (psv) {
            psv->SaveViewState();
            psv->DestroyViewWindow();
            psv->Release();
        }

        m_hwndView = NULL;
        m_fHandsOff = FALSE;

        if (m_pcache) {
            GlobalFree(m_pcache);
            m_pcache = NULL;
        }

        m_psb->EnableModelessSB(TRUE);

        CancelPendingActions();
    }

    ReleaseAndNull(&_psf);

    if (fViewObjectChanged)
        NotifyViewClients(DVASPECT_CONTENT, -1);

    if (m_pszTitle) {
        LocalFree(m_pszTitle);
        m_pszTitle = NULL;
    }

    SetRect(&m_rcBounds, 0, 0, 0, 0);
    return S_OK;
}
Assembly Code - 编译代码

这小节包含了注解的反汇编例子。 (从上面的文章写法,可以看得出,原文作者书写教学水平不行,它就不会将反汇编代码也罗列一下吗?总览后,再逐条讲解才是最好的。这样应付式的教学方式是任何教学领域的阻碍知识传播的最大原因。)

下面的函数使用了 ebp 寄存器作为帧指针的头部,如下:

HRESULT CUserView::CloseView(void)
SAMPLE!CUserView__CloseView:
71517134 55               push    ebp
71517135 8bec             mov     ebp,esp

这样设置帧后,函数可以通过增加 ebp 的方式来偏移内存地址而访问参数,而减少 ebp 偏移可以访问局部变量。

这个方法(CloseView)是在私有的COM接口上的,所以调用约定是 __stdcall。意味着参数是从右向左压栈的(在这个例子中没有参数),“this”指针也会压栈,然后才是函数的调用。因此,在进入函数之前,栈看起来是这样的:

[esp+0] = return address
[esp+4] = this

在前面的两个指令之前,参数是可访问的:(应该说是,在前面两个指令前,还有上一帧的 ebp 压栈备份)

[ebp+0] = previous ebp pushed on stack
[ebp+4] = return address
[ebp+8] = this

一个函数使用 ebp 作为帧指针,第一个入栈的参数在地址偏移 [ebp+8] 可以访问;后续的参数在此连续更高的DWORD位宽偏移来访问。

71517137 51               push    ecx
71517138 51               push    ecx

这个函数仅需要两个局部的帧变量,因此需要 sub esp, 8 指令。(国外文章的表达能力很差,需要各位读者有比较高的阅读能力才能看懂,这里应该说,因为该栈帧有两个局部变量,所以需要提前划分该这个栈帧空间,就是ebp到esp夹起来的地址空间,所以需要提前偏移esp,让后续其他的函数栈帧的空间相互独立。)压栈后的数值都在 [ebp-4] 与 [ebp-8] 的栈上。

一个函数使用 ebp 作为帧指针,栈局部变量是可以从 ebp 寄存器值加上负方向的偏移(就是ebp减少对应的DWORD 地址宽度,可以访问其他本地变量)来访问的。

71517139 56               push    esi

现在编译器保存寄存器,是为了需要在函数之间调用是恢复用的。实际上保存了bit位与块,第一行实际插入的代码。(国外应该是小学的水平的表达能力,就如同他们的总统一样差的表达能力(特L普))

7151713a 8b7508           mov     esi,[ebp+0x8]     ; esi = this
7151713d 57               push    edi               ; save another registers

CloseView 是在 ViewState 类下的的方法,ViewState 对象是在栈帧偏移 12个byte的地址(上面不是ebp+0x8吗?为何这里说是12)。因此 this 是指针 ViewState class(实例对象)的。(原文这里后面还有一段废话就不写了)

    if (m_fDestroyed)
7151713e 33ff             xor     edi,edi           ; edi = 0

XOR异或一个寄存器本身就是标准的设为0的方式。

71517140 39beac000000     cmp     [esi+0xac],edi    ; this->m_fDestroyed == 0?
71517146 7407             jz      NotDestroyed (7151714f)  ; jump if equal

cmp 指令比较两个值(通过减法)。jz 指令检测结果是否为0,代表两个值相等。

cmp 比较两个值;然后 jz 指令根据比较结果跳转。

    return S_OK;
71517148 33c0             xor     eax,eax           ; eax = 0 = S_OK
7151714a e972010000       jmp     ReturnNoEBX (715172c1) ; return, do not pop EBX

编译器会延迟的保存EBX寄存器,直到函数过后才保存,因此如果程序在此次test测试比较结果而提前的跳出函数,那么将不会恢复EBX寄存器。

    BOOL fViewObjectChanged = FALSE;
    ReleaseAndNull(&m_pdtgt);

这两行代码的执行是交错的,所以要注意。 lea 指令计算出地址偏移然后储存在目标寄存器上。 实际上,内存地址是不能引用的。

lea 指令取到变量的地址。

71517155 53               push    ebx

你应该在EBX寄存器损坏前保存它。(what the f**k,这个表达能力也是谁了。应该说,在函数出栈前先备份ebx,出栈是恢复用,不就得了吗?为何你要表达得那么的作?)

71517156 8b1d10195071     mov ebx,[_imp__ReleaseAndNull]

因为后续将频繁的调用 ReleaseAndNull,所以将它缓存在 EBX 是个好主意。

7151715c 50               push    eax               ; parameter to ReleaseAndNull
7151715d 897dfc           mov     [ebp-0x4],edi     ; fViewObjectChanged = FALSE
71517160 ffd3             call    ebx               ; call ReleaseAndNull
    if (m_psv) {
71517162 397e74           cmp     [esi+0x74],edi    ; this->m_psv == 0?
71517165 0f8411010000     je      No_Psv (7151727c) ; jump if zero

记得在函数之间调用时恢复EDI寄存器(因此抵用 ReleaseAndNull 不会改变它),要将EDI寄存器设为零。因此让它保持为0,可用于快速的检测为零。

        m_psb->EnableModelessSB(FALSE);
7151716b 8b4638           mov     eax,[esi+0x38]    ; eax = this->m_psb
7151716e 57               push    edi               ; FALSE
7151716f 50               push    eax               ; "this" for callee
71517170 8b08             mov     ecx,[eax]         ; ecx = m_psb->lpVtbl
71517172 ff5124           call    [ecx+0x24]        ; __stdcall EnableModelessSB

上面的模式是COM方法调用的特性。

COM方法非常流行,学习如何组织他们是对的。(这里后面有一句废话,不写)

        if(m_pws) m_pws->ViewReleased();
71517175 8b8614010000     mov     eax,[esi+0x114]   ; eax = this->m_pws
7151717b 3bc7             cmp     eax,edi           ; eax == 0?
7151717d 7406             jz      NoWS (71517185) ; if so, then jump
7151717f 8b08             mov     ecx,[eax]         ; ecx = m_pws->lpVtbl
71517181 50               push    eax               ; "this" for callee
71517182 ff510c           call    [ecx+0xc]         ; __stdcall ViewReleased
NoWS:
        HWND hwndCapture = GetCapture();
71517185 ff15e01a5071    call [_imp__GetCapture]    ; call GetCapture

间接调用全局的东西,是如果导入与实现Microsoft Win32 程序(的关键)。装载器修复了目标(目标文件)的全局对象的精准的地址。(这里也有一句废话,不写)。查看调用导入的目标对象的函数。你通常有导入的函数的名字,你可以在你的源码任何地方这么使用(我又不小心翻译多一句废话)。

        if (hwndCapture && hwndCapture == m_hwnd) {
            SendMessage(m_hwnd, WM_CANCELMODE, 0, 0);
        }
7151718b 3bc7             cmp     eax,edi           ; hwndCapture == 0?
7151718d 7412             jz      No_Capture (715171a1) ; jump if zero

函数返回值放在 EAX 寄存器。

7151718f 8b4e44           mov     ecx,[esi+0x44]    ; ecx = this->m_hwnd
71517192 3bc1             cmp     eax,ecx           ; hwndCapture = ecx?
71517194 750b             jnz     No_Capture (715171a1) ; jump if not

71517196 57               push    edi               ; 0
71517197 57               push    edi               ; 0
71517198 6a1f             push    0x1f              ; WM_CANCELMODE
7151719a 51               push    ecx               ; hwndCapture
7151719b ff1518195071     call    [_imp__SendMessageW] ; SendMessage
No_Capture:
        m_fHandsOff = TRUE;
        m_fRecursing = TRUE;
715171a1 66818e0c0100000180 or    word ptr [esi+0x10c],0x8001 ; set both flags at once

        NotifyClients(m_psv, NOTIFY_CLOSING);
715171aa 8b4e20           mov     ecx,[esi+0x20]    ; ecx = (CNotifySource*)this.vtbl
715171ad 6a04             push    0x4               ; NOTIFY_CLOSING
715171af 8d4620           lea     eax,[esi+0x20]    ; eax = (CNotifySource*)this
715171b2 ff7674           push    [esi+0x74]        ; m_psv
715171b5 50               push    eax               ; "this" for callee
715171b6 ff510c           call    [ecx+0xc]         ; __stdcall NotifyClients

注意,你如何在不同基础类方法调用来调整你的 “this” 指针。

        m_fRecursing = FALSE;
715171b9 80a60d0100007f   and     byte ptr [esi+0x10d],0x7f
        m_psv->UIActivate(SVUIA_DEACTIVATE);
715171c0 8b4674           mov     eax,[esi+0x74]    ; eax = m_psv
715171c3 57               push    edi               ; SVUIA_DEACTIVATE = 0
715171c4 50               push    eax               ; "this" for callee
715171c5 8b08             mov     ecx,[eax]         ; ecx = vtbl
715171c7 ff511c           call    [ecx+0x1c]        ; __stdcall UIActivate
        psv = m_psv;
        m_psv = NULL;
715171ca 8b4674           mov     eax,[esi+0x74]    ; eax = m_psv
715171cd 897e74           mov     [esi+0x74],edi    ; m_psv = NULL
715171d0 8945f8           mov     [ebp-0x8],eax     ; psv = eax

第一个局部变量是 psv。

ReleaseAndNull(&_pctView);
715171d3 8d466c           lea     eax,[esi+0x6c]    ; eax = &_pctView
715171d6 50               push    eax               ; parameter
715171d7 ffd3             call    ebx               ; call ReleaseAndNull
        if (m_pvo) {
715171d9 8b86a8000000     mov     eax,[esi+0xa8]    ; eax = m_pvo
715171df 8dbea8000000     lea     edi,[esi+0xa8]    ; edi = &m_pvo
715171e5 85c0             test    eax,eax           ; eax == 0?
715171e7 7448             jz      No_Pvo (71517231) ; jump if zero

注意编译器特别的准备了 m_pvo 成员的地址,因为在整个过程中将会频繁的使用到。因此,有它的地址将更方便更少量的代码实现。

if (SUCCEEDED(m_pvo->GetAdvise(NULL, NULL, &pSink)) && pSink) {
715171e9 8b08             mov     ecx,[eax]         ; ecx = m_pvo->lpVtbl
715171eb 8d5508           lea     edx,[ebp+0x8]     ; edx = &pSink
715171ee 52               push    edx               ; parameter
715171ef 6a00             push    0x0               ; NULL
715171f1 6a00             push    0x0               ; NULL
715171f3 50               push    eax               ; "this" for callee
715171f4 ff5120           call    [ecx+0x20]        ; __stdcall GetAdvise
715171f7 85c0             test    eax,eax           ; test bits of eax
715171f9 7c2c             jl      No_Advise (71517227) ; jump if less than zero
715171fb 33c9             xor     ecx,ecx           ; ecx = 0
715171fd 394d08           cmp     [ebp+0x8],ecx     ; _pSink == ecx?
71517200 7425             jz      No_Advise (71517227)

注意编译器推断出 “this” 参数是不需要的(因为它在之前早就储存在 ESI 寄存器中了)。因此,这内存可以重用,就想局部变量 pSink 一样。

如果函数使用 EBP 栈帧,那么进来的参数可使用 EBP + offset来获取,而局部变量则 EBP - offset 来获取。但是,在这个情况中,编译器为了免费重用内存的目的。

如果你细心留意,你会发现编译器已对这代码优化了一些。lea edi, [edi+0xa8] 将会在 push 0x0 指令后延时处理,用 push edi 来替代。这将会节省2 bytes。

                if (pSink == (IAdviseSink *)this)

These next several lines are to compensate for the fact that in C++, (IAdviseSink )NULL must still be NULL. So if your “this” is really "(ViewState)NULL", then the result of the cast should be NULL and not the distance between IAdviseSink and IBrowserService.(废话太多,部分我也看不懂。)

71517202 8d46ec           lea     eax,[esi-0x14]    ; eax = -(IAdviseSink*)this
71517205 8d5614           lea     edx,[esi+0x14]    ; edx = (IAdviseSink*)this
71517208 f7d8             neg     eax               ; eax = -eax (sets carry if != 0)
7151720a 1bc0             sbb     eax,eax           ; eax = eax - eax - carry
7151720c 23c2             and     eax,edx           ; eax = NULL or edx

即使崩腾有条件传送指令,基础i386体系架构的没有,因此编译器使用特殊的技术手段来模拟条件传送指令而不使用任何的jumps跳转指令。

一般的条件的方式等价于下列:

        neg     r
        sbb     r, r
        and     r, (val1 - val2)
        add     r, val2

如果 r 非零,neg r设置进位标记,因为 neg 通过0-为负数。并且,减法0-会生成一个借位(设置进位),如果减法为非零值。也会损坏 r 寄存器的值,但那时可接受的,因为你怎么都需要重写。

下一条指令,sbb r, r 指令是减去自己,结果总是零。然而,它也会减去进位(借位),所以最终设置 r 为0或是-1,分别是相对是否进位了的标记。

因此,如果 r 为0 , sbb r, r 设置 r 为0,或是如果原始 r 不为0,那么将设置为 -1。

第三条指令执行一个掩码。因为 r 寄存器位0或-1,“this” 让 r 为0,或是从 -1 调整为0 (val1 - val1),而,任意数与 -1 与操作都是它本身。

因此,and r, (val1 - val1) 设置 r 为0,如果 r 本身为0,或是如果 r 本身非0,那么设置为 (val1 - val2)。

最后,你将 val2 增加到 r,结果为 val2 或是 (val1 - val2) + val2 = val1。

因此经过一些列的指令后,如果 r 为0, 设置 r 为 val2,如果r 非0,那么设置为 val1。这个汇编等价于: r = r ? val1 : val2。

在这示例中,你可以看到 val2 = 0 和 *val1 = (IAdviseSink )this。(注意编译器忽略了最后的 add eax, 0 指令,因为他没有任何影响。)

7151720e 394508           cmp     [ebp+0x8],eax ; pSink == (IAdviseSink*)this?
71517211 750b             jnz     No_SetAdvise (7151721e) ; jump if not equal

在这小节前,你设置 EDI 为 m_pvo 的地址。你现在想要使用它。你也得将提前将 ECX 设置为0。

m_pvo->SetAdvise(0, 0, NULL);
71517213 8b07             mov     eax,[edi]         ; eax = m_pvo
71517215 51               push    ecx               ; NULL
71517216 51               push    ecx               ; 0
71517217 51               push    ecx               ; 0
71517218 8b10             mov     edx,[eax]         ; edx = m_pvo->lpVtbl
7151721a 50               push    eax               ; "this" for callee
7151721b ff521c           call    [edx+0x1c]        ; __stdcall SetAdvise
No_SetAdvise:
                pSink->Release();
7151721e 8b4508           mov     eax,[ebp+0x8]     ; eax = pSink
71517221 50               push    eax               ; "this" for callee
71517222 8b08             mov     ecx,[eax]         ; ecx = pSink->lpVtbl
71517224 ff5108           call    [ecx+0x8]         ; __stdcall Release
No_Advise:

所有这些 COM 方法调用你应该非常熟悉。

后面两个语句是交错的。别忘了 EBX 包含了 ReleaseAndNull 的地址。

fViewObjectChanged = TRUE;
            ReleaseAndNull(&m_pvo);
71517227 57               push    edi               ; &m_pvo
71517228 c745fc01000000   mov     dword ptr [ebp-0x4],0x1 ; fViewObjectChanged = TRUE
7151722f ffd3             call    ebx               ; call ReleaseAndNull
No_Pvo:
        if (psv) {
71517231 8b7df8           mov     edi,[ebp-0x8]     ; edi = psv
71517234 85ff             test    edi,edi           ; edi == 0?
71517236 7412             jz      No_Psv2 (7151724a) ; jump if zero
            psv->SaveViewState();
71517238 8b07             mov     eax,[edi]         ; eax = psv->lpVtbl
7151723a 57               push    edi               ; "this" for callee
7151723b ff5034           call    [eax+0x34]        ; __stdcall SaveViewState

这里有更多的 COM 方法调用。

psv->DestroyViewWindow();
7151723e 8b07             mov     eax,[edi]         ; eax = psv->lpVtbl
71517240 57               push    edi               ; "this" for callee
71517241 ff5028           call    [eax+0x28]        ; __stdcall DestroyViewWindow
            psv->Release();
71517244 8b07             mov     eax,[edi]         ; eax = psv->lpVtbl
71517246 57               push    edi               ; "this" for callee
71517247 ff5008           call    [eax+0x8]         ; __stdcall Release
No_Psv2:
        m_hwndView = NULL;
7151724a 83667c00         and     dword ptr [esi+0x7c],0x0 ; m_hwndView = 0

使用 0 与操作 (ANDing)内存定位如同设置它为0,因为任意数与0 与操作(AND)结果都是0。编译器之所以这样使用,即使会比较慢,但也会比 等价的 mov 指令更短。(这些代码优化的是大小,而不是速度。)

m_fHandsOff = FALSE;
7151724e 83a60c010000fe   and     dword ptr [esi+0x10c],0xfe
        if (m_pcache) {
71517255 8b4670           mov     eax,[esi+0x70]    ; eax = m_pcache
71517258 85c0             test    eax,eax           ; eax == 0?
7151725a 740b             jz      No_Cache (71517267) ; jump if zero
            GlobalFree(m_pcache);
7151725c 50               push    eax               ; m_pcache
7151725d ff15b4135071     call    [_imp__GlobalFree]    ; call GlobalFree
            m_pcache = NULL;
71517263 83667000         and     dword ptr [esi+0x70],0x0 ; m_pcache = 0
No_Cache:
        m_psb->EnableModelessSB(TRUE);
71517267 8b4638           mov     eax,[esi+0x38]    ; eax = this->m_psb
7151726a 6a01             push    0x1               ; TRUE
7151726c 50               push    eax               ; "this" for callee
7151726d 8b08             mov     ecx,[eax]         ; ecx = m_psb->lpVtbl
7151726f ff5124           call    [ecx+0x24]        ; __stdcall EnableModelessSB
        CancelPendingActions();

为了调用 CancelPendingActions ,你得将 (ViewState*)this 设置为 (CUserView*)this。也要注意 CancelPendingActions 使用 __thiscall 调用约定而不是 __stdcall。对应 __thiscall,“this” 指针通过 ECX 寄存器传入,而不是栈。

71517272 8d4eec           lea     ecx,[esi-0x14]    ; ecx = (CUserView*)this
71517275 e832fbffff       call CUserView::CancelPendingActions (71516dac) ; __thiscall
    ReleaseAndNull(&_psf);
7151727a 33ff             xor     edi,edi           ; edi = 0 (for later)
No_Psv:
7151727c 8d4678           lea     eax,[esi+0x78]    ; eax = &_psf
7151727f 50               push    eax               ; parameter
71517280 ffd3             call    ebx               ; call ReleaseAndNull
    if (fViewObjectChanged)
71517282 397dfc           cmp     [ebp-0x4],edi     ; fViewObjectChanged == 0?
71517285 740d             jz      NoNotifyViewClients (71517294) ; jump if zero
       NotifyViewClients(DVASPECT_CONTENT, -1);
71517287 8b46ec           mov     eax,[esi-0x14]    ; eax = ((CUserView*)this)->lpVtbl
7151728a 8d4eec           lea     ecx,[esi-0x14]    ; ecx = (CUserView*)this
7151728d 6aff             push    0xff              ; -1
7151728f 6a01             push    0x1               ; DVASPECT_CONTENT = 1
71517291 ff5024           call    [eax+0x24]        ; __thiscall NotifyViewClients
NoNotifyViewClients:
    if (m_pszTitle)
71517294 8b8680000000     mov     eax,[esi+0x80]    ; eax = m_pszTitle
7151729a 8d9e80000000     lea     ebx,[esi+0x80]    ; ebx = &m_pszTitle (for later)
715172a0 3bc7             cmp     eax,edi           ; eax == 0?
715172a2 7409             jz      No_Title (715172ad) ; jump if zero
        LocalFree(m_pszTitle);
715172a4 50               push    eax               ; m_pszTitle
715172a5 ff1538125071     call   [_imp__LocalFree]
        m_pszTitle = NULL;

记得 EDI 寄存器仍然是0,而EBX是 &m_pszTitle,因为这些寄存器需要函数调用后恢复。

715172ab 893b             mov     [ebx],edi         ; m_pszTitle = 0
No_Title:
    SetRect(&m_rcBounds, 0, 0, 0, 0);
715172ad 57               push    edi               ; 0
715172ae 57               push    edi               ; 0
715172af 57               push    edi               ; 0
715172b0 81c6fc000000     add     esi,0xfc          ; esi = &this->m_rcBounds
715172b6 57               push    edi               ; 0
715172b7 56               push    esi               ; &m_rcBounds
715172b8 ff15e41a5071     call   [_imp__SetRect]

注意你不在需要 “this” 值了,所以编译器使用 add 指令来跳转,而不是使用之前的使用其他寄存器来储存地址的方式。这确实会提升 win 性能,因为 Pentium u/v 的流水线,因为 v 管线可以处理算术,但不同计算地址。

return S_OK;
715172be 33c0             xor     eax,eax           ; eax = S_OK

最后,恢复需要处理的寄存器,清理栈,并返回到caller(调用函数),删除传入的参数。

715172c0 5b               pop     ebx               ; restore
ReturnNoEBX:
715172c1 5f               pop     edi               ; restore
715172c2 5e               pop     esi               ; restore
715172c3 c9               leave                     ; restores EBP and ESP simultaneously
715172c4 c20400           ret     0x4               ; return and clear parameters
关注
打赏
1664331872
查看更多评论
立即登录/注册

微信扫码登录

0.0445s