주석이 추가된 x86 디스어셈블리
다음 섹션에서는 디스어셈블리 예제를 안내합니다.
소스 코드
다음은 분석할 함수에 대한 코드입니다.
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;
}
어셈블리 코드
이 섹션에는 주석이 추가된 디스어셈블리 예제가 포함되어 있습니다.
ebp 레지스터를 프레임 포인터로 사용하는 함수는 다음과 같이 시작됩니다.
HRESULT CUserView::CloseView(void)
SAMPLE!CUserView__CloseView:
71517134 55 push ebp
71517135 8bec mov ebp,esp
이렇게 하면 함수가 ebp의 양수 오프셋으로 매개 변수에 액세스하고 지역 변수를 음의 오프셋으로 액세스할 수 있도록 프레임이 설정됩니다.
이는 프라이빗 COM 인터페이스의 메서드이므로 호출 규칙이 __stdcall. 즉, 매개 변수가 오른쪽에서 왼쪽으로 푸시되고(이 경우 없음), "this" 포인터가 푸시된 다음 함수가 호출됩니다. 따라서 함수에 진입할 때 스택은 다음과 같습니다.
[esp+0] = return address
[esp+4] = this
위의 두 지침 후에는 매개 변수에 다음과 같이 액세스할 수 있습니다.
[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
이 함수에는 두 개의 로컬 스택 변수만 필요하므로 하위 esp, 8 명령입니다. 그런 다음 푸시된 값을 [ebp-4] 및 [ebp-8]로 사용할 수 있습니다.
ebp를 프레임 포인터로 사용하는 함수의 경우 ebp 레지스터의 음수 오프셋에서 스택 지역 변수에 액세스할 수 있습니다.
71517139 56 push esi
이제 컴파일러는 함수 호출에서 보존해야 하는 레지스터를 저장합니다. 실제로 실제 코드의 첫 번째 줄과 인터리빙된 비트와 조각으로 저장합니다.
7151713a 8b7508 mov esi,[ebp+0x8] ; esi = this
7151713d 57 push edi ; save another registers
따라서 CloseView는 기본 개체의 오프셋 12에 있는 ViewState의 메서드입니다. 따라서 이는 ViewState 클래스에 대한 포인터이지만 다른 기본 클래스와 혼동될 수 있는 경우 더 신중하게 (ViewState*)로 지정됩니다.
if (m_fDestroyed)
7151713e 33ff xor edi,edi ; edi = 0
레지스터를 자체로 XORing하는 것은 레지스터를 0으로 만드는 표준 방법입니다.
71517140 39beac000000 cmp [esi+0xac],edi ; this->m_fDestroyed == 0?
71517146 7407 jz NotDestroyed (7151714f) ; jump if equal
cmp 명령은 두 값을 빼서 비교합니다. jz 명령은 결과가 0인지 확인하여 비교된 두 값이 같음을 나타냅니다.
cmp 명령은 두 값을 비교합니다. 비교 결과에 따라 후속 j 명령이 점프합니다.
return S_OK;
71517148 33c0 xor eax,eax ; eax = 0 = S_OK
7151714a e972010000 jmp ReturnNoEBX (715172c1) ; return, do not pop EBX
컴파일러가 함수의 뒷부분까지 EBX 레지스터 저장을 지연시켰기 때문에 프로그램이 이 테스트에서 "조기 종료"될 경우 종료 경로는 EBX를 복원하지 않는 경로여야 합니다.
BOOL fViewObjectChanged = FALSE;
ReleaseAndNull(&m_pdtgt);
이러한 두 코드 줄의 실행은 인터리브되므로 주의해야 합니다.
NotDestroyed:
7151714f 8d86c0000000 lea eax,[esi+0xc0] ; eax = &m_pdtgt
lea 명령은 메모리 액세스의 효과 주소를 계산하고 대상에 저장합니다. 실제 메모리 주소는 역참조되지 않습니다.
lea 명령은 변수의 주소를 사용합니다.
71517155 53 push ebx
손상되기 전에 해당 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 등록을 0으로 설정했고 EDI는 함수 호출에서 유지되는 레지스터입니다(따라서 ReleaseAndNull 에 대한 호출이 변경되지 않음). 따라서 여전히 값이 0이며 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 메서드 호출은 매우 인기가 있으므로 이를 인식하는 방법을 배우는 것이 좋습니다. 특히 Vtable 오프셋에서 QueryInterface=0, AddRef=4 및 Release=8의 세 가지 IUnknown 메서드를 직접 인식할 수 있어야 합니다.
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에서 양수 오프셋에 도달하고 지역 변수는 음수 오프셋에 배치됩니다. 그러나 이 경우와 마찬가지로 컴파일러는 어떤 용도로든 해당 메모리를 자유롭게 재사용할 수 있습니다.
주의를 기울이면 컴파일러가 이 코드를 좀 더 잘 최적화할 수 있음을 알 수 있습니다. 그것은 lea edi를 지연 수 있습니다., [esi+0xa8] 두 푸시 0x0 명령 후까지 명령, 푸시 edi로 대체. 2바이트를 저장했을 것입니다.
if (pSink == (IAdviseSink *)this)
다음 몇 줄은 C++에서 (IAdviseSink *)NULL 이 여전히 NULL이어야 한다는 사실을 보완하기 위한 것입니다. 따라서 "this"가 실제로 "(ViewState*)NULL"인 경우 캐스트의 결과는 IAdviseSink와 IBrowserService 사이의 거리가 아니라 NULL 이어야 합니다.
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 아키텍처는 그렇지 않으므로 컴파일러는 특정 기술을 사용하여 점프를 수행하지 않고 조건부 이동 명령을 시뮬레이션합니다.
조건부 평가의 일반적인 패턴은 다음과 같습니다.
neg r
sbb r, r
and r, (val1 - val2)
add r, val2
neg r은 r이 0이 아닌 경우 0에서 빼서 값을 부정하기 때문에 캐리 플래그를 설정합니다. 또한 0에서 빼면 0이 아닌 값을 빼면 대여(캐리 설정)가 생성됩니다. 또한 r 레지스터의 값도 손상되지만, 어쨌든 덮어쓰려고 하기 때문에 허용됩니다.
다음으로 , sbb r, r 명령은 자체에서 값을 빼고 항상 0이 됩니다. 그러나 캐리(대여) 비트도 뺍니다. 따라서 순 결과는 각각 캐리가 명확한지 또는 설정되었는지에 따라 r 을 0 또는 -1로 설정하는 것입니다.
따라서 sbb r, r 은 r 의 원래 값이 0이면 r 을 0으로 설정하고 원래 값이 0이 아니면 -1로 설정합니다.
세 번째 명령은 마스크를 수행합니다. r 레지스터가 0 또는 -1이기 때문에 "this"는 r 0을 그대로 두거나 r을 -1에서 (val1 - val1)로 변경하는 역할을 합니다. 이 경우 ANDing 모든 값이 -1이면 원래 값이 남습니다.
따라서 "and r, (val1 - val1)"의 결과는 r의 원래 값이 0이면 r을 0으로 설정하거나 r의 원래 값이 0이 아니면 "(val1 - val2)"로 설정합니다.
마지막으로 val2 를 r에 추가하여 val2 또는 (val1 - val2) + val2 = val1을 생성합니다.
따라서 이 일련의 명령의 궁극적인 결과는 원래 0인 경우 r 을 val2 로 설정하거나 0이 아닌 경우 val1 로 설정하는 것입니다. r = r ? val1 : val2와 동일한 어셈블리입니다.
이 특정 instance val2 = 0 및 val1 = (IAdviseSink*)를 볼 수 있습니다. (컴파일러가 최종 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하면 AND 0이 0이므로 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*)에서 (CUserView*)로 이동해야 합니다. CancelPendingActions는 __stdcall 대신 __thiscall 호출 규칙을 사용합니다. __thiscall 따르면 스택에 전달되는 대신 ECX 레지스터에 "this" 포인터가 전달됩니다.
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" 값이 필요하지 않으므로 컴파일러는 주소를 보관하기 위해 다른 레지스터를 사용하는 대신 추가 명령을 사용하여 수정합니다. V 파이프는 산술 연산을 수행할 수 있지만 계산을 처리할 수는 없으므로 펜티엄 u/v 파이프라인으로 인해 실제로 성능이 향상됩니다.
return S_OK;
715172be 33c0 xor eax,eax ; eax = S_OK
마지막으로 보존에 필요한 레지스터를 복원하고 스택을 클린 호출자에게 돌아와 들어오는 매개 변수를 제거합니다.
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