Profile

2018. 10. 25. 00:54

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

반응형

1. 서론


32/64bit 환경에서 Stack Canary 기법이 걸려있는 것을 많이 볼 수 있다.


여러 pwnable 문제만 봐도 Stack Canary + Heap Exploit이 거의 대다수를 이루고 있다.


근데 여태까지 Stack Canary가 Mitigation인 것만 알고 있었고 어떤 식으로 동작을 하는지 분석을 해본적이 없었다.


뭐.. 심심하기도 해서 이렇게 분석한 것을 글로 남긴다.



2. 본론


먼저 아래와 같은 소스코드를 컴파일보자.


#include <stdio.h>

int main(){
        char buf[1024];
        puts("KuroNeko~");
        gets(buf);
        return 0;
}


[64bit 기준]

32bit 컴파일 : gcc -o canary canary.c -m32

64bit 컴파일 : gcc -o canary canary.c



이렇게 컴파일을 한 뒤, gdb를 통해 카나리를 얻고 체크하는 부분의 어셈블리는 아래의 그림과 같다.


[그림 1] 32bit 바이너리


[그림 2] 64bit 바이너리



32bit는 gs:0x14, 64bit는 QWORD PTR fs:0x28을 통해 canary를 얻어오고 함수가 끝나기 전에 xor을 통해서 검사를 진행한다.


일단, 32bit 기준으로 분석을 해보자.


linux에서 gs 레지스터는 TCB(Task Control Block)의 head 구조체를 참조하게 된다.


Link : https://github.com/lattera/glibc/blob/a2f34833b1042d5d8eeb263b4cf4caaea138c4ad/nptl/sysdeps/i386/tls.h#L44

typedef struct
{
  void *tcb;		/* Pointer to the TCB.  Not necessarily the
			   thread descriptor used by libpthread.  */
  dtv_t *dtv;
  void *self;		/* Pointer to the thread descriptor.  */
  int multiple_threads;
  uintptr_t sysinfo;
  uintptr_t stack_guard;
  uintptr_t pointer_guard;
  int gscope_flag;
#ifndef __ASSUME_PRIVATE_FUTEX
  int private_futex;
#else
  int __unused1;
#endif
  /* Reservation of some values for the TM ABI.  */
  void *__private_tm[5];
} tcbhead_t;

우리가 여기서 주목해야할 것은 0x14 offset에 stack_guard인데, 이 값이 바이너리에서 사용되는 Canary값이 되게 된다. 그렇다면 stack_guard가 어떤 식으로 값이 쓰여지는지 확인하기 위해 glibc repository에서 검색을 해봤다. 아래의 링크를 참조하자


Link : https://github.com/lattera/glibc/blob/a2f34833b1042d5d8eeb263b4cf4caaea138c4ad/nptl/sysdeps/i386/tls.h#L433

/* Set the stack guard field in TCB head.  */
#define THREAD_SET_STACK_GUARD(value) \
  THREAD_SETMEM (THREAD_SELF, header.stack_guard, value)
#define THREAD_COPY_STACK_GUARD(descr) \
  ((descr)->header.stack_guard                  \
   = THREAD_GETMEM (THREAD_SELF, header.stack_guard))

위와 같이 THREAD_SETMEM이란 Macro를 통해서 stack_guard에 값을 쓰고 있는데, THREAD_SETMEM은 아래와 같다.



Link : https://github.com/lattera/glibc/blob/a2f34833b1042d5d8eeb263b4cf4caaea138c4ad/nptl/sysdeps/i386/tls.h#L328

# define THREAD_SETMEM(descr, member, value) \
  ({ if (sizeof (descr->member) == 1)               \
       asm volatile ("movb %b0,%%gs:%P1" :              \
         : "iq" (value),                \
           "i" (offsetof (struct pthread, member)));        \
     else if (sizeof (descr->member) == 4)              \
       asm volatile ("movl %0,%%gs:%P1" :             \
         : "ir" (value),                \
           "i" (offsetof (struct pthread, member)));        \
     else                     \
       {                      \
   if (sizeof (descr->member) != 8)             \
     /* There should not be any value with a size other than 1,       \
        4 or 8.  */                 \
     abort ();                    \
                        \
   asm volatile ("movl %%eax,%%gs:%P1\n\t"            \
           "movl %%edx,%%gs:%P2" :              \
           : "A" (value),               \
       "i" (offsetof (struct pthread, member)),       \
       "i" (offsetof (struct pthread, member) + 4));        \
       }})

보다싶이, 멤버 변수의 크기에 따라서 처리를 해주고 있는 모습을 볼 수 있다.


인자로 넣어진 값들은 THREAD_SLEF, header.stack_guard, value인데, THREAD_SELF 는 pthread 구조체이고 header.stack_guard의 offset 을 구해서 value을 쓰고 있는 것을 볼 수 있다.


일단, 그럼 THREAD_SET_STACK_GUARD Macro가 어디서 사용되는지 검색을 해보면 libc_start_main함수에서 사용하는 것을 볼 수 있다.


Link : https://github.com/lattera/glibc/blob/a2f34833b1042d5d8eeb263b4cf4caaea138c4ad/csu/libc-start.c#L148

  /* Set up the stack checker's canary.  */
  uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
# ifdef THREAD_SET_STACK_GUARD
  THREAD_SET_STACK_GUARD (stack_chk_guard);
# else
  __stack_chk_guard = stack_chk_guard;
# endif

_dl_setup_stack_chk_guard함수를 통해서 얻은 값을 canary로 설정하게 되는 것을 볼 수 있으니 _dl_setup_stack_chk_guard함수를 찾아보면 아래와 같다.


Link : https://github.com/lattera/glibc/blob/a2f34833b1042d5d8eeb263b4cf4caaea138c4ad/sysdeps/generic/dl-osinfo.h#L22

static inline uintptr_t __attribute__ ((always_inline))
_dl_setup_stack_chk_guard (void *dl_random)
{
  union
  {
    uintptr_t num;
    unsigned char bytes[sizeof (uintptr_t)];
  } ret = { 0 };

  if (dl_random == NULL)
    {
      ret.bytes[sizeof (ret) - 1] = 255;
      ret.bytes[sizeof (ret) - 2] = '\n';
    }
  else
    {
      memcpy (ret.bytes, dl_random, sizeof (ret));
#if BYTE_ORDER == LITTLE_ENDIAN
      ret.num &= ~(uintptr_t) 0xff;
#elif BYTE_ORDER == BIG_ENDIAN
      ret.num &= ~((uintptr_t) 0xff << (8 * (sizeof (ret) - 1)));
#else
# error "BYTE_ORDER unknown"
#endif
    }
  return ret.num;
}

인자로 받은 dl_random의 NULL 여부를 통해서 reg.bytes에 값을 작성하게 되는데, union이므로 값을 같이 사용한다.


그리고 최하위 1byte를 NULL로 만들어준다. 이 때, 인자로 받았던 _dl_random은 전역변수로 정의 되어있고 찾아보면 아래와 같은 소스코드에서 작성된 것을 볼 수 있다.


Link : https://github.com/lattera/glibc/blob/a2f34833b1042d5d8eeb263b4cf4caaea138c4ad/sysdeps/generic/ldsodefs.h#L756

extern void *_dl_random attribute_hidden attribute_relro;

_dl_random이 어디서 값이 쓰여지는지 확인을 해본 결과 아래의 소스코드에서 작성되는 것을 볼 수 있다.


Link : https://github.com/lattera/glibc/blob/a2f34833b1042d5d8eeb263b4cf4caaea138c4ad/elf/dl-support.c#L242

void
internal_function
_dl_aux_init (elfw(auxv_t) *av)
{
  int seen = 0;
  uid_t uid = 0;
  gid_t gid = 0;

  _dl_auxv = av;
  for (; av->a_type != at_null; ++av)
    switch (av->a_type)
      {
      case at_pagesz:
        glro(dl_pagesize) = av->a_un.a_val;
        break;
      case at_clktck:
        glro(dl_clktck) = av->a_un.a_val;
        break;
      case at_phdr:
        gl(dl_phdr) = (void *) av->a_un.a_val;
        break;
      case at_phnum:
        gl(dl_phnum) = av->a_un.a_val;
        break;
      case at_hwcap:
        glro(dl_hwcap) = (unsigned long int) av->a_un.a_val;
        break;
#ifdef need_dl_sysinfo
      case at_sysinfo:
        gl(dl_sysinfo) = av->a_un.a_val;
        break;
#endif
#if defined need_dl_sysinfo || defined need_dl_sysinfo_dso
      case at_sysinfo_ehdr:
        gl(dl_sysinfo_dso) = (void *) av->a_un.a_val;
        break;
#endif
      case at_uid:
        uid ^= av->a_un.a_val;
        seen |= 1;
        break;
      case at_euid:
        uid ^= av->a_un.a_val;
        seen |= 2;
        break;
      case at_gid:
        gid ^= av->a_un.a_val;
        seen |= 4;
        break;
      case at_egid:
        gid ^= av->a_un.a_val;
        seen |= 8;
        break;
      case at_secure:
        seen = -1;
        __libc_enable_secure = av->a_un.a_val;
        __libc_enable_secure_decided = 1;
        break;
      case at_random:
        _dl_random = (void *) av->a_un.a_val;
        break;
# ifdef dl_platform_auxv
      dl_platform_auxv
# endif
      }
  if (seen == 0xf)
    {
      __libc_enable_secure = uid != 0 || gid != 0;
      __libc_enable_secure_decided = 1;
    }
}
#endif

보다시피 auxv라고 하는 것을 사용하는 것을 볼 수 있는데, auxv는 Auxiliary Vectors의 약자로 kernel data를 user process에게 전달하는 메커니즘이다.


다른 것들도 중요하긴한데, 우리가 여기서 주목해야할 것은 AT_RANDOM type일 때 _dl_random 값을 넣어주는 것을 볼 수 있다는 점이다. 


auxv에서 얻어온 값을 _dl_random에 집어넣고 그 이후에 _dl_setup_stack_chk_guard함수에 의해서 우리가 자주 봐왔던 canary값을 만들어주게 된다.


auxv에 대해서 더 알고 싶다면 아래의 링크를 통해서 확인해보면 된다.


Link : http://articles.manugarg.com/aboutelfauxiliaryvectors.html



위의 링크를 참조해보면, envp의 바로 뒤부터 auxv가 있는 것을 알 수 있고, 이 곳은 Program Loader에 의해서 작성된다.


그럼 이제, auxv의 AT_RANDOM type 에 어떤 값이 쓰여지는지 알아보기 위해 아래의 소스코드를 보자.

Link : https://github.com/torvalds/linux/blob/master/fs/binfmt_elf.c#L261

...
  /*
   * Generate 16 random bytes for userspace PRNG seeding.
   */
  get_random_bytes(k_rand_bytes, sizeof(k_rand_bytes));
  u_rand_bytes = (elf_addr_t __user *)
           STACK_ALLOC(p, sizeof(k_rand_bytes));
  if (__copy_to_user(u_rand_bytes, k_rand_bytes, sizeof(k_rand_bytes)))
    return -EFAULT;

  /* Create the ELF interpreter info */
  elf_info = (elf_addr_t *)current->mm->saved_auxv;
  /* update AT_VECTOR_SIZE_BASE if the number of NEW_AUX_ENT() changes */
#define NEW_AUX_ENT(id, val) \
  do { \
    elf_info[ei_index++] = id; \
    elf_info[ei_index++] = val; \
  } while (0)

#ifdef ARCH_DLINFO
  /* 
   * ARCH_DLINFO must come first so PPC can do its special alignment of
   * AUXV.
   * update AT_VECTOR_SIZE_ARCH if the number of NEW_AUX_ENT() in
   * ARCH_DLINFO changes
   */
  ARCH_DLINFO;
#endif
  NEW_AUX_ENT(AT_HWCAP, ELF_HWCAP);
  NEW_AUX_ENT(AT_PAGESZ, ELF_EXEC_PAGESIZE);
  NEW_AUX_ENT(AT_CLKTCK, CLOCKS_PER_SEC);
  NEW_AUX_ENT(AT_PHDR, load_addr + exec->e_phoff);
  NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr));
  NEW_AUX_ENT(AT_PHNUM, exec->e_phnum);
  NEW_AUX_ENT(AT_BASE, interp_load_addr);
  NEW_AUX_ENT(AT_FLAGS, 0);
  NEW_AUX_ENT(AT_ENTRY, exec->e_entry);
  NEW_AUX_ENT(AT_UID, from_kuid_munged(cred->user_ns, cred->uid));
  NEW_AUX_ENT(AT_EUID, from_kuid_munged(cred->user_ns, cred->euid));
  NEW_AUX_ENT(AT_GID, from_kgid_munged(cred->user_ns, cred->gid));
  NEW_AUX_ENT(AT_EGID, from_kgid_munged(cred->user_ns, cred->egid));
  NEW_AUX_ENT(AT_SECURE, bprm->secureexec);
  NEW_AUX_ENT(AT_RANDOM, (elf_addr_t)(unsigned long)u_rand_bytes);
...

주석이 설명해주는 것처럼 커널에서 생성된 16 byte random bytes들을 생성 후, 유저영역으로 값을 복사하고 vector에 추가하는 것을 볼 수 있다.


하지만 전부 canary로 사용하는 것이 아닌 하위 4byte만 canary값으로 사용하는 것을 볼 수 있다.



위의 과정들을 종합해보면 결과적으로는 아래와 같이 동작하게 된다.

1. Program Loader에 의해서 kernel에서 생성된 u_rand_bytes를 user 영역에 복사 후, auxv에 설정해준다.

2. ld.so에서 AT_* 관련된 값들을 읽어들이면서 해당하는 값들을 셋팅한다.

3. 1번에서 설정된 u_rand_bytes(16 bytes)의 하위 4byte를 canary로 사용(32bit 기준)하는데 하위 1byte를 NULL로 만든 것을 사용한다.

  -> gs:0x14 (stack_guard)에 값을 설정



커널에서 랜덤값을 읽는 것과 ld.so에서 AT_* 관련 값들을 설정해주는 것을 제외하고 위의 과정이 맞는지 알아보기 위해 아래와 같은 코드를 작성했다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/unistd.h>
#include <asm/ldt.h>
#include <pthread.h>

#define GET_THREAD_AREA 244

void *test(){
        struct user_desc uinfo;
        pthread_t th;
        int status;

        uinfo.entry_number = 12;
        syscall(244, &uinfo);

        printf("baseaddr : %p\n", uinfo.base_addr);
        printf("Canary : %p\n", *(unsigned int *)(uinfo.base_addr + 0x14));

        while(1);

        return NULL;
}

int main(){
        struct user_desc uinfo;
        pthread_t th;
        int status;

        uinfo.entry_number = 12;
        syscall(244, &uinfo);

        printf("baseaddr : %p\n", uinfo.base_addr);
        printf("Canary : %p\n", *(unsigned int *)(uinfo.base_addr + 0x14));

        if(pthread_create(&th, NULL, test, NULL) < 0){
                puts("wtf...");
                return -1;
        }

        pthread_join(th, (void **)&status);

        return 0;
}
위의 소스코드를 간단하게 요약하자면, 프로그램은 시작되면 ld.so에서 set_thread_area syscall을 통해, 현재 thread의 속성(?)을 설정해주게 된다. 설정되었던 속성들을 얻어오기 위해서 get_thread_area syscall을 사용해야하는데 이 때, entry_number를 통해서 여러 정보들 중 하나(현재 스레드)를 골라 얻어오게 되는 원리다.
여기서 얻어온 user_desc구조체의 base_addr 변수의 값은 TCB(Thread Control Block)의 주소이며, gs 레지스터가 가지고 있는 주소와 동일하다. 즉, base_addr + 0x14는 gs:0x14와 동일한 결과를 가져온다는 것을 알 수 있으므로, 그 값을 읽어보기 위해 실행하면 아래와 같은 결과가 나온다.

[그림 3] get_thread_area를 통한 카나리 얻기


심심하니 재미로 canary가 있는 주소를 알아보도록 하자.

[그림 4] canary 값 검색


일단 위와 같이 0xf7de3714 주소에 카나리가 들어있는 것을 볼 수 있다. 그럼 이제 base_addr를 출력해주는 곳으로 이동해서 값과 어디에 위치하고 있는지 확인해보자.

[그림 5] base_addr (eax) 확인


보다시피 ubuntu 16.04 기준 ld-2.23.so에서는 libc 바로 이전 영역에 base_addr이 가리키고 있는 것을 볼 수 있다. 그리고 현재 eax 레지스터가 가지고 있는 값은 TCB(Thread Control Block)이므로 eax + 0x14를 하게 되면 위의 그림에서 봤던 카나리 값이 나오게 될 것이다.


[그림 6] canary값 확인



3. 결론


일단 32bit에서 어떻게 Canary가 작성되는지 코드 분석을 통해서 어떻게 카나리가 로드되는지 확인해봤는데,


언젠가 또 심심하면 커널에서 random value를 어떻게 생성하는 지 분석할 것 같다. 그래서 언제 다음 글이 올라올지 모르겠다.


* 틀린게 있다면 댓글로 알려주세요

'공부' 카테고리의 다른 글

xss payload  (0) 2019.04.19
[Windows Kernel Driver] 개발환경 구성  (0) 2018.11.25
python AES  (0) 2018.08.10
[IDA] C++ Class 변환  (0) 2017.08.09
[QEMU] iptime emulating  (2) 2017.07.26
블로그 이미지

KuroNeko_

KuroNeko

,

python AES

공부 2018. 8. 10. 00:54
반응형

웹이나 프로그램 분석을 하는데 AES 암/복호화를 사용하는 경우가 자주 있다.

 

주로, 로그인할 때나 파일 암/복호화를 할 때 사용된다. 이외에도 많긴한데, 그거는 프로그래머 마음이니

 

python으로 간단하게 짤 수 있는데 그냥 외우기 귀찮아서 글을 올린다.

 

[PyCrypto 설치]

# python2.7
pip install PyCrypto

# python3
pip3 install pycryptodome

 

python에서 AES ECB/CBC 암/복호화할 때 밑에꺼만 살짝 수정하면 된다.

 

[ECB Mode]

from Crypto.Cipher import AES

# aes-256-ecb
BLOCK_SIZE = 32
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * bytes([BLOCK_SIZE - len(s) % BLOCK_SIZE])
unpad = lambda s: s[:-ord(s[len(s) - 1:])]

key = "keyz".ljust(BLOCK_SIZE, "\x00")

aes = AES.new(key, AES.MODE_ECB)
enc = aes.encrypt(pad("KuroNeko"))
print(unpad(aes.decrypt(enc)))

 

[CBC Mode]

from Crypto.Cipher import AES

# aes-128-cbc
BLOCK_SIZE = 16
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * bytes([BLOCK_SIZE - len(s) % BLOCK_SIZE])
unpad = lambda s: s[:-ord(s[len(s) - 1:])]

key = "keyz".ljust(BLOCK_SIZE, "\x00")
iv = "".ljust(BLOCK_SIZE, "\x00")

aes = AES.new(key, AES.MODE_CBC, IV=iv)
enc = aes.encrypt(pad("KuroNeko"))
print(unpad(aes.decrypt(iv + enc)[16:]))

 

'공부' 카테고리의 다른 글

[Windows Kernel Driver] 개발환경 구성  (0) 2018.11.25
유저 영역 Stack Canary 분석  (2) 2018.08.16
[IDA] C++ Class 변환  (0) 2017.08.09
[QEMU] iptime emulating  (2) 2017.07.26
[how2heap] poison_null_byte  (0) 2017.05.09
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

pip install --ignore-installed pwntools를 해준 다음


pip install pwntools를 다시 해주면 설치가 완료된다.

'자료' 카테고리의 다른 글

[Conoha] letsencrypt wildcard 인증서 발급  (0) 2018.12.09
Profile  (0) 2018.10.25
[Python] Mutation Fuzzer  (0) 2018.05.16
[Library] vcpkg boost 1.66 설치에러 해결방법  (0) 2018.02.01
Abusing File Structure  (0) 2018.01.04
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

옵션 적용 방법


gcc -o helloworld helloworld.c [Options]



[NX]

적용 : 별다른 옵션이 필요없다 (최신 버전 기준)

해제 : -zexecstack


[Stack Canary]

적용 : 별다른 옵션이 필요없다 (최신 버전 기준)

해제 : -fno-stack-protector


[ASLR]

적용 : 운영체제에서 제공해준다 (최신 버전 기준)

해제 : echo 0 > /proc/sys/kernel/randomize_va_space

- randomize_va_space=0 : ASLR 해제

- randomize_va_space=1 : 랜덤 스택 & 랜덤 라이브러리 설정

- randomize_va_space=2 : 랜덤 스택 & 랜덤 라이브러리 & 랜덤 힙 설정


[RELRO]

- Partial

적용 : -Wl,-z,relro

해제 : 별다른 옵션이 필요없다.

- Full

적용 : -Wl,-z,relro,-z,now

해제 : 별다른 옵션이 필요없다.


[PIE]

적용 : -fPIE -pie

해제 : 별다른 옵션이 필요없다.



예시

gcc -o helloworld helloworld.c -zexecstack -Wl,-z,relro

블로그 이미지

KuroNeko_

KuroNeko

,
반응형

[Pwntools]

apt-get install python2.7 python2.7-dev python-pip


pip install pwntools


apt-get install libcapstone-dev



[Peda]


Link
블로그 이미지

KuroNeko_

KuroNeko

,

[Python] Mutation Fuzzer

자료 2018. 5. 16. 23:56
반응형
Link

windbg에서 사용되는 cdb를 이용해 간단한 Mutation 퍼저를 만들었습니다.

간단하게 FuzzBase클래스를 상속하면 mutation 기능을 확장할 수 있게 만들어져있습니다.

현재 dump퍼저를 먼저 구현했고, smart 퍼저도 구현할 생각입니다.


'자료' 카테고리의 다른 글

Profile  (0) 2018.10.25
[Pwntools] pyserial uninstall fail시  (0) 2018.07.17
[Library] vcpkg boost 1.66 설치에러 해결방법  (0) 2018.02.01
Abusing File Structure  (0) 2018.01.04
[C++] Python format 구현  (0) 2017.12.03
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

boost 라이브러리를 vcpkg로 설치하게 되면 아래와 같은 에러가 발생한다.


'mt' not recognized ~~~


위의 에러는 SDK의 mt.exe를 찾지 못하여 발생하는 것으로, 먼저 SDK가 설치되어 있는지 확인해본다.


visual studio 버전에 맞는 SDK를 설치하게 되면 windows 10 기준으로


C:\Program Files (x86)\Windows Kits\10


에 SDK가 설치되는데 운영체제에 따라서 폴더들이 나눠져있다.



SDK 경로내에 bin\10.0.15063.0\x86을 들어가면 mt.exe, mt.exe.config이 존재하는데,


해당 파일을 vcpkg 경로의 installed\{TARGET_TRIPLET}\tools\boost-build에 복사한다.


그 다음 boost-modular-build.cmake에 


    ######################

    # Perform build + Package

    ######################


부터 if 문이 존재하는데 해당 if문 안에 


file(COPY ${BOOST_BUILD_PATH}/mt.exe ${BOOST_BUILD_PATH}/mt.exe.config DESTINATION ${_bm_SOURCE_PATH})


를 추가하면 빌드가 슥슥 잘된다.


근데, zlib이나 bzip2같이 dependency들은 CMakeTmp 폴더에 복사해주면 되겠다.

'자료' 카테고리의 다른 글

[Pwntools] pyserial uninstall fail시  (0) 2018.07.17
[Python] Mutation Fuzzer  (0) 2018.05.16
Abusing File Structure  (0) 2018.01.04
[C++] Python format 구현  (0) 2017.12.03
[Ubuntu] upgrade시 용량부족으로 인한 문제 해결  (0) 2017.10.12
블로그 이미지

KuroNeko_

KuroNeko

,