ghostparty.cpp 소스를 보면서 실제 문제가 될만한 곳들을 먼 훑어본 후, 익스플로잇 해봅니다.
전체 소스코드는 여기서 확인.
https://github.com/dokydoky/writeup/blob/master/pwnerable.tw/ghostparty/ghostparty.cpp
Vulerability case 1
class Alan : public Ghost {
public :
Alan():lightsaber(NULL){
type = "Alan" ;
};
Alan( int ghostage,string ghostname,string ghostmsg){
type = "Alan" ;
age = ghostage ;
name = new char [ghostname.length() + 1 ];
strcpy(name,ghostname.c_str());
msg = ghostmsg ;
};
void addlightsaber(string str){
lightsaber = ( char *)str.c_str();
}
void ghostinfo(){
cout << "Type : " << type << endl ;
cout << "Name : " << name << endl ;
cout << "Age : " << age << endl ;
cout << "Lightsaber : " << lightsaber << endl ;
}
~Alan(){
};
private :
char *lightsaber ;
};
lightsaber의 UAF 취약점은 PIE를 leak할 수 있다.
lightsaber에 0x60 을 할당하면 0x60 크기의 dangling pointer가 생기고,
그 때 0x60 크기의 객체를 할당하면 lightsaber가 객체의 vtable를 갖고 있다. => ghostinfo()가 호출될 때 vtable주소 leak
EIP control은 불가.. ~Alan()에서 lightsaber를 delete하면 추가적인 exploit이 가능하나 delete 하지 않음.
=> 이 취약점으로 못풀게 의도적으로 해놓은 듯.
|
Vulerability case 2
281 class Kasa : public Ghost {
282 public :
283 Kasa(){
284 type = "Kasa-obake" ;
285 };
286
287 Kasa( int ghostage,string ghostname,string ghostmsg){
288 type = "Kasa-obake" ;
289 age = ghostage ;
290 name = new char [ghostname.length() + 1 ];
291 strcpy(name,ghostname.c_str());
292 msg = ghostmsg ;
293
294
295
296 };
297
298 void addinfo( int ghostfoot,string str){
299 unsigned int size = 0 ;
300 char buf[ 20 ] ;
301 foot = ghostfoot ;
302 eyes = str ;
303 cout << "Input to echo :" ;
304 size = read( 0 ,buf, 20 );
305 buf[size] = 0 ;
306
307
308 cout << "echo :" << buf << endl ;
309 };
310
311 void ghostinfo(){
312 cout << "Type : " << type << endl ;
313 cout << "Name : " << name << endl ;
314 cout << "Age : " << age << endl ;
315 cout << "Foot : " << foot << endl ;
316 cout << "Eyes : " << eyes << endl ;
317 }
318 ~Kasa(){
319 foot = 0 ;
320 eyes.clear();
321 };
322 private :
323 int foot ;
324 string eyes ;
325 };
|
297 void addinfo( int ghostfoot,string str){
298 unsigned int size = 0 ;
299 char buf[ 20 ] ;
300 foot = ghostfoot ;
301 eyes = str ;
302 cout << "Input to echo :" ;
303 size = read( 0 ,buf, 20 );
304 buf[size] = 0 ;
305
307 cout << "echo :" << buf << endl ;
308 };
309
-00000000000000A1 db ? ; undefined
-00000000000000A0 buf db 32 dup(?) << buf[ 20 ] + padding( 12 )
- 0000000000000080 randomBuf db 32 dup(?)
- 0000000000000060 secretBuf db 32 dup(?)
- 0000000000000040 db ? ; undefined
buf는 1byte null override가 있지만, 실제로 바이너리에서 32byte가 할당되서 별 문제 없음.
|
Vulerability case 3
추가적으로 살펴보면 Zombie.addsecret에서 sfd를 close하지 않는데, exploit에 도움이 되지는 않음.
그리고!! (char *) 타입의 멤버를 가진 객체들 중에 복사생성자, 대입연산자를 정의하지 않아서 UAF취약점이 있는 객체들이 있음.
디폴트 복사생성자는 얕은 복사(shallow copy)를 하기 때문에 발생.
class Vampire : public Ghost {
public :
Vampire():blood(NULL){
type = "Vampire" ;
};
Vampire( int ghostage,string ghostname,string ghostmsg){
type = "Vampire" ;
age = ghostage ;
name = new char [ghostname.length() + 1 ];
strcpy(name,ghostname.c_str());
msg = ghostmsg ;
blood = NULL ;
};
void addblood(string com){
blood = new char [com.length()+ 1 ];
memcpy(blood,com.c_str(),com.length());
}
void ghostinfo(){
cout << "Type : " << type << endl ;
cout << "Name : " << name << endl ;
cout << "Age : " << age << endl ;
cout << "Blood : " << blood << endl ;
}
~Vampire(){
delete[] blood;
};
private :
char *blood ;
};
template < class T>
void speaking(T ghost){
ghost.speak();
};
|
문제가 있는 class들은 아래와 같다.
- Vampire, Alan : 복사생성자, 대입연산자 없음
- Devil, Zombie : 대입연산자 없음
하지만, 대입연산자로 인해 실제 코드에서 취약점이 발생되는 부분은 없고,
Alan은 위에서 설명했듯이 destructor에 delete이 없어서 PIE leak까지만 가능.
Vampire의 shallow copy constructor로 인한 UAF 취약점을 이용해 exploit 해봅시다.
Exploit code
https://github.com/dokydoky/writeup/blob/master/pwnerable.tw/ghostparty/ghostExploit.py
class Vampire : public Ghost {
public :
Vampire():blood(NULL){
type = "Vampire" ;
};
Vampire( int ghostage,string ghostname,string ghostmsg){
type = "Vampire" ;
age = ghostage ;
name = new char [ghostname.length() + 1 ];
strcpy(name,ghostname.c_str());
msg = ghostmsg ;
blood = NULL ;
};
Vampire( const Vampire&) = delete;
void operator=( const Vampire&) = delete;
void addblood(string com){
blood = new char [com.length()+ 1 ];
memcpy(blood,com.c_str(),com.length());
}
void ghostinfo(){
cout << "Type : " << type << endl ;
cout << "Name : " << name << endl ;
cout << "Age : " << age << endl ;
cout << "Blood : " << blood << endl ;
}
~Vampire(){
delete[] blood;
};
private :
char *blood ;
};
template < class T>
void speaking(T &ghost){
ghost.speak();
};
|
speaking 함수에서 parameter type을 Reference로 넘겨주면(Call-by-Value가 아닌 Call-by-Reference) 복사 생성자가 호출되지 않으므로 문제가 없다.
하지만, 근본적으로 문제를 해결하기 위해서는 디폴트 복사 생성자와 디폴트 대입 생성자가 생성되지 않도록 명시해야 한다.
이렇게 되면 Call-by-value로 넘기는 경우 compile time에 error가 발생하므로, 취약점이 발생하는 코드를 방지할 수 있다.
참고 : https://www.securecoding.cert.org/confluence/display/cplusplus/MEM51-CPP.+Properly+deallocate+dynamically+allocated+resources
추가로,
vtable을 조작할 때 speak() 함수의 offset을 조작해서 exploit 할 수 없다.
출제자가 의도한 건지는 모르겠지만 아래 코드에서 speaking(**iter)가 호출될 때 type이 항상 Ghost이기 때문에
바이너리에서 보면 speaking함수에서 vtable을 참조하지 않고 항상 Ghost.speak()이 호출된다.
그래서 ghostinfo()의 offset을 조작해서 exploit 해야 한다.
template < class T>
void speaking(T ghost){
ghost.speak();
};
void night(){
unsigned int num ;
vector<Ghost *>::iterator iter ;
for (iter = ghostlist.begin();iter != ghostlist.end() ; iter++){
num = 31 +rand()% 7 ;
cout << "\033[" << num << "m" ;
speaking(**iter);
cout << "\033[0m" ;
}
}
|