Notice
Recent Posts
Recent Comments
Link
«   2025/12   »
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
Archives
Today
Total
관리 메뉴

HW_chick hacker

과유불급 문제 - CTF문제 본문

Activity/Hawkis

과유불급 문제 - CTF문제

{{HW}} 2023. 6. 7. 03:28

스택 버퍼오버플로우를 이용한 CTF문제를 풀려면 스택 프레임 구조를 파악하고 알고 있어야 한다.

 


스택이란?

Stack : Last in First out (LIFO)를 기반으로 작동하는 자료구조. push(), pop() 등의 함수를 사용하여 조작

스택 영역은 함수의 호출과 함께 할당됩니다. 함수가 끝나면 자연스럽게 사라지죠.

지역 변수, 매개 변수, 반환값(Return)들이 이 곳에 저장됩니다.

특징이 있다면 스택은 높은 주소에서 낮은 주소로 커지는데, 그 이유는 운영 체제의 핵심인 Kernel을 절대로 침범할 수 없게 하기 위해서입니다.

스택에서 꼭 알아야 할 두 가지 레지스터가 있는데, ebp esp라는 녀석입니다.

ebp는 베이스 포인터입니다. 스택에서 제일 낮은 위치(메모리 상에선 가장 높은 주소)를 가리킵니다.

esp는 스택 포인터입니다. 스택이 지금 어디에 있는지를 가리킵니다.

 


스택 프레임이란?

Stack Frame : 스택 영역에 저장되는 함수의 정보, 공간

어떤 함수던 실행될 때 스택 프레임을 생성합니다.

그 안에는 매개 변수, Return, 지역 변수들이 자리잡고 있죠.

자, 함수가 호출되었다!! 라고 생각해 보겠습니다.

우선 스택 프레임을 만드는 단계인 프롤로그가 진행됩니다,

push ebp
mov ebp, esp

 

베이스 포인터를 스택에 저장 후 스택 포인터를 베이스 포인터에 저장합니다.

이제 함수의 역할이 끝났으니 스택 프레임이 소멸되는 단계가 필요합니다. 이를 에필로그라고 해요.

mov esp, ebp
pop ebp

위 명령어로 인해 함수의 역할이 끝난 후 위 그림과 같은 상태로 돌아가는 겁니다.

pop 명령을 통해 RET 으로 이동하겠죠.

이제 어셈블리어를 뜯어봤을 때 프롤로그와 에필로그가 보인다!! 하면 함수의 시작과 끝이라고 생각하시면 됩니다.

 


버퍼 오버플로우

버퍼 오버플로우 취약점은 스택 프레임 내에서 선언된 지역 변수가 다른 영역을 침범해 발생합니다.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}
void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(30);
}

void read_me(){
        system("/bin/sh");
}

void food() {
        puts("do you like fastfood?");
        puts("choose!");
        puts("[h]amberger");
        puts("[p]izza");
        puts("[c]hiken");
        printf("> ");
}

int main(int argc, char *argv[])
{
        char name[0x50] = {};
        char choose[2] = {};
        initialize();
        while(1) {
                food();
                read(0, choose, 2);
                switch(choose[0]){
                        case 'h':
                                printf("i like hamberger!");
                                break;
                        case 'p':
                                printf("what do you want?");
                                read(0,name,79);
                                break;
                        case 'c':
                                printf("i don't like chiken.\n");
                                printf("pick another one: ");
                                read(0,name,0x100);
                                return 0;
                        default:
                                break;
                }
        }
}

코드를 보면 main함수에서 name은 0x50인 80바이트이다.

64비트로 되어 있어 80바이트+8(sfp)+실행흐름 바꿀 주소로 만들어 파이썬코드를 짜서 익스플로어 할겁니다.

case 'c'를 선택하여 버퍼주소를 넣어서 버퍼오버플로우를 할 것이다.

read_me 함수를 볼 때 system@plt에 /bin/sh가 존재 한다.

#!/bin/bash 란? 쉘스크립트 작성 시 제일 먼저 #!/bin/bash 를 기재하는 이유는 해당 파일을 bash 쉘로 실행시키겠다는 의미이다.,

gdb로 main함수를 뜯어 보겠다.

 

위 두줄을 볼 떄 프롤로그를 확인할수 있다.

read_me를 확인할수 없지만 disas main을 확인 후 쉘 코드 실행시키는 함수를 read_me함수를 확인해 보겠다.

 

버퍼의 공간이 read_me함수에 read_me<+0> 주소값 0x401214이므로 ret 공간에 주소를 채워봐야한다.

pwntool을 이용하여 파이썬코드를 짜서 익스시킬 것인데 0x401214 주소를 ret공간에 흐름을 바꿀 주소를 넣으면 문제가 생긴다. 나도 모르는 문제지만 주소에 1 이상을 더하면 익스 할 때 정상적으로 돌아간다 -.- ;;;

그래서 read_me<+4> 주소를 가져와 버퍼시켰다.,

 

파이썬 코드는 먼저 case 'c'를 선택 창에 두고 c코드를 보면 name 80바이트 이상인 name을 0x100를 read하겠다고 볼 수 있다. 먼저 sendline을 이용하여 'c'를 통해 다음 단계로 넘어가고 'a' 값을 88바이트(80바이트 + 8(SFP)) + p64 비트인 ret공간 주소를 넣어 payload문을 작성하여 익스하였다.

 

익스 후 해당 flag값이 있는 경로를 찾아가서 flag값을 찾으면 끝난다.,

 

 


마무리

우리 동아리 "협곡의 살모사"님이 만든 문제인데 

저로써는 포너블이 거의 처음이라 파악하고 공부하고 푸는데 오래 걸렸다. 

"협곡의 살모사"님 문제좀 살살 내주세요 -.-;;;