실습에대한 답이 있으므로 직접 해결하실분은 보지마세요
Memory Corruption - C++
Buffer Overflow in C++
1
2
3
4
5
6
| // g++ -o bof-1 bof-1.cpp
#include <iostream>
int main(void) {
char buf[20];
std::cin >> buf;
}
|
1
2
3
4
5
6
| // g++ -o bof-2 bof-2.cpp
#include <iostream>
int main(void) {
std::string buf;
std::cin >> buf;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // g++ -o container-1 container-1.cpp
#include <algorithm>
#include <vector>
#include <iostream>
void f(const std::vector<int> &src) {
//std::vector<int> dest(src); //오버플로우 막기
std::vector<int> dest(5);//오버플로우
std::copy(src.begin(), src.end(), dest.begin());
}
int main(void){
int size = 0;;
std::vector<int> v;
std::cin >> size;
v.resize(size);
v.assign(size, 0x41414141);
f(v);
}
|
1
2
3
4
5
6
7
8
9
10
| // g++ -o container-3 container-3.cpp
#include <algorithm>
#include <vector>
int main() {
//std::vector<int> v(10);
std::vector<int> v;
std::fill_n(v.begin(), 10, 0x42);
}
|
Segmentation fault (core dumped)로 강종된다.
크기할당이 없지만 없는 자리에 고정값을 채우기에 오버플로우가 일어난다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // g++ -o container-5 container-5.cpp -std=c++11
#include <iostream>
#include <vector>
void f(const std::vector<int> &c) {
// for(auto i = c.begin(), e = i + c.size(); i != e; ++i) {
for(auto i = c.begin(), e = i + 20; i != e; ++i) {
std::cout << *i << std::endl;
}
}
int main(void) {
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
f(v);
}
|
out of range
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // g++ -o container-7 container-7.cpp
#include <vector>
#include <iostream>
//void insert_table(std::vector<int> &table, unsigned long long pos, int value) {
void insert_table(std::vector<int> &table, long long pos, int value) {
if(pos >= (long long)table.size()){
std::cout << "overflow!" << std::endl;
return;
}
table[pos] = value;
}
int main(void){
long long idx;
std::vector<int> v(5);
std::cin >> idx;
insert_table(v, idx, 0x41414141);
}
|
pos에 음수가 들어오게 할시에 덮어 쓸 수 있음
Uninitialized Value
1
2
3
4
5
6
| // g++ -o c-1 uninit-1.c
#include <iostream>
int main(void) {
int i;
std::cout << i;
}
|
초기화 하지않은 i출력
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| //g++ -o uninit-2 uninit-2.cpp
#include <iostream>
class S {
int c;
public:
int f(int i) const { return i + c; }
};
int f() {
S s;
return s.f(10);
}
int main(void) {
int val = f();
std::cout << val << std::endl;
}
|
초기화하지 않은 멤버 변수 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // g++ -o uninit-3 uninit-3.cpp
#include <iostream>
class S {
public:
S() : mem(0), mem_size(0) { }
S(int mem_size) {
if(mem_size > 0){
this->mem = new char[mem_size];
this->mem_size = mem_size;
}
}
char *mem;
int mem_size;
};
int main(void){
S s(-1);
std::cout << s.mem << std::endl;
}
|
S가 mem_size가 0보다 클때만 할당하는데 작은값이 들어갔을때는 값들이 쓰레기로 나돌아다닌다. 후에 접근가능할시에 잠재적 위험이 있을 수 있다.
Use-After-Free
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // g++ -o uaf-1 uaf-1.cpp
#include <iostream>
struct S {
void f();
};
int main(void) {
S *s = new S;
// ...
delete s;
// ...
s->f();
}
|
해제된 메모리에 접근
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // g++ -o uaf-2 uaf-2.cpp
#include <string>
#include <iostream>
std::string str_func(){
std::string a = "aaaa";
return a;
}
void display_string(const char * buf){
std::cout << buf << std::endl;
}
int main(void) {
const char *str = str_func().c_str();
display_string(str);
std::string b = "bbbb"; //uaf
display_string(str); /* Undefined behavior */
}
|
str_func을 return 할때 지역변수인 a는 메모리가 해제된다. 따라서 str은 해제된 a를 가르켜 첫번째 출력에서 aaaa가 출력되고 후에 a와 같은 주소를 가르키는 b에서 “bbbb”로 초기화시키고 str을 출력했을때 bbbb가 나온다.
1
2
3
4
5
6
7
| // g++ -o smart-1 smart-1.cpp -std=c++11
#include <memory>
int main() {
int* i = new int;
std::shared_ptr<int> p1(i);
std::shared_ptr<int> p2(i);
}
|
같은 메모리를 서로 다른 두 개의 스마트 포인터가 가리키게 해서는 안 된다
여기에선 i를 두번해제한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // g++ smart-2 smart-2.cpp
#include <iostream>
#include <memory>
struct B {
virtual ~B() = default; // polymorphic object
// ...
};
struct D : B {};
void g(std::shared_ptr<D> derived){
std::cout << "hi im g!" << std::endl;
};
int main(void) {
std::shared_ptr<B> poly(new D);
// ...
g(std::shared_ptr<D>(dynamic_cast<D *>(poly.get())));
}
|
인자로넘겨주면서 dynamic_cast로 인해 shared_ptr이 새로 생성되어 인자로 넘겨지는데 이과정에서 poly의 메모리가 해제된다 따라서 두번해제되는 버그가 생김
1
2
3
4
5
6
| // g++ -o smart-3 smart-3.cpp -std=c++11
#include <memory>
int main() {
std::shared_ptr<int> p1 = std::make_shared<int>();
std::shared_ptr<int> p2(p1);
}
|
smart-1에 대한 해결책
얕은 복사
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| // g++ shallow_copy shallow_copy.cpp
#include <iostream>
#include <cstring>
class A{
public:
A(int _age, char *_name) {
age = _age;
name = new char [strlen(_name)+1];
strcpy(name,_name);
}
A(const A& s)
{
age = s.age;
name = s.name;
}
int age;
char *name;
};
int main(void){
A st1(20, "dreamhack");
A st2(st1);
std::cout << st1.name << std::endl;
std::cout << st2.name << std::endl;
strcpy(st1.name, "hackdream"); //modify st1 name
std::cout << st1.name << std::endl;
std::cout << st2.name << std::endl;
}
|
깊은복사
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| // g++ deep_copy deep_copy.cpp
#include <iostream>
#include <cstring>
class A{
public:
A(int _age, char *_name) {
age = _age;
name = new char[strlen(_name)+1];
strcpy(name,_name);
}
A(const A& s)
{
age = s.age;
name = new char[strlen(s.name)+1];
strcpy(name,s.name);
}
int age;
char *name;
};
int main(void){
A st1(20, "dreamhack");
A st2(st1);
std::cout << st1.name << std::endl;
std::cout << st2.name << std::endl;
strcpy(st1.name, "hackdream"); //modify st1 name
std::cout << st1.name << std::endl;
std::cout << st2.name << std::endl;
}
|
vector에서 class나 포인터를 사용할때도 마찮가지
Type Confusion
변수나 객체를 선언 혹은 초기화되었을 때와 다른 타입으로 사용할 때 발생하는 취약점
1
2
3
4
5
6
7
| //gcc -o type_confusion1 type_confusion1.c
#include <stdio.h>
int main(void){
int val;
scanf("%d", &val);
puts(val);
}
|
puts는 char *를 인자로 받는데 int형을 넘겨버림 Segmentation fault 발생
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| // g++ -o wrong_cast wrong_cast.cpp
#include <iostream>
#include <string.h>
using namespace std;
class Parent
{
public:
};
class Print: public Parent
{
public:
virtual void print_str(char *str) {
cout << str << endl;
}
~Print() {
}
};
class Read: public Parent
{
public:
virtual void read_str(char *str) {
cout << "Input: " << str << endl;
cin >> str;
cout << "Data: " << str << endl;
}
~Read() {
}
};
int main()
{
Parent *p1 = new Print();
Parent *p2 = new Read();
Print *b1;
char buf[256];
strcpy(buf, "I'm print_str");
b1 = static_cast<Print*>(p1);
b1->print_str(buf);
b1 = static_cast<Print*>(p2);
b1->print_str(buf);
return 0;
}
|
String in C++
1
2
3
4
5
6
7
8
9
10
11
12
| // g++ -o string-leak string-leak.cpp
#include <iostream>
#include <fstream>
int main(void) {
char buffer[32];
std::ifstream is ("test.txt", std::ifstream::binary); //test.txt have 32bytes of "a"
try {
is.read(buffer, sizeof(buffer));
} catch (std::ios_base::failure &e) {
// Error handling
}
std::cout << buffer << std::endl;
|
id.read는 끝 널 처리를 안해준다 따라서 문자열로 출력시 뒤에가 릭이 발생
1
2
3
4
5
6
7
8
9
10
| // g++ -o string-null string-null.cpp
#include <cstdlib>
#include <string>
#include <iostream>
int main(void) {
std::string tmp(std::getenv("TMP"));
if(!tmp.empty()) {
std::cout << tmp << std::endl;
}
}
|
TMP가 비어있을시에 string은 null을 가지며 null dereference가 발생