RE CTFd CMU x86 writeup
はじめに
以下の常設CTFに掲載されているCMU x86の問題をPhase1~6まで解いたので,未来の自分のためにもwriteupを残しておきます.
- https://reversing.ctfd.io
- Cutterを使用.
- 勉強のためにアセンブラを読むため,デコンパイラ機能は不使用.
Phase 1
Phase 1では,はじめに文字列Public speaking is very easy.
が定義される.
次に,後述するstrings_not_equal
で,入力された文字列と上述の文字列とが等しくないかを判定している.等しくない場合,戻り値1が返されることで,explode_bomb
が実行して失敗する.
そのため,そのまま上述の文字列を入力すればクリアになる.
回答は Public speaking is very easy. である.
コード
;-- phase_1:
gcc2_compiled.(int32_t arg_4h);
; arg int32_t arg_4h @ stack + 0x4
0x08048b20 push ebp
0x08048b21 mov ebp, esp
0x08048b23 sub esp, 8
0x08048b26 mov eax, dword [arg_4h]
0x08048b29 add esp, 0xfffffff8
0x08048b2c push str.Public_speaking_is_very_easy. ; 0x80497c0 ; int32_t arg_8h
0x08048b31 push eax ; int32_t arg_4h ; ユーザの入力した文字列
0x08048b32 call strings_not_equal ; sym.strings_not_equal
0x08048b37 add esp, 0x10
0x08048b3a test eax, eax ; 文字列が等しい場合,eax=0なのでジャンプ
0x08048b3c je 0x8048b43
0x08048b3e call explode_bomb ; sym.explode_bomb
0x08048b43 mov esp, ebp
0x08048b45 pop ebp
0x08048b46 ret
0x08048b47 nop
string_not_equal
本関数は,2つの文字列の先頭ポインタを引数で受け取っている.
後述のstring_length
で,それぞれの文字列長を求め,ebx
とeax
に格納し,同じ長さであることを確認している.
次のループでは,文字列の各文字を比較している.
- 文字を比較する.等しければループを続行し,等しくなければ戻り値1を設定して関数を終了する.
- 文字列の位置を指すポインタを1増加し,次の文字を参照する.
よって,本関数は与えられた2つの文字列が一致した場合0を,不一致の場合1を返す関数である.
コード
strings_not_equal(int32_t arg_4h, int32_t arg_8h);
; var int32_t var_1ch @ stack - 0x1c
; arg int32_t arg_4h @ stack + 0x4
; arg int32_t arg_8h @ stack + 0x8
0x08049030 push ebp
0x08049031 mov ebp, esp
0x08049033 sub esp, 0xc
0x08049036 push edi
0x08049037 push esi
0x08049038 push ebx
0x08049039 mov esi, dword [arg_4h] ; 第1引数の文字列の先頭ポインタ
0x0804903c mov edi, dword [arg_8h] ; 第2引数の文字列の先頭ポインタ
0x0804903f add esp, 0xfffffff4
0x08049042 push esi ; int32_t arg_4h
0x08049043 call string_length ; sym.string_length
0x08049048 mov ebx, eax ; 文字列長をebxに格納
0x0804904a add esp, 0xfffffff4
0x0804904d push edi ; int32_t arg_4h
0x0804904e call string_length ; sym.string_length
0x08049053 cmp ebx, eax ; 文字列長を比較
0x08049055 je 0x8049060
0x08049057 mov eax, 1 ; 文字列長が不一致の場合,戻り値1として関数終了
0x0804905c jmp 0x804907f
0x0804905e mov esi, esi
0x08049060 mov edx, esi ; 第1引数の文字列の先頭ポインタ
0x08049062 mov ecx, edi ; 第2引数の文字列の先頭ポインタ
0x08049064 cmp byte [edx], 0 ; NUL文字で終了
0x08049067 je 0x804907d
0x08049069 lea esi, [esi]
0x08049070 mov al, byte [edx]
0x08049072 cmp al, byte [ecx] ; 文字を比較して不等の場合,戻り値1として関数終了
0x08049074 jne 0x8049057
0x08049076 inc edx ; 第1引数の文字列を指すポインタを1増加
0x08049077 inc ecx ; 第2引数の文字列を指すポインタを1増加
0x08049078 cmp byte [edx], 0 ; NUL文字で終了
0x0804907b jne 0x8049070
0x0804907d xor eax, eax ; 戻り値0
0x0804907f lea esp, [var_1ch]
0x08049082 pop ebx
0x08049083 pop esi
0x08049084 pop edi
0x08049085 mov esp, ebp
0x08049087 pop ebp
0x08049088 ret
0x08049089 lea esi, [esi]
string_length
まず,引数(arg_4h
)は文字列の先頭ポインタを受け取る.
次に,戻り値eax
を0で初期化する.戻り値eax
は引数の文字列長である.
1文字目がNUL文字の場合は関数の後方までジャンプし,0を返して終了する.
それ以外の場合,ループが実行される.
- 文字列の位置を指すポインタ
edx
を1増加させる. - 戻り値であり,文字列長の
eax
を1増加させる. -
edx
のポインタが指す値がNUL文字ならループを終了する
よって,本関数は引数で与えられた文字列の長さを返す関数である.
コード
string_length(int32_t arg_4h);
; arg int32_t arg_4h @ stack + 0x4
0x08049018 push ebp
0x08049019 mov ebp, esp
0x0804901b mov edx, dword [arg_4h] ; 引数の文字列の先頭ポインタ
0x0804901e xor eax, eax ; 文字列の長さ,初期値0
0x08049020 cmp byte [edx], 0 ; \0であれば関数終了
0x08049023 je 0x804902c
0x08049025 inc edx ; 次の文字を参照
0x08049026 inc eax ; 文字列の長さ+1
0x08049027 cmp byte [edx], 0 ; \0であれば関数終了
0x0804902a jne 0x8049025
0x0804902c mov esp, ebp
0x0804902e pop ebp
0x0804902f ret
Phase 2
はじめにread_six_numbers
が実行され,ユーザが入力した6つの整数を配列に格納する.
まず,配列の0番目に格納された値は1でないと,explode_bomb
が実行される.
次に,ebx
をループ変数とするループが実行される.
-
eax
にebx+1
が代入される. -
eax
と配列のebx-1
番目の値を乗算する. - 乗算結果が配列の
ebx
番目と等しいなら,ループする.等しくなければexplode_bomb
が実行される.
配列[] = {A, B, C, D, E, F};
とする.
ebx |
eax |
x |
ebx-1 番目の数 |
= |
ebx 番目の数 |
---|---|---|---|---|---|
1 | 2 | x | A | = | B |
2 | 3 | x | B | = | C |
3 | 4 | x | C | = | D |
4 | 5 | x | D | = | E |
5 | 6 | x | E | = | F |
配列の0番目Aは1であることが分かっているため,残りのB~Fを求めると以下の通りである.
ebx |
eax |
x |
ebx-1 番目の数 |
= |
ebx 番目の数 |
---|---|---|---|---|---|
1 | 2 | x | 1 | = | 2 |
2 | 3 | x | 2 | = | 6 |
3 | 4 | x | 6 | = | 24 |
4 | 5 | x | 24 | = | 120 |
5 | 6 | x | 120 | = | 720 |
よって,回答は1 2 6 24 120 720である.
read_six_numbers
read_six_numbers
にはユーザの入力した文字列と,配列の先頭ポインタ(var_8h
)が渡される.
次に,sscanf
を実行しており,入力された数字を配列の各要素に格納している.
sscanf
は戻り値に変換できた数を返すため,6つ変換できたかを確認している.
コード
read_six_numbers(const char *s, int arg_8h);
; arg const char *s @ stack + 0x4
; arg int arg_8h @ stack + 0x8
0x08048fd8 push ebp
0x08048fd9 mov ebp, esp
0x08048fdb sub esp, 8
0x08048fde mov ecx, dword [s]
0x08048fe1 mov edx, dword [arg_8h]
; read_six_numbers("入力された文字列", numbers);
; sscanf("入力された文字列", "%d %d %d %d %d %d", (省略));
; 左から順にedx, edx+0x4, ..., edx+14
0x08048fe4 lea eax, [edx + 0x14]
0x08048fe7 push eax
0x08048fe8 lea eax, [edx + 0x10]
0x08048feb push eax
0x08048fec lea eax, [edx + 0xc]
0x08048fef push eax
0x08048ff0 lea eax, [edx + 8]
0x08048ff3 push eax
0x08048ff4 lea eax, [edx + 4]
0x08048ff7 push eax
0x08048ff8 push edx
0x08048ff9 push str.d__d__d__d__d__d ; 0x8049b1b ; const char *format
0x08048ffe push ecx ; const char *s
0x08048fff call sscanf ; sym.imp.sscanf ; int sscanf(const char *s, const char *format, va_list args)
0x08049004 add esp, 0x20
0x08049007 cmp eax, 5 ; 変換できた数が5つより大きい(6つ読み取れた)場合はbombが爆発しない
0x0804900a jg 0x8049011
0x0804900c call explode_bomb ; sym.explode_bomb
0x08049011 mov esp, ebp
0x08049013 pop ebp
0x08049014 ret
0x08049015 lea esi, [esi]
sscanf("入力された文字列", "%d %d %d %d %d %d", 配列の0番目のアドレス,配列の1番目のアドレス, ..., 配列の5番目のアドレス);
Phase 3
Phase 3ではsscanf
で,整数,文字,整数を読み取っている.順に,[var_10h]
,[var_9h]
,[var_8h]
に格納される.
次に,1つ目の整数が7より大きい場合は0x0804 8bd6
番地にジャンプする.
7以下の場合eax
に[var_10h]
が格納され,jmp dowrd [eax*4 + data.080597e8]
でジャンプする.data.080597e8
にはアドレス値が連続で格納されており,1つ目の整数によってジャンプ先が変わる仕掛けになっている.これはswitch-case文の仕組みである.
1つ目の整数が0の場合(Cだとcase 0:
)の場合である.
-
bl
に文字qを格納する. - 1つ目の整数が777と等しいかチェックする.
他も同様の事を実施しているので省略する.
最後にbl
と中央の文字が等しいかをチェックしている.
よって,回答は以下のいずれか各行である.(例えば,0 q 777)
1つ目の整数 | 中央の文字 | 2つ目の整数 | 2つ目の整数(10進数) |
---|---|---|---|
0 | q | 0x309 | 777 |
1 | b | 0xd6 | 214 |
2 | b | 0x2f3 | 755 |
3 | k | 0xfb | 251 |
4 | o | 0xa0 | 160 |
5 | t | 0x1ca | 458 |
6 | v | 0x30c | 780 |
7 | b | 0x20c | 524 |
コード
; var int32_t var_1ch @ stack - 0x1c
; var int var_10h @ stack - 0x10
; var char var_9h @ stack - 0x9
; var uint_fast32_t var_8h @ stack - 0x8
; arg const char *s @ stack + 0x4
0x08048b98 push ebp
0x08048b99 mov ebp, esp
0x08048b9b sub esp, 0x14
0x08048b9e push ebx
0x08048b9f mov edx, dword [s]
0x08048ba2 add esp, 0xfffffff4
0x08048ba5 lea eax, [var_8h]
0x08048ba8 push eax
0x08048ba9 lea eax, [var_9h]
0x08048bac push eax
0x08048bad lea eax, [var_10h]
0x08048bb0 push eax
0x08048bb1 push str.d__c__d ; 0x80497de ; const char *format
0x08048bb6 push edx ; const char *s
0x08048bb7 call sscanf ; sym.imp.sscanf ; int sscanf(const char *s, const char *format, va_list args)
0x08048bbc add esp, 0x20
0x08048bbf cmp eax, 2 ; 2
0x08048bc2 jg 0x8048bc9
0x08048bc4 call explode_bomb ; sym.explode_bomb
0x08048bc9 cmp dword [var_10h], 7 ; 1つ目の整数が7の場合,default
0x08048bcd ja case.default.switch.0x08048bd6
0x08048bd3 mov eax, dword [var_10h]
;-- switch
0x08048bd6 jmp dword [eax*4 + data.080497e8] ; 0x80497e8 ; switch table (8 cases) at 0x80497e8
0x08048bdd lea esi, [esi]
;-- case 0: ; from 0x8048bd6
0x08048be0 mov bl, 0x71 ; blに文字qを代入
0x08048be2 cmp dword [var_8h], 0x309 ; 2つ目の整数が777かチェック
0x08048be9 je 0x8048c8f
0x08048bef call explode_bomb ; sym.explode_bomb
0x08048bf4 jmp 0x8048c8f ; sym.phase_3+0xf7
0x08048bf9 lea esi, [esi]
;-- case 1: ; from 0x8048bd6
(省略)
0x08048c8f cmp bl, byte [var_9h] ; 中央の文字が各case文でblに格納した文字と等しいかチェック
0x08048c92 je 0x8048c99
0x08048c94 call explode_bomb ; sym.explode_bomb
0x08048c99 mov ebx, dword [var_1ch]
0x08048c9c mov esp, ebp
0x08048c9e pop ebp
0x08048c9f ret
Phase 4
Phase 4ではssancf
で整数を1つ読み込んでいる.整数が2つ以上入力されたり,整数が0以下の場合はexplode_bomb
が実行されて失敗する.
読み込んだ整数は,func4
の引数に渡されている.そして戻り値が55(=0x37)であればクリアである.
func4
はフィボナッチ数を求める関数であるため,Phase 4の回答は9である.
※func4
の動作がメインのためphase_4
の内容は省略.
func4
func4
は自分自身をcallしており,再帰関数であることがわかる.
- 引数
ebx
が1以下の場合,戻り値として1を返す. - 引数
ebx
が1より大きい場合,1引いた値を自分自身に渡している.さらに,2引いた値を自分自身に渡している.
それぞれの戻り値はesi
とeax
に格納され,最後に加算している.
従って,func4
は以下の通り表せる.
すなわちfunc4
はフィボナッチ数を求める関数である.
※ 0, 1, 1, ...ではなく1, 2, 3, ...から始まる.
コード
func4(va_list arg_4h);
; var int32_t var_1ch @ stack - 0x1c
; arg va_list arg_4h @ stack + 0x4
0x08048ca0 push ebp
0x08048ca1 mov ebp, esp
0x08048ca3 sub esp, 0x10
0x08048ca6 push esi
0x08048ca7 push ebx
0x08048ca8 mov ebx, dword [arg_4h]
0x08048cab cmp ebx, 1 ; 引数が1以下の時,1を返す
0x08048cae jle 0x8048cd0
0x08048cb0 add esp, 0xfffffff4
; esi = func4(引数-1)
0x08048cb3 lea eax, [ebx - 1]
0x08048cb6 push eax ; va_list arg_4h
0x08048cb7 call func4
0x08048cbc mov esi, eax
0x08048cbe add esp, 0xfffffff4
; eax = func4(引数-2)
0x08048cc1 lea eax, [ebx - 2]
0x08048cc4 push eax ; va_list arg_4h
0x08048cc5 call func4
; eax + esi = func4(引数-2) + func4(引数-1)
0x08048cca add eax, esi
0x08048ccc jmp 0x8048cd5
0x08048cce mov esi, esi
0x08048cd0 mov eax, 1
0x08048cd5 lea esp, [var_1ch]
0x08048cd8 pop ebx
0x08048cd9 pop esi
0x08048cda mov esp, ebp
0x08048cdc pop ebp
0x08048cdd ret
0x08048cde mov esi, esi
Phase 5
はじめにstring_length
で入力された文字列長が6と等しいかチェックしている.
続いてisrveawhobpnutfg
という文字列が定義される.
さらにedx
をループ変数とするループで次の順番に処理を行っている.
- 入力された文字を取り出して
al
に格納する. - 文字と0xfとのAND演算を行う(
al &= 0xf
). -
isrveawhobpnutfg
のal
番目の文字を,var_ch(ecx)
のポインタに順番に格納する.
最後に,var_ch(ecx)
の指す文字列がgiants
と等しければクリアとなる.
入力値(al ) |
al &= 0xf |
isrv... のN番目 |
isrv... のN番目の文字 |
---|---|---|---|
'0' | al &= 0xf |
0 | i |
'1' | al &= 0xf |
1 | s |
al &= 0xf |
2 | r | |
al &= 0xf |
3 | v | |
al &= 0xf |
4 | e | |
'5' | al &= 0xf |
5 | a |
al &= 0xf |
6 | w | |
al &= 0xf |
7 | h | |
al &= 0xf |
8 | o | |
al &= 0xf |
9 | b | |
al &= 0xf |
10 | p | |
';' | al &= 0xf |
11 | n |
al &= 0xf |
12 | u | |
'=' | al &= 0xf |
13 | t |
al &= 0xf |
14 | f | |
'?' | al &= 0xf |
15 | g |
よって,回答を ?05;=1 とすることでisrv...
からgiants
を取り出せる.
コード
phase_5(int32_t arg_4h);
; var int32_t var_1ch @ stack - 0x1c
; var int32_t var_ch @ stack - 0xc
; var int32_t var_6h @ stack - 0x6
; arg int32_t arg_4h @ stack + 0x4
0x08048d2c push ebp
0x08048d2d mov ebp, esp
0x08048d2f sub esp, 0x10
0x08048d32 push esi
0x08048d33 push ebx
0x08048d34 mov ebx, dword [arg_4h] ; 入力された文字列の先頭アドレス
0x08048d37 add esp, 0xfffffff4
0x08048d3a push ebx ; int32_t arg_4h
0x08048d3b call string_length ; sym.string_length
0x08048d40 add esp, 0x10
0x08048d43 cmp eax, 6 ; 入力された文字列が6文字かチェック
0x08048d46 je 0x8048d4d
0x08048d48 call explode_bomb ; sym.explode_bomb
0x08048d4d xor edx, edx ; ループ変数 edx = 0
0x08048d4f lea ecx, [var_ch] ; ecx = var_ch(ある配列の先頭アドレス)
0x08048d52 mov esi, array.123 ; 文字列"isrveawhobpnutfg"の先頭アドレス
0x08048d57 mov al, byte [edx + ebx] ; 入力された文字列のedx番目の文字をalに格納
0x08048d5a and al, 0xf ; al &= 0xf
0x08048d5c movsx eax, al ; eax = al
0x08048d5f mov al, byte [eax + esi] ; 文字列"isr..."の先頭からal(eax)番目の文字をalに格納
0x08048d62 mov byte [edx + ecx], al ; ある配列var_chに順番に格納
0x08048d65 inc edx ; edx++
0x08048d66 cmp edx, 5 ; 5 ; 6回ループ(0から5)
0x08048d69 jle 0x8048d57
; ある配列に格納した文字列が,giantsと等しければクリア
0x08048d6b mov byte [var_6h], 0
0x08048d6f add esp, 0xfffffff8
0x08048d72 push str.giants ; 0x804980b ; int32_t arg_8h
0x08048d77 lea eax, [var_ch]
0x08048d7a push eax ; int32_t arg_4h
0x08048d7b call strings_not_equal ; sym.strings_not_equal
0x08048d80 add esp, 0x10
0x08048d83 test eax, eax
0x08048d85 je 0x8048d8c
0x08048d87 call explode_bomb ; sym.explode_bomb
0x08048d8c lea esp, [var_1ch]
0x08048d8f pop ebx
0x08048d90 pop esi
0x08048d91 mov esp, ebp
0x08048d93 pop ebp
0x08048d94 ret
0x08048d95 lea esi, [esi]
Phase 6
Phase 6は長いため分割する.
値のチェック
はじめにread_six_numbers
で6つの整数を読み取っている.次の二重ループで値の整合性をチェックしている.
- 外側のループ開始
- 整数を順に参照し,1減じた値が5以下であることをチェック
- 6回分ループしたら内側のループをスキップ
- 内側のループ開始
- 外側のループで参照している整数が,他の整数と異なることをチェック
- 内側のループ終了
- 外側のループ終了
つまり,入力値は6以下かつ他の入力値と異なる必要がある.
コード
phase_6(int32_t arg_4h);
; var int32_t var_5ch @ stack - 0x5c
; var int32_t var_40h @ stack - 0x40
; var int32_t var_3ch @ stack - 0x3c
; var int32_t var_38h @ stack - 0x38
; var int32_t var_34h @ stack - 0x34
; var int32_t var_1ch @ stack - 0x1c
; arg int32_t arg_4h @ stack + 0x4
0x08048d98 push ebp
0x08048d99 mov ebp, esp
0x08048d9b sub esp, 0x4c
0x08048d9e push edi
0x08048d9f push esi
0x08048da0 push ebx
0x08048da1 mov edx, dword [arg_4h]
0x08048da4 mov dword [var_38h], 0x804b26c ; obj.node1
0x08048dab add esp, 0xfffffff8
0x08048dae lea eax, [var_1ch]
0x08048db1 push eax ; int arg_8h ; 6つの整数を格納する配列の先頭アドレス
0x08048db2 push edx ; const char *s (ユーザが入力した文字列)
0x08048db3 call read_six_numbers ; sym.read_six_numbers
; edi = 0;
0x08048db8 xor edi, edi
0x08048dba add esp, 0x10
0x08048dbd lea esi, [esi]
; edi = 0; edi <= 5; edi++;
0x08048dc0 lea eax, [var_1ch] ; 6つの整数を格納する配列の先頭アドレス
0x08048dc3 mov eax, dword [eax + edi*4] ; edi番目の整数を順に取り出す
0x08048dc6 dec eax ; 整数を1減ずる
0x08048dc7 cmp eax, 5 ; 5 ; 5以下であれば爆発しない
0x08048dca jbe 0x8048dd1
0x08048dcc call explode_bomb ; sym.explode_bomb
0x08048dd1 lea ebx, [edi + 1] ; 内側のループの初期値
0x08048dd4 cmp ebx, 5 ; 5
0x08048dd7 jg 0x8048dfc
0x08048dd9 lea eax, [edi*4]
0x08048de0 mov dword [var_3ch], eax ; edi番目を[var_3ch]に格納
0x08048de3 lea esi, [var_1ch]
; ebx = edi + 1; edi <= 5; edi++;
0x08048de6 mov edx, dword [var_3ch] ; edi番目
0x08048de9 mov eax, dword [edx + esi] ; edi番目以降の値をループで取り出して
0x08048dec cmp eax, dword [esi + ebx*4] ; 等しくないことをチェック
0x08048def jne 0x8048df6
0x08048df1 call explode_bomb ; sym.explode_bomb
0x08048df6 inc ebx
0x08048df7 cmp ebx, 5 ; 5
0x08048dfa jle 0x8048de6
0x08048dfc inc edi
0x08048dfd cmp edi, 5 ; 5
0x08048e00 jle 0x8048dc0
ポインタ配列
続いて,ポインタ配列の先頭を指すvar_34h
を[var_40h]
に格納する.
最初のループでは,6つの整数を格納する配列を参照する.
- 外側のループ開始
-
[var_38h]
に格納されたアドレスをesi
に格納する. - 入力値が,1未満であれば内側のループをスキップ
- 内側のループ開始(入力値-1回ループ)
- 「
esi
のアドレスに8加えたアドレス」に格納された値をesi
に格納する.
- 「
- 内側のループ終了
-
[var_40h]
に格納したポインタ配列の先頭アドレスvar_34h
を参照し,esi
に格納したアドレスを格納する.
-
- 外側のループ終了
[var_38h]
の値は,phase_6
の冒頭0x08048da
行目にて,0x804b26c
の値が格納されている.
...
;-- node2:
0x0804b260 .dword 0x000002d5 ; esi ← esi(0x0804b260) + 8 (入力値が2の時)
0x0804b264 .dword 0x00000002
0x0804b268 .dword 0x0804b254 ; obj.node3
;-- node1:
0x0804b26c .dword 0x000000fd ; ← [var_38h]
0x0804b270 .dword 0x00000001
0x0804b274 .dword 0x0804b260 ; esi ← [var_38h] + 8 (入力値が1の時)
...
外側のループでは,6つの入力値を順に参照する.内側のループでは(入力値-1)回分だけループを繰り返し,esi+8
のアドレスを,以下の順に取り出す.
- 入力値が1の場合,内側のループは実行されない.
-
esi
=0x0804b26c
-
- 入力値が2の場合,内側のループは1回だけ実行される.
0x0804b274 = 0x0804b26c + 8
-
esi
=0x0804b274
に格納された値0x0804b260
- 入力値が3の場合,内側のループは2回だけ実行される.
0x0804b274 = 0x0804b26c + 8
-
esi
=0x0804b274
に格納された値0x0804b260
0x0804b268 = 0x0804b260 + 8
-
esi
=0x0804b268
に格納された値0x0804b254
最後に,[var_40h]
が先頭の配列にesi
に格納されたアドレスが指す値が格納される.まとめると次の表になる.
入力値 |
esi+8 の値 |
esi+8 に格納されたアドレス |
「esi+8 に格納されたアドレス」のアドレスが指す値 |
---|---|---|---|
1 | - | 0x0804b26c |
0x000000fd |
2 | 0x0804b268 |
0x0804b260 |
0x000002d5 |
3 | 0x0804b25c |
0x0804b254 |
0x0000012d |
4 | 0x0804b250 |
0x0804b248 |
0x000003e5 |
5 | 0x0804b244 |
0x0804b23c |
0x000000d4 |
6 | 0x0804b238 |
0x0804b230 |
0x000001b0 |
コード
0x08048e02 xor edi, edi ; edi = 0;
0x08048e04 lea ecx, [var_1ch] ; 入力配列の先頭アドレス
0x08048e07 lea eax, [var_34h] ; ポインタ配列の先頭アドレス
0x08048e0a mov dword [var_40h], eax ; [ver_40h]にポインタ配列の先頭アドレスを格納
0x08048e0d lea esi, [esi]
; edi = 0; edi <= 5; edi++;
0x08048e10 mov esi, dword [var_38h] ; esi = [var_38h]
0x08048e13 mov ebx, 1 ; ebx = 1
0x08048e18 lea eax, [edi*4] ; edi番目
0x08048e1f mov edx, eax
0x08048e21 cmp ebx, dword [eax + ecx] ; 「1 >= 入力配列のedi番目の値」ならループの直後にジャンプ
0x08048e24 jge 0x8048e38
0x08048e26 mov eax, dword [edx + ecx] ; 入力配列のedi番目の値
0x08048e29 lea esi, [esi]
; ebx = 1; ebx < 配列のedi番目の値; ebx++;
0x08048e30 mov esi, dword [esi + 8] ;
0x08048e33 inc ebx
0x08048e34 cmp ebx, eax
0x08048e36 jl 0x8048e30
0x08048e38 mov edx, dword [var_40h] ; アドレスvar_34hを格納
0x08048e3b mov dword [edx + edi*4], esi ; var_34hからedi番目に上のループの結果を格納
0x08048e3e inc edi
0x08048e3f cmp edi, 5 ; 5
0x08048e42 jle 0x8048e10
0x08048e44 mov esi, dword [var_34h] ; ポインタ配列の先頭に格納されたアドレス
0x08048e47 mov dword [var_38h], esi
0x08048e4a mov edi, 1 ; edi = 1
0x08048e4f lea edx, [var_34h] ; ポインタ配列の先頭
; edi = 1; edi <= 5; edi++;
0x08048e52 mov eax, dword [edx + edi*4] ; ポインタ配列から順にアドレスを取り出す
0x08048e55 mov dword [esi + 8], eax ; (アドレス+8)に格納
0x08048e58 mov esi, eax
0x08048e5a inc edi
0x08048e5b cmp edi, 5 ; 5
0x08048e5e jle 0x8048e52 ;
`[var_38h]`のアドレス
;-- node6:
0x0804b230 .dword 0x000001b0
0x0804b234 .dword 0x00000006
0x0804b238 .dword 0x00000000
;-- node5:
0x0804b23c .dword 0x000000d4
0x0804b240 .dword 0x00000005
0x0804b244 .dword 0x0804b230 ; obj.node6
;-- node4:
0x0804b248 .dword 0x000003e5
0x0804b24c .dword 0x00000004
0x0804b250 .dword 0x0804b23c ; obj.node5
;-- node3:
0x0804b254 .dword 0x0000012d
0x0804b258 .dword 0x00000003
0x0804b25c .dword 0x0804b248 ; obj.node4
;-- node2:
0x0804b260 .dword 0x000002d5
0x0804b264 .dword 0x00000002
0x0804b268 .dword 0x0804b254 ; obj.node3
;-- node1:
0x0804b26c .dword 0x000000fd ; ← var_38h
0x0804b270 .dword 0x00000001
0x0804b274 .dword 0x0804b260 ; obj.node2
判定
最後に,[var_38h]
の値をループで取り出している.そして,現在参照している値が次の値よりも大きいかを判定している.つまり,6つの整数を入力することで,先ほどの表の最右列の値を[var_38h]
の配列に格納し,その値が降順に並んでいることでクリアになる.
入力値 |
esi+8 の値 |
esi+8 に格納されたアドレス |
「esi+8 に格納されたアドレス」のアドレスが指す値 |
---|---|---|---|
1 | - | 0x0804b26c |
0x000000fd |
2 | 0x0804b268 |
0x0804b260 |
0x000002d5 |
3 | 0x0804b25c |
0x0804b254 |
0x0000012d |
4 | 0x0804b250 |
0x0804b248 |
0x000003e5 |
5 | 0x0804b244 |
0x0804b23c |
0x000000d4 |
6 | 0x0804b238 |
0x0804b230 |
0x000001b0 |
表より,以下の順序で入力すれば,値が降順に並ぶ.
入力配列 | 4 | 2 | 6 | 3 | 1 | 5 | |
---|---|---|---|---|---|---|---|
ポインタ配列 | 0x0804b248 |
0x0804b260 |
0x0804b230 |
0x0804b254 |
0x0804b26c |
0x0804b23c |
|
上のアドレスが指す値 | 0x03e5 |
0x02d5 |
0x01b0 |
0x012d |
0x00fd |
0x00d4 |
← 降順 |
よって,回答は4 2 6 3 1 5である.
コード
0x08048e60 mov dword [esi + 8], 0 ; 0
0x08048e67 mov esi, dword [var_38h] ; esi = dataポインタ
0x08048e6a xor edi, edi ; edi = 0;
0x08048e6c lea esi, [esi]
; edi = 0; edi <= 4; edi++;
0x08048e70 mov edx, dword [esi + 8] ; esi+8番目に格納された値
0x08048e73 mov eax, dword [esi] ; esi番目に格納された値
0x08048e75 cmp eax, dword [edx] ; esi+8番目のアドレスの値の方が大きいか?
0x08048e77 jge 0x8048e7e
0x08048e79 call explode_bomb ; sym.explode_bomb
0x08048e7e mov esi, dword [esi + 8]
0x08048e81 inc edi
0x08048e82 cmp edi, 4 ; 4
0x08048e85 jle 0x8048e70
0x08048e87 lea esp, [var_5ch] ;
0x08048e8a pop ebx
0x08048e8b pop esi
0x08048e8c pop edi
0x08048e8d mov esp, ebp
0x08048e8f pop ebp
0x08048e90 ret
0x08048e91 lea esi, [esi]
Discussion