c / c + + 함수 깊이 탐색 (3) - 허 멤버 함수 호출 의 기본 과정
c + + 를 배 운 적 이 있 으 면 한동안 알 수 있 습 니 다. c + + 는 가상 함수 에 의 해 다 형 을 실현 합 니 다. 다음 코드 는 다음 과 같 습 니 다.
#include <iostream>
using namespace std;
class Base
{
public:
virtual void Print()
{
cout<<"^-^"<<endl;
}
};
class Derive:public Base
{
public:
virtual void Print()
{
cout<<"T-T"<<endl;
}
};
int main()
{
Base *p=new Derive();
p->Print();
}
하하, T - T 출력 ~ ~ ~ ~ ~
가상 함수 의 실현 원 리 를 이해 하 는 데 있어 서 c + 초보 자가 중수 에 가 는 데 필수 적 인 길 중 하나 이다. 그 실현 원리 에 대해 개인 적 으로 라 는 책 을 추천 합 니 다.
원 리 는 매우 투철 하 게 말한다.현재 주류 컴 파일 러 의 구체 적 인 실현 방식 을 분석 하고 어 셈 블 리 의 측면 에서 컴 파일 러 의 가상 함수 의 실현 원 리 를 분석 합 니 다 (최근 c / c + 작업 을 찾 으 면 가상 함수 가 가능성 이 높 을 것 으로 예상 합 니 다 ~).
먼저 c + + 표준 은 가상 함수 의 행위 만 규정 하고 이러한 행위 의 구체 적 인 실현 을 규정 하지 않 았 으 나 현재 주류 의 컴 파일 러 (vc, g +) 는 실현 에 있어 어느 정도 호흡 을 이 루 었 다. 모두 대상 앞의 4 바이트 에 가상 시계 지침 을 삽입 하 는 것 이다.
이 가상 시계 바늘 은 대응 하 는 클래스 의 가상 시 계 를 가리 키 며, 가상 함 수 를 호출 할 때 가상 시계 바늘 을 통 해 가상 시 계 를 찾 아 최종 적 으로 호출 할 함 수 를 얻 는 것 이다. 이것 이 바로 동적 으로 연 결 된 바 텀 실현 방식 이다.
다음은 vc 10 기본 컴 파일 옵션 debug 아래 위의 프로그램의 어 셈 블 리 입 니 다.
257: int main()
258: {
01031500 push ebp
01031501 mov ebp,esp
01031503 sub esp,0DCh
01031509 push ebx
0103150A push esi
0103150B push edi
0103150C lea edi,[ebp-0DCh]
01031512 mov ecx,37h
01031517 mov eax,0CCCCCCCCh
0103151C rep stos dword ptr es:[edi]; (/RTCs) , ,
; cc, int 3
; int , -858993460
; ,-858993460 0xcccccccc
; , debug ,
; 。
259: Base *p=new Derive();
0103151E push 4 ;operator new , Derive 。
01031520 call operator new (1031208h) ; operator new
01031525 add esp,4 ;__cdecl , , ,
01031528 mov dword ptr [ebp-0D4h],eax; operator new dword ptr[ebp-0D4h]
; ,operator new ,
; ,vc eax
0103152E cmp dword ptr [ebp-0D4h],0 ; 0
01031535 je main+4Ah (103154Ah) ; 0
01031537 mov ecx,dword ptr [ebp-0D4h]; operator new ecx,vc
; this ecx 。
0103153D call Derive::Derive (1031127h) ; , ,
; ,
01031542 mov dword ptr [ebp-0DCh],eax ; ,
; eax, 。
;
01031548 jmp main+54h (1031554h) ; 。
0103154A mov dword ptr [ebp-0DCh],0; je main+4Ah (103154Ah)
; , , p 0,
; this 0, this+ 。
01031554 mov eax,dword ptr [ebp-0DCh] ; , dword ptr [ebp-0DCh]
; 。
0103155A mov dword ptr [p],eax ; dword ptr [p] ,
; ,
260: p->Print();
0103155D mov eax,dword ptr [p] ; eax, eax=p(p )
01031560 mov edx,dword ptr [eax] ; eax , eax
; , 4 , 4 edx
; edx=*(int*)p, 4
; , 。
01031562 mov esi,esp
01031564 mov ecx,dword ptr [p] ;this ecx
01031567 mov eax,dword ptr [edx]; , , edx ,
; 4 , , edx
; , ,
;0x00100000, 0x00100000~0x00100003
; ,0x00100004~0x00100007 .....
; ,eax=*(int*)*(int*)p
; eax
01031569 call eax ;
0103156B cmp esi,esp
0103156D call @ILT+435(__RTC_CheckEsp) (10311B8h)
261: }
01031572 xor eax,eax
01031574 pop edi
01031575 pop esi
01031576 pop ebx
01031577 add esp,0DCh
0103157D cmp ebp,esp
0103157F call @ILT+435(__RTC_CheckEsp) (10311B8h)
01031584 mov esp,ebp
01031586 pop ebp
01031587 ret
상기 분석 을 통 해 * (int *) * (int *) p 와 같은 표현 식 으로 가상 표 의 함수 방법 을 얻 을 수 있 습 니 다. 이 곳 은 포인터 응용 을 고찰 하 는 기본 적 인 기능 입 니 다.
void(*f)()=(void(*)())*(int*)*(int*)p;
f();
최종 호출 은 Derive:: Print () 입 니 다.분명히 * (int *) (* (int *) p + 4) 는 가상 표 의 두 번 째 함수 주소 값 입 니 다. 있 으 면 ~ ~ ~ ~ ~
컴 파일 러 가 만 든 구조 함수 에서 무엇 을 했 는 지 살 펴 보 자.
Derive::Derive: 01031127 jmp Derive::Derive (10315B0h)
메모리 10315 B0h 에 있 는 어 셈 블 리 명령 을 찾 습 니 다:
Derive::Derive:
010315B0 push ebp
010315B1 mov ebp,esp
010315B3 sub esp,0CCh
010315B9 push ebx
010315BA push esi
010315BB push edi
010315BC push ecx
010315BD lea edi,[ebp-0CCh]
010315C3 mov ecx,33h
010315C8 mov eax,0CCCCCCCCh
010315CD rep stos dword ptr es:[edi]
010315CF pop ecx
010315D0 mov dword ptr [ebp-8],ecx; ecx this dword ptr [ebp-8]
010315D3 mov ecx,dword ptr [this] ;this ecx, ,
;dword ptr [this] dword ptr [ebp-8]
010315D6 call Base::Base (1031131h);
010315DB mov eax,dword ptr [this] ;dword ptr [this] Base::Base
; ,
010315DE mov dword ptr [eax],offset Derive::`vftable' (1037834h)
; dword ptr [this]
; Derive::`vftable' ,dword ptr [this]
; , p, *(int*)p=
;Derive::`vftable' .
010315E4 mov eax,dword ptr [this] ; eax,
010315E7 pop edi
010315E8 pop esi
010315E9 pop ebx
010315EA add esp,0CCh
010315F0 cmp ebp,esp
010315F2 call @ILT+435(__RTC_CheckEsp) (10311B8h)
010315F7 mov esp,ebp
010315F9 pop ebp
010315FA ret
베이스 보기:: 베이스 (1031131 h);어 셈 블 리 코드
Base::Base:
01031690 push ebp
01031691 mov ebp,esp
01031693 sub esp,0CCh
01031699 push ebx
0103169A push esi
0103169B push edi
0103169C push ecx
0103169D lea edi,[ebp-0CCh]
010316A3 mov ecx,33h
010316A8 mov eax,0CCCCCCCCh
010316AD rep stos dword ptr es:[edi]
010316AF pop ecx
010316B0 mov dword ptr [ebp-8],ecx
010316B3 mov eax,dword ptr [this]
010316B6 mov dword ptr [eax],offset Base::`vftable' (1037844h)
010316BC mov eax,dword ptr [this] ; Derive ,
010316BF pop edi
010316C0 pop esi
010316C1 pop ebx
010316C2 mov esp,ebp
010316C4 pop ebp
010316C5 ret
여기까지 분석 하면 여러분 들 이 가상 함수 호출 에 대해 기본 적 인 인식 을 가지 고 있다 고 믿 습 니 다. 컴 파일 러 는 가상 함 수 를 실현 할 때 주로 다음 과 같은 절차 가 있 습 니 다.
1. 컴 파일 할 때 클래스 의 성명 에 따라 가상 함수 표를 생 성 합 니 다.
2. 대상 을 만 들 때 컴 파일 러 는 클래스 의 구조 함수 에 일부 코드 를 삽입 하여 대상 의 가상 포인터 를 초기 화 합 니 다. 보통 (vc g +) 구조 함수 에 들 어 갑 니 다.
시작 부분 에 코드 를 삽입 합 니 다.
3. 포인터 나 인용 으로 가상 함 수 를 호출 할 때 동적 바 인 딩 을 활성화 합 니 다. 실질 적 으로 가상 포인터 로 함 수 를 찾 는 과정 입 니 다.
그래서 이와 같은 코드 Derive () {memset (this, 0, sizeof (Derive);} 은 재난 적 입 니 다 ~ ~ ~
가상 함수 의 실현 은 구조 함 수 를 빌려 야 하기 때문에 구조 함 수 는 가상 함수 가 될 수 없습니다 ~ ~ ~
마지막 으로 c + + 가상 함수 에 관 한 hack 의 간단 한 프로그램 두 개 를 소개 하여 번역기 에 대한 가상 함수 체제 에 대한 이 해 를 강화 합 니 다 ~ ~ ~
#include <iostream>
#include <vector>
using namespace std;
class Base
{
public:
virtual void PrintA()
{
cout<<"^-^"<<endl;
}
virtual void PrintB()
{
cout<<"T-T"<<endl;
}
};
class Derive:public Base
{
public:
virtual void PrintA()
{
cout<<":)"<<endl;
}
virtual void PrintB()
{
cout<<":("<<endl;
}
};
void Hack1()
{
cout<<"Hack1"<<endl;
}
void Hack2()
{
cout<<"Hack2"<<endl;
}
int main()
{
Base *p=new Derive();
int *pVtable[2]={(int*)Hack1,(int*)Hack2};//
*(int*)p=(int)pVtable;//
p->PrintA();
p->PrintB();
system("pause");
}
허 표 지침 을 수정 하여 납치 절 차 를 진행 하 는 것 이 분명 합 니 다. 다음은 허 표를 수정 하여 납치 절 차 를 진행 하 겠 습 니 다 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
#include <iostream>
#include <Windows.h>
using namespace std;
class Base
{
public:
virtual void PrintA()
{
cout<<"^-^"<<endl;
}
virtual void PrintB()
{
cout<<"T-T"<<endl;
}
};
class Derive:public Base
{
public:
virtual void PrintA()
{
cout<<":)"<<endl;
}
virtual void PrintB()
{
cout<<":("<<endl;
}
};
void Hack1()
{
cout<<"Hack1"<<endl;
}
int main()
{
Base *p=new Derive();
int PrintAAdress=*(int*)*(int*)p;// PrintA
int PrintBAdress=*(int*)(*(int*)p+4);// PrintB
//vc debug , jmp
// 0xe9
if (*(unsigned char*)PrintAAdress==0xe9)
{
DWORD d;
int PrintBOffset=*(int*)(PrintBAdress+1);// jmp
int Hack1Offset=*(int*)((int)Hack1+1);
//jmp jmp , PrintA
// PrintB,
int diff=PrintBOffset-(PrintAAdress-PrintBAdress);
WriteProcessMemory(GetCurrentProcess(),(int*)(PrintAAdress+1), &diff, 4, &d);
diff=Hack1Offset-(PrintBAdress-(int)Hack1);
WriteProcessMemory(GetCurrentProcess(),(int*)(PrintBAdress+1), &diff, 4, &d);
//release
}else{
DWORD dwIdOld;
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,1,GetCurrentProcessId());
// ,debug ,
VirtualProtectEx(hProcess,(int*)*(int*)p,4,PAGE_READWRITE,&dwIdOld);
WriteProcessMemory(hProcess,(int*)*(int*)p, &PrintBAdress, 4, 0);
VirtualProtectEx(hProcess,(int*)*(int*)p,4,dwIdOld,&dwIdOld);
int Hack1Adress= (int)Hack1;
VirtualProtectEx(hProcess,(int*)(*(int*)p+4),4,PAGE_READWRITE,&dwIdOld);
WriteProcessMemory(hProcess,(int*)(*(int*)p+4), &Hack1Adress, 4, 0);
VirtualProtectEx(hProcess,(int*)(*(int*)p+4),4,dwIdOld,&dwIdOld);
}
// , Derive , PrintB Hack1
p->PrintA();
p->PrintB();
}
위의 프로그램 은 컴 파일 러 가 만 든 가상 표를 성공 적 으로 수정 한 것 입 니 다. 실제 hack 라 고 할 수 있 습 니 다 ~ ~, 위의 프로그램 vc9 / 10 + win 7 debug / release 기본 컴 파일 옵션 통과 ~ ~ ~ ~
만약 에 상기 두 프로그램 을 알 게 된다 면 가상 표 의 컴 파일 러 에 대한 인식 이 더욱 깊 어 질 것 이 라 고 믿 습 니 다 ~ ~ ~
원 리 는 바로 이 모양 입 니 다. 다 중 계승 상황 에서 좀 번 거 로 울 수 있 습 니 다. 대상 이 여러 개의 가상 포인터 가 생 길 수 있 기 때 문 입 니 다. 또한 가상 분석 함수 가 가상 표 에서 구 조 를 하고 각 컴 파일 러 의 차이 도 비교적 큽 니 다.
그 러 니까 왜 come 이 실 현 될 때 release 와 비슷 한 인터페이스 가 있어 야 하 는 지 ~ ~ ~
자, 여기까지 쓰 세 요. 시간 나 면 보충 할 게 요 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.