Computational Recruiting

给了一个端口,可以先用nc连接上去拿到题目,提交答案可以得到flag

翻译后的题干:

您将获得一个包含 N = 200 个不同潜在候选人的文件。每个候选人都有 6 种不同的技能,每个技能的得分为 1 <= s <= 10。 计算其总体价值的公式为:

_score = round(6 * (int(s) * _weight)) + 10
overall_value = round(5 * ((health * 0.18) + (agility * 0.20) + (charisma * 0.21) + (knowledge * 0.08) + (energy * 0.17) + (resourcefulness * 0.16)))

注意:这里的 round() 函数是 Python 3 的 round(),它使用了一个称为 Banker’s Rounding 的概念 6 项技能的权重为:

health_weight = 0.2、agility_weight = 0.3、charisma_weight = 0.1、knowledge_weight = 0.05、energy_weight = 0.05、resourcefulness_weight = 0.3

按总体价值最高的顺序输入前 14 名候选人。 像这样输入:Name_1 Surname_1 - score_1,Name_2 Surname_2 - score_2,…,Name_i Surname_i - score_i 例如 Timothy Pempleton - 94,Jimmy Jones - 92,Randolf Ray - 92,…

数据文本像是:

First Name Last Name Health Agility Charisma Knowledge Energy Resourcefulness
Alis Reeson 2 5 5 8 7 10
Gerri Bielfelt 8 9 3 8 5 9

code:

import socket

def calculate_skill_score(skill_score, skill_weight):
return round(6 * (int(skill_score) * skill_weight)) + 10

def calculate_overall_value(health, agility, charisma, knowledge, energy, resourcefulness):
health_weight = 0.2
agility_weight = 0.3
charisma_weight = 0.1
knowledge_weight = 0.05
energy_weight = 0.05
resourcefulness_weight = 0.3

overall_value = round(5 * (
(calculate_skill_score(health, health_weight) * 0.18) +
(calculate_skill_score(agility, agility_weight) * 0.20) +
(calculate_skill_score(charisma, charisma_weight) * 0.21) +
(calculate_skill_score(knowledge, knowledge_weight) * 0.08) +
(calculate_skill_score(energy, energy_weight) * 0.17) +
(calculate_skill_score(resourcefulness, resourcefulness_weight) * 0.16)
))
return overall_value

def get_flag(answer):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as a:
a.connect(('94.237.59.199', 54323))
response = a.recv(1024)
print("Received:", response.decode())
a.sendall(answer + b'\n')
response = a.recv(1024)
print("Response:", response.decode())

candidates = []
with open('data.txt', 'r', encoding='utf-8') as file:
for line in file:
parts = line.strip().split() # 使用空格来分割行
name, surname, *scores = parts # *scores表示part列表剩下的元素
scores = list(map(int, scores)) # 转换技能分数为整数
overall_value = calculate_overall_value(*scores)
candidates.append((name, surname, overall_value))

# 对候选人按总体价值排序
sorted_candidates = sorted(candidates, key=lambda x: x[2], reverse=True) # key参数表示一个匿名函数,将canadidates的值传给x,并取第三个元素作为排序值降序排序
output = ', '.join([f"{name} {surname} - {score}" for name, surname, score in sorted_candidates[:14]])
get_flag(output.encode())

A Nightmare On Math Street

┌──(mikannse㉿kali)-[~]
└─$ nc 94.237.59.199 59897

#####################################################################
# #
# I told you not to fall asleep! #
# #
# A 500 question quiz is coming up. #
# #
# Be careful; Dream math works a little differently: #
# Addition and multiplication have the REVERSE order of operation. #
# #
# And remember, if you fail in your sleep, you fail in real life... #
# #
#####################################################################


[001]: (5 * 77 + (40 + 88 + 13)) = ?
> 526
Time ran out! You need practice!

要计算数学题,并且时间不能太长,那就socket了,但是加法和乘法的计算顺序要调换,结果实现之后发现又来了一道题,也就是说不止要完成一道题,那就不停循环直到接收到含有”HTB{“字符的消息,结果要计算500道,出题人是不是人类啊!!!

import socket

def adjust_operators(question: str) -> str:
question_adjusted = [] # 更改后的待计算序列
during_plus_op = False # 是否处于加法上下文
for unit in question.split():
if unit == "*":
# 如果 during_plus_op 为 True,表示当前处于加法运算的上下文中,需要在最后一条记录后面加上 ) 并插入 *
if during_plus_op:
question_adjusted[-1] = f"{question_adjusted[-1]}) *"
during_plus_op = False
else:
question_adjusted.append(unit)
continue

if unit == "+":
# 检查下一个单元是否以左括号 ( 开头。如果是,则直接将 + 追加到 question_adjusted 列表中。
if question[question.index(unit)+1].startswith("("):
question_adjusted.append(unit)
continue
if during_plus_op:
question_adjusted[-1] = f"{question_adjusted[-1]} +"
else:
question_adjusted[-1] = f"({question_adjusted[-1]} +"
during_plus_op = True
continue

question_adjusted.append(unit)
# 如果仍在加法上下文中,则加上右括号
if during_plus_op:
question_adjusted[-1] += ")"
# 将列表 question_adjusted 中的元素连接成一个字符串,元素之间用空格分隔
question_adjusted_str = " ".join(question_adjusted)
print(f"{question_adjusted_str=}")
return question_adjusted_str

# 创建TCP套接字
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
# 连接到服务器
sock.connect(('94.237.57.238', 46014))

while True:
# 接收数据
res = sock.recv(1024)
print(res)

# 取出要计算的题目
lines = res.splitlines()
if len(lines) > 1:
text = lines[-2].decode().split('=')[0].split(':')[1].strip()
print('mytext:', text)

# 调整运算符顺序
adjusted_text = adjust_operators(text)

# 计算表达式的值
answer = eval(adjusted_text)
print('myanswer:', answer)

# 发送答案
sock.sendall((str(answer) + '\n').encode())

# 检查是否接收到含有 "HTB{" 的字符串
if b"HTB{" in res:
break # 如果找到了,就退出循环

else:
print("No data received.")

except ConnectionRefusedError:
print("Connection refused. Please check the server address and port.")
except Exception as e:
print(f"An error occurred: {e}")

M0rsarchive

得到一个压缩包,解码会得到下一个压缩包,以及一张图片。这个图片上像是摩斯电码的东西,看样子是这个压缩包的密码。那就利用PIL库读取像素转成摩斯电码然后一层层解密

import re
import os
import sys
import zipfile
from PIL import Image


def get_pass(morse_list):
password = ""
MORSE_CODE_DICT = {'.-': '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', '-----': '0', '.----': '1','..---': '2', '...--': '3', '....-': '4', '.....': '5','-....': '6', '--...': '7', '---..': '8', '----.': '9','-..-.': '/', '.-.-.-': '.', '-.--.-': ')', '..--..': '?','-.--.': '(', '-....-': '-', '--..--': ','}
for morse in morse_list:
password += MORSE_CODE_DICT.get(morse)
return password


def get_morse():
fp = open('./pwd.png', 'rb')
image = Image.open(fp)
pixel = list(image.getdata()) # 获取图片的所有所有像素
background = pixel[0] # 以第一个像素为背景色
chars = []
for i,v in enumerate(pixel): # 获取像素列表的id和值
if v == background:
chars.append(" ")
else: # *表示摩斯电码
chars.append("*")
output = "".join(chars)
"""正则匹配测试建议:https://regex101.com/
^ : asserts position at start of a line
$ : asserts position at the end of a line
\s : matches any whitespace character (equivalent to [\r\n\t\f\v ])
* : matches the previous token between zero and unlimited times, as many times as possible, giving back as needed (greedy)
\* : matches the character *
{3}: matches the previous token exactly 3 times
"""
output = re.sub(r'^\s*', '', output) #匹配开头的任意个空白字符,并替换为空
output = re.sub(r'\s*$', '', output) #匹配结尾的任意个空白字符,并替换为空
output = re.sub(r'\*{3}', '-', output) #匹配3个*号,并替换为字符"-"
output = re.sub(r'\*', '.', output) #匹配单个*号,并替换为字符"."
output = re.sub(r'\s{2,}', ' | ', output) #(用于处理多行摩斯密码的情况)匹配两个以上空白字符,如果存在,就替换为"|"
output = re.sub(r'\s', '', output) #匹配空白字符,并替换为空
output = output.split('|')
fp.close()
return output


def unzip_file(path, number, password):
zip_path = "flag_" + str(1000-number) + ".zip"
fp = zipfile.ZipFile(zip_path)
for file in fp.namelist():
fp.extract(file,"./",pwd=password.encode("utf-8"))
fp.close()


def main():
path = sys.path[0] # 当前脚本的运行目录

for number in range(1,1001):
print("Processing the "+ str(number) + "th archive.")
morse_list = get_morse()
password = get_pass(morse_list)
unzip_file(path, number, password)
path = "./flag"
os.chdir(path) # 切换当前工作目录

fp = open('./flag', 'r')
flag = fp.readlines()
print(flag)
fp.close()


if __name__ == "__main__":
main()

Emdee five for life

是一个网页,要输入所给的字符串才能进入下一个关,并且加密速度要快

import requests
from bs4 import BeautifulSoup
import hashlib

URL = 'http://94.237.53.113:37440'

def md5_encrypt(text):
# 创建一个MD5哈希对象
md5_hash = hashlib.md5()
# 更新哈希对象
md5_hash.update(text.encode('utf-8'))
# 获取十六进制形式的哈希值
hexdigest = md5_hash.hexdigest()
return hexdigest

def get_text(res):
if res.status_code == 200:
soup = BeautifulSoup(res.text, 'html.parser')
h3_element = soup.find('h3', align='center') # 找到<h3>并且有align属性的
if h3_element:
text = h3_element.get_text(strip=True)
return text
else:
print("未找到目标字符串")
else:
print("请求失败,状态码:", res.status_code)

# 创建一个Session对象
session = requests.Session()

# 发送GET请求以获取初始的Cookie
session_res = session.get(URL)

# 获取当前的Cookie
cookie = session_res.cookies.get_dict()

md5 = '' # 初始值为空字符串

while True:
# 发送带有当前Cookie的POST请求
res = session.post(URL, data={'hash': md5}, cookies=cookie,timeout=5)
if 'HTB' in res.text:
print(res.text)
break
text = get_text(res)

if text is not None:
md5 = md5_encrypt(text)
print(f"New MD5: {md5}")

本以为又要做几百关没想到两三次就有了

Eternal Loop

又是一个压缩包,包名叫做37366.zip,里面有一个5900.zip,解压需要密码,猜测使用5900发现解压成功,反正就是无穷嵌套了,使用zipfile库自动解压

import zipfile

zip_path='./37366.zip'

while True:
with zipfile.ZipFile(zip_path,'r') as zip_file:
for filename in zip_file.namelist():
password=filename.split('.')[0]
print(password)
try:
zip_file.extractall('./',pwd=password.encode())
print(f'Successfully extract {filename}')
zip_path=f'./{filename}'
continue
except RuntimeError as e:
print(f"extract {filename} failed!: {e}")
break

发现解压到了6969.zip解压失败了,里面是一个别的文件,使用rockyou爆破得到密码,解压得到一个sqlite3数据库文件,strings一下得到flag

┌──(mikannse㉿kali)-[~/Desktop]
└─$ zip2john 6969.zip >hash
ver 2.0 efh 5455 efh 7875 6969.zip/DoNotTouch PKZIP Encr: TS_chk, cmplen=335181, decmplen=884736, crc=E8183254 ts=5B04 cs=5b04 type=8

┌──(mikannse㉿kali)-[~/Desktop]
└─$ john --wordlist=/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
letmeinplease (6969.zip/DoNotTouch)
1g 0:00:00:00 DONE (2024-09-07 21:02) 25.00g/s 3481Kp/s 3481Kc/s 3481KC/s korn13..katiekatie
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
┌──(mikannse㉿kali)-[~/Desktop]
└─$ strings DoNotTouch|grep HTB
1969-01-01 00:00:002069-01-01 00:00:00Chillin with SatanHellHTB{z1p_and_unz1p_ma_bruddahs}