-
Shallow Copy 문제점Hack/Pwnable 2018. 1. 29. 17:19
한국말로는 얕은 복사라고 하고, 반대의 복사방법은 Deep Copy, 깊은복사라고 한다.
우선 아래 코드를 보자
using namespace std;
class Person
{
public:
int age;
char *name;
public:
Person(int _age, char *_name) {
age = _age;
name = new char [strlen(_name)+1];
strcpy(name,_name);
}
Person(const Person& s)
{
age = s.age;
name = s.name;
}
~Person() {
// delete name;
}
void printPerson()
{
cout << "Name: " << name << endl;
cout << "Age: " << age << endl;
}
};
int main()
{
Person A(32,"s0ngsari");
Person B = A;
A.age = 21;
strcpy(A.name, "tenten");
A.printPerson();
B.printPerson();
}코드를 보면 Person 클래스안에 생성자, 소멸자, 멤버함수가 존재하는것을 알 수 있고, 메인함수에서는 해당 클래스 멤버에 값을 전달한다. A클래스 변수는 age 와 name 을 각각 32와 s0ngsari를 받고, B 클래스 객체는 그대로 받게된다. 그리고 A클래스 객체의 age와 name을 각각 21과 tenten으로 바꿔주고 출력을 시켜보았다. 먼저 실행결과는 아래와 같다.
root@ubuntu:/home/study# ./shallow
Name: tenten
Age: 21
Name: tenten
Age: 32분명 A의 객체만 tenten으로 수정해주었는데 B의 name 또한 데이터가 바껴버렸다.
Person 생성자에서 name을 new char로 힙에 할당을 해주는데, 힙 포인터를 같이 사용함을 알 수 있다.
0x614c10: 0x00000000 0x00000000 0x00000021 0x00000000
0x614c20: 0x676e3073 0x69726173 0x00000000 0x00000000실제로 A 클래스의 name과 B클래스의. name이 같은 주소를 가지고있는것을 확인할 수 있다.
root@ubuntu:/home/study# ./shallow
0x1089c20
0x1089c20
Name: tenten
Age: 21
Name: tenten
Age: 32이제, 위 코드에서 소멸자에 있는 delete name 주석을 지워보고 실행해보자. 당연히 더블프리버그가 발생할 것이다.
(gdb) disas $rip
Dump of assembler code for function _ZN6PersonD2Ev:
0x0000000000400d40 <+0>: push %rbp
0x0000000000400d41 <+1>: mov %rsp,%rbp
0x0000000000400d44 <+4>: sub $0x10,%rsp
0x0000000000400d48 <+8>: mov %rdi,-0x8(%rbp)
0x0000000000400d4c <+12>: mov -0x8(%rbp),%rax
0x0000000000400d50 <+16>: mov 0x8(%rax),%rax
0x0000000000400d54 <+20>: mov %rax,%rdi
=> 0x0000000000400d57 <+23>: callq 0x400990 <_ZdlPv@plt>첫번째 A 소멸자에서 name을 delete하는데, B 객체도 name 가리키는 포인터가 같기 때문에 당연히 DFB가 발생한다.
위 delete의 인자는 다음과 같다.
(gdb) i r
rax 0x614c20 6376480이는 name의 포인터인데, 당연히 터질수밖에 :)
이런 버그를 방지하기 위해 개발자들은 깊은 복사를 이용한다.
아래와 같이 깊은 복사를 이용해보자
using namespace std;
class Person
{
public:
int age;
char *name;
public:
Person(int _age, char *_name) {
age = _age;
name = new char[strlen(_name)+1];
strcpy(name,_name);
}
Person(const Person& s)
{
age = s.age;
name = new char[strlen(_name)+1];
strcpy(name,s.name);
}
~Person() {
delete name;
}
void printPerson()
{
cout << "Name: " << name << endl;
cout << "Age: " << age << endl;
}
};
int main()
{
Person A(32,"s0ngsari");
Person B = A;
A.age = 21;
strcpy(A.name, "tenten");
A.printPerson();
B.printPerson();
}얕은 복사와 같이 같은 포인터를 참조하고 값을 넣지않고, 새로운 영역을 할당해서 값을 관리하는것을 알 수 있다.
root@ubuntu:/home/study# ./shallow
0x1711c20
0x1711c40
Name: tenten
Age: 21
Name: s0ngsari
Age: 32A.name 과 B.name을 출력해보면 영역이 새로 할당되어 같은 포인터를 가리키지 않아, 버그가 발생하지 않는다.
0x614c10: 0x00000000 0x00000000 0x00000021 0x00000000
0x614c20: 0x746e6574 0x69006e65 0x00000000 0x00000000
0x614c30: 0x00000000 0x00000000 0x00000021 0x00000000
0x614c40: 0x676e3073 0x69726173 0x00000000 0x00000000
0x614c50: 0x00000000 0x00000000 0x00000411 0x00000000소멸자가 호출될 때 또한 다른 포인터를 인자로 가진다.
(gdb) i r $eax
eax 0x614c40 6376512 // first destroy
(gdb) i r $eax
eax 0x614c20 6376480 // second destroy정답: 깊은 복사를 이용하자 ! 얕은 복사를 이용하면 Use-After-Free가 발생하여 함수포인터가 있다면 익스플로잇이 가능할것이당!
'Hack > Pwnable' 카테고리의 다른 글
Helper Python (0) 2018.10.22 ASIS CTF 2018 Tinypwn (1) 2018.08.30 File Stream Pointer에 관한 글 (6) 2017.01.24 nc서버 열기 (XINETD) (0) 2016.12.19 malloc large_chunk exploit scenario (2) 2016.12.02