강의정리/Z0FCourse_Re

[x64] Chapter 6 - DLL/6.08 MysteryFunc.md

우와해커 2020. 2. 3. 15:10

6.08 MysteryFunc
우리가 함수 이름을 가지고 있기 때문에 몇가지 DLL exports를 리버싱하는 방법을 소개하기로 결정했습니다.
불행히도, 이것은 항상 그렇지는 않습니다.
이 함수는 아주 작지만 예상되는 내용을 약간 미리 볼 수 있습니다.

 

여기 MysteryFunc를 디스어셈블했습니다.

 

~~그림(디스어셈블 코드)~~

 

이 함수는 두개의 파라미터를 받는 것처럼 보입니다. RCX와 RDX가 사용되기 때문입니다.

 

MOV QWORD PTR DS:[RDX], RCX
RDX는 사용 방식으로 인해 일부 데이터 구조에 대한 포인터처럼 보입니다.
이것이 의미하는 것은 일종의 데이터 구조체의 지표인 오프셋 (예 : + 0x8)으로 액세스되고 있다는 것입니다.
이 코드는 배열이나 클래스 (또는 비슷한 것) 일 수 있습니다.

이 코드는 RCX (첫 번째 매개 변수)를 [RDX] (두 번째 매개 변수가 가리키는 주소)로 이동합니다.
RDX는 일종의 포인터입니다.

 

LEA RAX, QWORD PTR DS:[RCX + 0x4]
RCX + 0x4의 주소를 RAX에 로드합니다.
RCX로부터 오프셋이 사용되고 있기 때문에 RCX도 일종의 데이터 구조체라고 생각합니다.

 

MOV QWORD PTR DS:[RDX + 0x8], RCX
RCX (첫 번째 매개 변수)를 RDX + 0x8로 이동하십시오.

 

MOV QWORD PTR DS:[RDX + 0x10], RAX
RAX (RCX + 0x4 주소)를 RDX + 0x10으로 이동하십시오. RDX는 분명하게 일종의 구조체 또는 배열입니다.

 

LEA RAX, QWORD PTR DS:[RCX + 0x8]
RCX + 0x8 주소를 RAX에로드하십시오.

 

MOV QWORD PTR DS:[RDX + 0x18], RAX
RAX를 RDX + 0x18로 옮깁니다

 

MOV RAX, RCX
RCX는 리턴 값으로써 RAX로 이동됩니다. 이것은 이 함수가 어떤 구조체에 대한 포인터를 반환한다는 것을 알려줍니다. 이 구조체는 또한 두번째 파라미터입니다. 수정되지 않은 매개 변수를 반환하는 것이 이상하지만 그게 그런 방식입니다.

 

이제 파해쳐 봅시다. 함수는 어떤 종류의 데이터 구조(struct, class, array 등)인 두 개의 매개 변수를 취하는 것으로 보입니다.  빠른 참고로, 데이터 구조가 전달 될 때 단지 시작/기본 주소가 전달될 뿐입니다.

이 주소에서 함수는 오프셋을 통해 구조의 요소에 액세스합니다.
이 함수는 데이터 구조체에 대한 초기화/셋업 코드의 일부 형태일 수 있습니다.
이 함수의 전반인 목적은 하나의 데이터 구조체에서 다른 데이터 구조체로 자료를 복사하는 것입니다.
제가 흥미로운 점은 데이터 구조가 정렬되어 있지 않다는 것입니다.
제가 의미하는 바의 예는 x[0] = x[1]과 같습니다. 즉, 이것은 단순한 데이터 구조체 복사 함수가 아닙니다.
아마도 추가적인 데이터를 붙이거나 또는 그것들이 서로 다른 구조일 수 있습니다.
새로 알아낸 지식으로 코드를 다시 분석해 봅시다.

 

MOV QWORD PTR DS:[RDX], RCX
RCX 데이터 구조체의 주소는 RDX 데이터 구조체의 첫 번째 요소에 들어갑니다.


LEA RAX, QWORD PTR DS:[RCX + 0x4]
RCX 데이터 구조체에서 두 번째 매개 변수의 주소는 RAX로 이동됩니다.
여기서 RAX는 주소를 구조로 옮기는 중개인으로 사용됩니다.
이는 MOV RDX + 0x8, [RCX + 0x4]와 같은 작업을 수행 할 수 없기 때문에 수행됩니다.


MOV QWORD PTR DS:[RDX + 0x8], RCX
RCX는 RDX 데이터 구조에서 두번째 요소로 이동합니다. RDX의 첫번째 요소가 주소이기 때문에 (RCX로 지정됨)
이것이 두 번째 요소이며 세 번째 요소가 아니라는 것을 알고 있습니다 .(오프셋 + 0x0, + 0x4 + 0x8에 3, 4 바이트 정수 요소가있는 것처럼).
이 DLL은 64 비트이므로 주소는 64비트 (8바이트)일 가능성이 높습니다.
따라서 첫 번째 요소는 당신이 익숙한 4바이트가 아닌 64비트 (8 바이트) 주소입니다.

 

 

그렇다면 RCX에 몇 개의 요소가 있는지 어떻게 알 수 있습니까?
우리가 확인한 것보다 데이터 구조체 안에는 더 많은 요소가 있을 수 있습니다.
이것은 모든 데이터 구조체에 해당됩니다. 이 경우 적어도 우리는 우리가 확인한 요소들은 4바이트인라는 것을 알 수 있습니다.
그 이유는 그것들이 RCX + 0x8, RCX + 0x10이 아닌 RCX + 0x4, RCX + 0x8과 같이 액세스되기 때문입니다.

 


MOV QWORD PTR DS:[RDX + 0x10], RAX
RAX (RCX 데이터 구조체의 두 번째 요소의 주소)를 RDX 데이터 구조체의 세번째 요소로 이동하십시오.


LEA RAX, QWORD PTR DS:[RCX + 0x8]
RCX 데이터 구조체의 세 번째 요소의 주소를 RAX로 이동하십시오.


MOV QWORD PTR DS:[RDX + 0x18], RAX
RAX (RCX 데이터 구조체의 세 번째 요소의 주소)를 RDX 데이터 구조체의 네번째 요소로 이동하십시오.

 

 

MOV RAX, RCX
데이터 구조체의 주소인 첫 번째 매개 변수를 리턴하십시오.
다시 말하지만, 이건 이상합니다. RCX는 수정되지 않은 매개 변수가 포함합니다. (왜 그것을 반환할까요?)

그리고 RCX는 이미 RDX 데이터 구조에 복사되었습니다.
그것은 무의미하거나, 이상하거나, 심지어 중복되어 보일지 모르지만 리버싱할 때 당신이 본 것에 놀라게 될 것입니다.


이제 이해되기 시작했다!! 우리는 여전히 데이터 구조가 무엇인지 확신할 수 없습니다.
그것들은 클래스, 배열 또는 기타 데이터 구조체 타입일 수 있습니다.
실제로 클래스와 배열의 차이는 매우 작습니다. 우리가 추측 할 수있는 의사 코드를 살펴 보겠습니다.

 

둘다 클래스인 경우

class MyClass{
public:
	int x, y, z;
};
class AddrClass {
public:
	void* addrOfOldClass;
	int *x, *y, *z;
};

void* CopyClass(MyClass* oldClass, AddrClass* newClass) {
	newClass->addrOfOldClass = oldClass; //addr of oldClass
	newClass->x = (int*)&oldClass->x; //addr of oldClass->x
	newClass->y = (int*)&oldClass->y;
	newClass->z = (int*)&oldClass->z;
	return oldClass;
}

int main() {
	MyClass oldClass;
	myClass1.x = 10;
	myClass1.y = 20;
	myClass1.z = 30;

	AddrClass newClass;
	CopyClass(&oldClass, &newClass);
}


둘다 배열인 경우

void* CopyArray(int oldArray[], void* newArray[]) {
	newArray[0] = &oldArray; //addr of oldArray
	newArray[1] = &oldArray[0]; //addr of oldArray[0]
	newArray[2] = &oldArray[1];
	newArray[3] = &oldArray[2];
	return &oldArray;
}

int main() {
	int myArray[3] = { 1,2,3 };
	void* addrArray[4]; //array of pointers
	CopyArray(myArray, addrArray);
}

 

바라건대 이제 당신이 MysteryFunc가 무엇을 하고 있는지 이해하기 바랍니다.


여기 실제 함수 코드가 있습니다.
당신은 extern "C"__declspec (dllexport)을 무시할 수 있습니다.
__declspec (dllexport) : 함수를 DLL export로 정의하고 extern "C"를 사용하여 이름 맹글링을 방지합니다.
나는 우리가 리버싱한 DLL을 작성했기 때문에 소스 코드를 얻었습니다.

 

extern "C" __declspec(dllexport) void* MysteryFunc(Player* player, int* arr[]) {
	arr[0] = (int*)player;
	arr[1] = (int*)&player->score;
	arr[2] = (int*)&player->health;
	arr[3] = (int*)&player->name;
	return player;
}

 

보시다시피, 우리는 거의 그것을 못 박았습니다. 실제 코드는 클래스와 배열이 모두 사용되었음을 나타냅니다.
보다 구체적으로, 클래스의 내용은 배열로 복사됩니다.

이 경우 매개 변수가 정적 분석만으로 클래스 또는 배열인지 알 수 있는 방법이 없습니다.
저수준에서는 구조와 배열이 같은 방식으로 액세스됩니다.
우리는 매개 변수가 데이터 구조라는 것을 알고 있었지만 어떤 종류인지 알기가 불가능했습니다.
좀 더 정확하게 우리가 할 수 있는 유일한 방법은 DLL을 사용하는 프로그램을 디버깅하고 MysteryFunc()가 어떻게 사용되는지 분석하는 것입니다.


나는 이 수업을 정말 즐겼습니다. 이런 종류의 문제 / 퍼즐 해결이 내가 리버싱을 즐기는 이유입니다.
이것은 간단한 예입니다. 우리는 더 복잡한 예를 곧 살펴볼 것입니다.