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()가 어떻게 사용되는지 분석하는 것입니다.
나는 이 수업을 정말 즐겼습니다. 이런 종류의 문제 / 퍼즐 해결이 내가 리버싱을 즐기는 이유입니다.
이것은 간단한 예입니다. 우리는 더 복잡한 예를 곧 살펴볼 것입니다.
'강의정리 > Z0FCourse_Re' 카테고리의 다른 글
CURRENTLY IN DEVELOPMENT (Chapter 7 - Windows) (0) | 2020.02.04 |
---|---|
[x64] DLL/6.09 ImplementingPlayer + 6.10 Final Notes.md (0) | 2020.02.04 |
[x64] Chapter 6 - DLL/6.7 PrintPlayerStats.md (0) | 2020.01.31 |
[x64] Chapter 6 - DLL/6.6 InitializePlayer.md (0) | 2020.01.30 |
[x64] Chapter 6 - DLL/6.5 PrintArray.md (0) | 2020.01.29 |