win()
win()
)
win()
when NX is on)Abusing functions which can read more data than they have allocated memory for (gets
, strcpy
)
Allows us to control the stack (local vars, ret)
Mitigations: ASLR/PIE/Stack canaries
Breaking them: leaking addresses/the canary
Once we can control execution of the program (e.g. changing the return address), where do we go?
Mitigations: NX, buffer too small for a payload
Breaking them: ROP, EggHunters
instead of writing our own assembly instruction, we re-use existing instructions from the program.
We use instructions preceding a ret
(gadgets), so we can jump to them, execute them, and jump back.
We chain these gadgets so we can execute a full payload, by: jumping to first one, executing it, jumping back, jumping to the second one, etc.
it grabs what rsp is pointing to, and jumps there
pop rcx
jmp rcx
rsp is integral to our ropchain
ret is the last thing in a function call
rbp
is grabbed off the stackrsp
should is pointing at the return address 0x18 [ ARGS ] <- parameters
0x14 [ RIP ] <- +++rsp now points here+++
0x10 [ RBP ] <- cleaned up by leave
0x0C [ AAAAAAAA ] <- local vars are dealloc'd
it’s going to execute each gadget, then grab the next one off the stack, and execute that
0x18 [ GADGET_3 ] <- parameters
0x18 [ GADGET_2 ] <- parameters
0x14 [ GADGET_1 ] <- +++rsp now points here+++
0x10 [ RBP ] <- cleaned up by leave
0x0C [ AAAAAAAA ] <- local vars are dealloc'd
0xAABBCCDD 0xAABBCCDD 0xAABBCCDD
^^^^^^^^ ^^ ^^^^
MOV RAX, 12 XOR RAX, RAX INC RAX; CALL WIN
note, I made those ^^^ up entirely
/* argv = envp = NULL */
xor rcx, rdx
xor rdx, rdx
/* push '/bin/sh' onto stack */
push 0x732f2f6e69622f
mov rbx, rsp
/* call execve() */
mov rax, 0xb /* Syscall Number 11 */
syscall /* Trigger syscall */
execve('/bin/sh', NULL, NULL)
RAX = 0x3B # (59)
RBX = address to /bin/sh
RCX = NULL
RDX = NULL
syscall
Instead of raw instructions, we’ll use gadgets
[GADGET_1] # RAX := 0x3B # (59)
[GADGET_2] # RBX := address to /bin/sh
[GADGET_3] # RCX := NULL
[GADGET_4] # RDX := NULL
[GADGET_5] # Syscall
> ROPgadget --binary ropme --search 'pop rbx'
0x0804832d : pop rbx ; ret
> ROPgadget --binary ropme --search'xor rcx'
0x080484b5 : xor rcx, rcx ; ret
> ROPgadget --binary ropme --search 'xor rdx'
0x080484b8 : xor rdx, rdx ; ret
> ROPgadget --binary ropme --search 'syscall'
0x080484bb : mov rax, 0x3B ; int 0x80
pop rbx; ret
grabs the next address on the stack, and stores it in rbx
p32(0x08041234) // pop rbx; ret;
p32(0x0804abcd) // address of "/bin/sh"
// now rbx stores a pointer to "/bin/sh"
p32(0x08041234) // pop rbx; ret;
p32(0xA) // 10
// now rbx == 10
we could grab the stack pointer, and store it in rbx
push esp, pop rbx; ret;
// now rbx will store &esp
generally both instructions will need to be in the same gadget
[ PADDING ] <== our first gadget should overwrite ret
[ RAX=0x3B ]
[ POP RBX ]
[ &BIN SH ]
[ XOR RCX ]
[ XOR RDX ]
[ SYSCALL ]
CALL SYSTEM
PUSH &('/bin/sh')
printf
, gets
, etc)* function offsets vary by LIBC version, you can find the correct offsets here
pwntools
libc = ELF("libc_version.so")
libc.address = printf_leak - libc.symbols['printf']
# libc.address is now the correct address
# you can directly access functions like:
libc.symbols['system'] # etc...
elf = ELF('binary_file')
# you can find strings
next(elf.search(b'/bin/sh'))
# you can also find gadgets
next(elf.search(asm(b'mov rax, 0xb; ret', os='linux', arch=e.arch)))
ret2libc
3 chals this week, try to solve them all
image-viewer?