2. 목차
• 실전 셸 코드
• Windows
• Universal 셸 코드
• 배경지식
• 실습
• Port Binding 셸 코드
• 포트 열기 실습
• 파이프 열기 실습
• Reverse Connection 셸 코드
• 실습
• Linux
• Linux 환경에서 셸 코드 제작
• 실습
• Port binding 셸 코드
• 포트열기 실습
• dup2 실습
• Reverse Connection 셸 코드
• 실습
• Metasploit과 셸 코드
• 셸 코드 제작
• 셸 코드 인코딩
• 부록
• 참고 사이트
• 참고 서적
4. WINDOWS UNIVERSAL 셸 코드
• 앞선 예제에서는 직접 함수 주소를 얻어온 뒤 하드코딩 했다
• 실제로는 컴퓨터를 부팅할 때마다 다른 주소를 이용하므로, 셸 코드에서 함수의 주소를 직접 알아내어 이용할 수 있도록 해야 한다.
• 여태까지는 LoadLibraryA나 GetProcAddress등의 함수를 이용해 주소를 얻어왔다
• ->이것 역시 함수이기 때문에, 셸 코드에서 이용하기 위해서는 이 함수의 주소를 얻어야 한다!
• 함수를 사용하기 위해 필요한 함수 주소를 셸 코드가 스스로 구해서 동적으로 입력해 주는 것이 Universal 셸 코드의 목적
• PE 헤더를 통해 직접 알아낸다.
5. WINDOWS UNIVERSAL 셸 코드
• 함수 주소를 구하는 과정
• 1) 원하는 DLL의 image base를 구함
• 2) DLL image base로부터 함수까지의 offset을 알아냄
• 함수 주소를 직접 구하기 위해 알아야 하는 것
• TEB
• PEB
• PE 헤더 – Export 관련 (Export table, Export Name Table 등)
6. WINDOWS UNIVERSAL 셸 코드 - DLL의 시작 주소 구하기 - 배경지식
• 배경지식
• TEB
• 쓰레드에 대한 정보를 담고 있는 구조체
• FS 레지스터를 이용하여 TEB에 접근할 수 있다
• PEB에 접근하기 위해 이용한다.
• FS:[0x30] 위치에 PEB의 주소가 있다
7. WINDOWS UNIVERSAL 셸 코드 - DLL의 시작 주소 구하기 - 배경지식
• 배경지식
• PEB
• 프로세스에 대한 정보를 담고 있는 구조체
• 여러 정보가 있는데, 그 중 로드된 PE(EXE, DLL 등)에 대한 정보를 이용할 것
• PEB.Ldr
• PEB_LDR_DATA 구조체의 주소를 가리키는 포인터
• PEB_LDR_DATA .InMemoryOrderModuleList
• PE image들의 데이터가 저장된 LDR_DATA_TABLE_ENTRY 구조체의 더블 링크드 리스트 시작 주소를 가리키는 포인터
• 리스트는 세 종류가 있는데, 이 중 이것을 사용했음
• LDR_DATA_TABLE_ENTRY.DllBase
• 모듈의 주소
• DLL의 image base
8. WINDOWS UNIVERSAL 셸 코드 - DLL의 시작 주소 구하기 - 배경지식
• 배경지식
FS : [0x30] PEB ...
+0x00c Ldr
PEB_LDR_DATA ...
+0x0014 InMemoryOrderModuleList
LDR_DATA_TABLE_ENTRY ...
+0x008 InMemoryOrderList
+0x018 DllBase
+0x02C BaseDllName
LDR_DATA_TABLE_ENTRY ...
+0x008 InMemoryOrderList
+0x018 DllBase
+0x02C BaseDllName
<EXE>
LDR_DATA_TABL_ENTRY ...
+0x008 InMemoryOrderList
+0x018 DllBase
+0x02C BaseDllName
<첫번째DLL>
<N번째DLL>
원하는 DLL의 PE 시작 주소(DOS 헤더 주소)
.......
9. WINDOWS UNIVERSAL 셸 코드 - DLL의 시작 주소 구하기 - 배경지식
• 배경지식
• Export
• DLL은 함수를 내보내기 위해 PE 헤더의 Export 헤더를 이용한다
• 실제로 DLL의 함수를 이용하기 위해 하는 작업과 동일한 방식으로 주소를
구하여 이용할 것
• 이용할 함수 찾는 법
• 1) 함수명 배열에서 이용하고자 하는 함수 이름과 해당 인덱스를 찾는다
• 2) Ordinals 배열에서 인덱스에 해당하는 서수 인덱스 값을 찾는다
• 3) EAT 배열에서 서수 인덱스에 해당하는 함수 offset을 확인한다
• 4) DLL base와 offset을 더해 함수의 실제 주소를 구한다
10. WINDOWS UNIVERSAL 셸 코드 - DLL의 시작 주소 구하기 - 배경지식
• 배경지식
• Function FuncC를 찾는 상황
OptionalHeader.
DataDirectory
+0x000 ExportTable.VirtualAddress
+0x004 ExportTable.Size
...
원하는 DLL의 PE
IMAGE_EXPORT_DERICTORY +0x01C AddressOfFunctions
+0x020 AddressOfNames
+0x024 AddressOfOrdinals
AddressOfNames RVA RVA RVA RVA
“FuncA” “FuncB” “FuncC” “FuncD”
AddressOfNameOrdinals 0 2 3 4
FuncA
코드
<index 2>
AddressOfFunctions RVA null RVA RVA RVA
FuncB
코드
FuncC
코드
FunD
코드
11. WINDOWS UNIVERSAL 셸 코드 - DLL의 IMAGE BASE 구하기 – 실습 1
• 실습
• DLL의 Image base 찾기
12. WINDOWS UNIVERSAL 셸 코드 - DLL의 IMAGE BASE 구하기 – 실습 1
• 실습
• 메모리에 올라와 있는 순서대로 접근할 수 있는 리스트를 이용했다
• 무엇이든 같은 리스트를 이용하면 됨
• 주의!
• 각 리스트는 더블 링크드 리스트
• F는 forward, B는 backward
• 각 리스트는 다음 PEB_TABLE_ENTRY의 _LIST_ENTRY 구조체를 가리킨다.
13. WINDOWS UNIVERSAL 셸 코드 - DLL의 IMAGE BASE 구하기 – 실습 1
• 실습
• BaseDllName은 _UNICODE_STRING 자료형이다.
• 앞 4바이트는 크기와 최대 크기를 의미하
• 실제 문자열이 있는 주소는 시작 주소 +4에 위치
• 주의!
• EBX는 _LIST_ENTRY 구조체의 주소이다.
• -> LDR_DATA_TABLE_ENTRY의 시작주소를 기준으로 +2C에 위치한 BaseDllName는 현재 EBX에 있는 값(InMemoryOrderLinks)을 기준으
로 +24에 위치한다.
• 여기서, 문자열 주소를 얻고 싶으므로 +4된 +28을 이용한 것
14. WINDOWS UNIVERSAL 셸 코드 - DLL의 IMAGE BASE 구하기 – 실습 1
• 실습
• 주의!
• EBX는 _LIST_ENTRY 구조체의 주소이다.
• -> LDR_DATA_TABLE_ENTRY의 시작주소를 기준으로 +18에 위치한 DllBase는
현재 EBX에 있는 값(InMemoryOrderLinks)을 기준으로 +10에 위치한다
15. WINDOWS UNIVERSAL 셸 코드 - DLL의 IMAGE BASE 구하기 – 실습 2
• 실습
• 앞선 예제에서는 편의를 위해 함수와 변수를 이용했다.
• 문자열 길이 구하기 함수를 이용했음
• -> 실제 셸 코드에서는 이 함수 또한 이용할 수 없다.
• 문자열을 위해 변수를 이용했음
• ->실제 셸 코드에서는 직접 문자열을 저장해가야 한다.
• 직접 구현해 이용하는 call이나 jmp는 괜찮음
• -> 상대주소이기 때문
• 조금 더 실전형으로 수정이 필요하다!
• 문자열 비교 : 직접 문자열을 비교하기보다는 hash를 이용하여 비교하는 편이 간단함
• 간단히 문자열의 아스키 코드를 전부 더하는 해시를 이용
• Ex) ABC의 hash 결과는 0x41 + 0x42 + 0x43 = 0xC6
• Integer overflow는 상관 없음 – 단순히 고유한, 같은 숫자가 나오면 됨
• -> DLL뿐만 아니라 함수를 찾을 때도 이용할 것
16. WINDOWS UNIVERSAL 셸 코드 - DLL의 IMAGE BASE 구하기 – 실습 2
• 실습
• Hash 이용
• 셸 코드에는 미리 구한 해시 값을 이용할 것
18. WINDOWS UNIVERSAL 셸 코드 - DLL의 IMAGE BASE 구하기 – 실습 2
• 실습
• Hash를 이용하기
• 미리 만든 KERNEL32.DLL의 해시 값인 0x330과 비교하도록 함
19. WINDOWS UNIVERSAL 셸 코드 - DLL의 IMAGE BASE 구하기 – 실습 2
• 실습
• Hash를 이용하기
• FIND 함수에서, 비교하고자 하는 문자열의 hash값을 생성한 뒤
인자로 받은 0x330과 비교한다.
• 결과를 EAX에 셋팅하고 나옴
20. WINDOWS UNIVERSAL 셸 코드 - DLL의 IMAGE BASE 구하기 – 실습 2
• 실습
• Hash를 이용하기
• HASH_UNICODE 함수에서, EAX에 있는 유니코드 문자열의 해시 값을
구한다.
21. WINDOWS UNIVERSAL 셸 코드 – 함수 주소 구하기 – 실습1
• 실습
• 함수 주소 구하기
• 함수 이름을 찾을 때도 Hash 이용
• 셸 코드에는 미리 구한 해시 값을 이용할 것
22. • 실습
• 함수 주소 구하기
• 찾은 DLL의 image base 위에서 Export
와 관련된 PE 헤더에 접근하여 함수
주소를 얻어온다.
직접 찾은 WinExec 함수 주소
실제 WinExec 함수 주소
24. • 실습
• 함수 주소 구하기
• 먼저 Export Table을 찾아간다.
OptionalHeader.
DataDirectory
+0x000 ExportTable.VirtualAddress
+0x004 ExportTable.Size
...
원하는 DLL의 PE
IMAGE_EXPORT_DERICTORY +0x01C AddressOfFunctions
+0x020 AddressOfNames
+0x024 AddressOfOrdinals
25. • 실습
• 함수 주소 구하기
• AddressOfNames 배열에서 찾고자 하
는 함수 이름과 일치하는 함수 이름
이 배열의 몇 인덱스에 있는지 찾는
다.
원하는 DLL의 PE
IMAGE_EXPORT_DERICTORY +0x01C AddressOfFunctions
+0x020 AddressOfNames
+0x024 AddressOfOrdinals
AddressOfNames RVA RVA RVA RVA
“FuncA” “FuncB” “FuncC” “FuncD”
<index 2>
26. • 실습
• 함수 주소 구하기
• 얻어낸 배열 인덱스를 이용하여,
AddressOfNameOrdinals 배열에 접근
한다.
• 같은 인덱스에 있는 값(서수)을 얻는
다.
원하는 DLL의 PE
IMAGE_EXPORT_DERICTORY +0x01C AddressOfFunctions
+0x020 AddressOfNames
+0x024 AddressOfOrdinals
AddressOfNames RVA RVA RVA RVA
“FuncA” “FuncB” “FuncC” “FuncD”
AddressOfNameOrdinals 0 2 3 4
<index 2>
27. • 실습
• 함수 주소 구하기
• 얻은 서수를 인덱스로 하여
AddressOfFunctions 배열에 접근한다.
• 함수 주소를 얻어온다.
원하는 DLL의 PE
IMAGE_EXPORT_DERICTORY +0x01C AddressOfFunctions
+0x020 AddressOfNames
+0x024 AddressOfOrdinals
AddressOfNameOrdinals 0 2 3 4
FuncA
코드
AddressOfFunctions RVA null RVA RVA RVA
FuncB
코드
FuncC
코드
FunD
코드
28. WINDOWS UNIVERSAL 셸 코드 – 이전에 만든 셸 코드와 합치기 – 실습
• 실습
• 기능을 하나씩 구현했으니 앞서 만
든 셸 코드와 합쳐보자
• 맨 위에 스택 확보 코드 추가
• ---임시 부분에 오른쪽에 있는 것 중
왼쪽에 있는 코드 추가
• 맨 아래 EXPLOIT 코드 추가
29. WINDOWS UNIVERSAL 셸 코드 – 이전에 만든 셸 코드와 합치기
• Universal 셸 코드 성공!
• 사용할 함수가 많다면 LoadLibraryA/W 함수와 GetProcAddress 함수 주소를 얻은 뒤 이용하는 것도 좋다.
• 이 코드 역시 실제로 사용하기 위해서는 null 바이트를 없애는 과정이 필요하다
• 직접 정제해보자!
31. WINDOWS PORT BINDING 셸 코드
• 앞에서 만든 셸코드는 단순히 cmd를 실행시키는 셸코드
• 이는 주로 취약점에 대해 공격이 가능하다고 증명하는 코드(Proof of Concept, PoC)로 사용되는 셸코드
• 실제 공격에 사용되는 셸코드는 조금 더 다양하고 강력한 기능을 수행
• 일반적으로 Exploit의 목적은 공격 대상의 시스템 권한을 획득하는 것
• 일반적으로 시스템 권한 획득은 대상 시스템의 셸 접속을 의미
• 셸 접속 : 로컬 컴퓨터가 아니라 외부에서 셸을 이용
• 셸에 접속할 수 있게 포트를 열도록 해주는 셸 코드를 포트 바인딩 셸코드 (또는 바인딩 셸코드)
라고 부름
• 특정 포트를 열고, 인증 절차 없이 공격 대상 시스템에 쉽게 접속할 수 있도록 해준다
• 백도어용으로도 이용
• 한 번 공격에 성공하면, 다음부터는 간편하게 셸에 접속할 수 있음
32. WINDOWS PORT BINDING 셸 코드
• 셸 코드에 추가로 필요한 기능
• 포트 열게 하기
• 포트로 들어오는 명령어를 셸에 전달하게 하기
• 출력을 포트로 전달하게 하기
대상 컴퓨터
공격자
port
cmd
stdin stdout
port
<Client>
<Server>
33. WINDOWS PORT BINDING 셸 코드 – 포트 열기
• 우선 어셈블리어로 TCP/IP 통신을 할 수 있는 포트를 여는 코드를 짜보자
대상 컴퓨터
공격자
port
cmd
stdin stdout
port
<Client>
<Server>
34. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• C언어로 짤 때는 비주얼 스튜디오 설정을 이용해 라이브러리를 이용해 보자
• 프로젝트 속성 – 링커 – 입력 – 추가 종속성에 ws2_32.lib를 입력
• 셸코드에서는 LoadLibraryA/W를 이용할 것
35. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• 서버 프로그래밍과 같다
• IP와 포트 주소를 bind
• 연결을 위해 listening -> 연결 요청이 들어오면 accept
• 오른쪽은 간단한 윈도우 소켓 프로그래밍 – 서버 이다.
• 어셈블리어로 이 코드를 짜고, 셸 코드로 만들 것
• 실제로 이용하기 위해서는 Universal 셸 코드로 만들어야 한다
• 직관적인 설명을 위해 직접 함수 주소를 이용하는 방식을 이용
36. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• C로 짠 서버 프로그램에서는 비주얼 스튜디오를 이용하여 라이브러리를 링킹했
지만, 셸코드에서는 직접 DLL을 로딩해야 한다.
• 앞서 구한 LoadLibraryA함수 주소를 이용하여 로딩하도록 했다
• 원래는 Universal 셸코드로, 직접 함수 주소를 구해서 호출해야 함
• Ws2_32.dll을 로딩하도록 하자
37. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• 주요 코드를 어셈블리어로 어떻게 써야할 지 보자
• WSAStartup 함수
• 리눅스와는 다르게, 소켓을 이용할 때 가장 먼저 호출한다.
• 초기화 과정
• 첫 번째 인자 : 윈도우 소켓의 버전
• 앞 바이트는 정수, 뒤 바이트는 소수를 의미
• Ex) 0x0203 = 2.3
• MAKEWORD는 매크로로, 두 숫자를 Word 형식으로 붙여준다
• 두 번째 인자 : 초기화 상태를 저장할 변수의 주소
38. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• 저장한 초기화 정보는 굳이 사용할 일이 없으니 필요 없는 스택 주소
를 인자로 주었다.
39. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• 주요 코드를 어셈블리어로 어떻게 써야할 지 보자
• socket 함수
• PF_INET은 2, SOCK_STREAM은 1로 define되어 있다는 점에 주목
• 함수가 반환한 값은 소켓 디스크립터이므로, 잘 저장해 두어야 한다
• 리스닝 소켓임에 주의
41. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• 주요 코드를 어셈블리어로 어떻게 써야할 지 보자
• 서버가 되기 위해 주소를 설정한다.
• servAddr는 sockaddr_in 구조체이다.
• 설정할 구조체 각 멤버의 위치에 주목
• sin_family에는 AF_INET(=2)를 넣을 것
• WORD 크기 멤버
• 구조체의 첫 번째 멤버
• 구조체 시작 주소
• sin_addr.s_addr에는 0을 넣을 것
• DWORD 크기 멤버
• 구조체의 세 번째 멤버
• 구조체 시작 주소 + 4h
• sin_port에는 포트번호를 넣을 것
• WORD크기 멤버
• 구조체의 두 번째 멤버
• 구조체 시작 주소 + 2h
• 빅 엔디안 값을 넣어야 함
• 직접 변환한 포트번호를 이용하자!
servAddr 주소 = ebp – 14h
42. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• 포트번호로 사용한 12345는 16진수로
0x3039
• -> 빅엔디안으로 넣으려면 코드에
0x3930를 써야 한다
• 메모리에 저장될 때 리틀 엔디안으
로 바뀌면서 자동으로 순서가 바뀜
servAddr 주소 = ebp – 14h
43. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• 주요 코드를 어셈블리어로 어떻게 써야할 지 보자
• bind 함수
• 소켓 디스크립터와 방금 쓴 sockaddr_in 구조체의 주소, 그리고
sockaddr_in 의 크기를 인자로 넣는다
• sockaddr_in 구조체 크기는 0x10임에 주목
44. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• 백업해둔 소켓 디스크립터와 구조체 시작 주소를 넣었다.
• bind 이후에는 서버 주소 정보를 이용하지 않는다
• -> 백업용으로 이용하던 esi를 자유롭게 사용 가능
45. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• 주요 코드를 어셈블리어로 어떻게 써야할 지 보자
• listen 함수
• 소켓 디스크립터와 적당한 대기 큐 개수를 넣는다.
47. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• 주요 코드를 어셈블리어로 어떻게 써야할 지 보자
• accept 함수
• 연결중인 클라이언트 정보 (sockaddr_in 구조체)를 저장할 공간을 고
려해야 한다
• 구조체 크기는 0x10임에 주목
• 구조체 크기는 숫자로 바로 넣을 수 없음.
• 0x10를 값으로 갖는 DWORD 크기의 메모리를 이용해야 함
• 반환되는 eax는 이번 연결에서의 실질적인 통신을 위해 사용되는 소
켓 디스크립터이다. 잘 백업해 두자.
• 연결이 끝나면 버려도 됨
49. WINDOWS PORT BINDING 셸 코드 – 포트 열기 – 실습
• 포트 열기
• 오른쪽은 클라이언트 코드다. 서버가 작동하는지 간단히 실험을 해 보자.
• 간단히 netcat을 이용하는 방법도 있다
• nc ip(또는 도메인) port 형식으로 연결 요청
50. WINDOWS PORT BINDING 셸 코드 - 파이프
• 포트로 들어오는 입력을 cmd에 전달하고, cmd의 결과를 포트로 내보내 보자
• 파이프를 이용
• (리눅스는 dup2함수를 이용할 수 있다. 윈도우는 cmd에서 이게 잘 안 먹혀서 파이프를 이용함)
대상 컴퓨터
공격자
port
cmd
stdin stdout
port
<Client>
<Server>
51. WINDOWS PORT BINDING 셸 코드 - 파이프
• 파이프?
• IPC(Inter Process Communication)의 일종
• 윈도우 파이프에는 두 종류가 있음
• 이름없는 파이프와 이름있는 파이프
• 그 중 이름없는 파이프를 이용할 것
• 이름없는 파이프
• 수도 파이프와 유사
• 파이프 양쪽 끝에 구멍이 있고, 그 중 한쪽 구멍을 통해서 들어간 물체가 반대쪽 구멍으로 나올 수 있음
• 각 구멍은 하나의 용도(입력 또는 출력)로 이용 가능
• 단방향 통신
• 관계가 있는 프로세스 사이의 통신에 유용(부모-자식 관계 등)
• 자식 프로세스의 표준 입출력을 파이프로 변경할 수도 있음
52. WINDOWS PORT BINDING 셸 코드 – 파이프 – 실습1
• 익명 파이프 사용 예
• 파이프를 생성한 뒤, 쓰기 전용 입구에 WriteFile 함수 호출
• WriteFile은 원래 파일을 쓰는 함수
• 파일을 열어 얻는 핸들 대신 쓰기 전용 파이프를 줌
• -> 파이프에 씀
• ReadFile 함수 호출
• ReadFile은 원래 파일을 읽는 함수
• 파일 핸들 대신 읽기 전용 파이프를 줌
• -> 파이프의 내용을 읽음7500f180 7500f090
53. WINDOWS PORT BINDING 셸 코드 – 파이프 – 실습2
• 익명 파이프 사용 예
• 부모-자식 관계에서 파이프를 이용하여 통신하기 예
• 부모 프로세스가 ipconfig.exe의 프로세스를 생성한다
• 자식 프로세스는 원래 stdout에 출력하던 결과를 파이프로 전달한다
• 부모 프로세스가 파이프로 결과를 받아서 자신의 stdout에 출력한다
55. WINDOWS PORT BINDING 셸 코드 – 파이프 – 실습2
• 부모-자식 관계에서 파이프 공유하기 예
• 공유 가능한 파이프를 만들기 위한 설정
56. WINDOWS PORT BINDING 셸 코드 – 파이프 – 실습2
• 부모-자식 관계에서 파이프 공유하기 예
• 표준 입출력을 이와 같이 파이프로 지정할 수 있다
• 표준 입출력을 재지정하고 싶다면 WinExec 대신
CreateProcessA/W 함수를 이용해야 한다.
57. WINDOWS PORT BINDING 셸 코드 – 파이프 – 실습2
• 앞에서 설명한 코드의 어셈블리 코드를 보고, 셸 코드를 짜보자.
• 먼저 셸코드 보호를 위한 스택을 확보한다.
• 편의를 위해 사용할 함수 주소를 직접 찾아 이용했다
• 원래는 universal 셸 코드로, 직접 주소를 얻어와야 함
58. WINDOWS PORT BINDING 셸 코드 – 파이프 – 실습2
• 파이프가 상속될 수 있도록 보안 설정 구조체를 만들고, 내용을 쓴다
• 구조체 값을 넣을 때 그 위치에 주의한다.
• 구조체 크기는 0xC
• nLength의 위치는 구조체 시작주소
• 크기 : 4바이트
• lpSecurityDescriptor의 위치는 시작주소 + 4
• 크기 : 4바이트
• bInheritHandle의 위치는 시작주소 + 8
• 크기 : 4바이트
• BOOL 자료형은 int를 재정의한 것
• CreatePipe 함수에 인자로 구조체의 시작 주소를 넣어야 함에 주의한다.
• 잘 백업해 둘 것
59. WINDOWS PORT BINDING 셸 코드 – 파이프 – 실습2
• CreatePipe함수를 호출한다
• 방금 만든 구조체의 주소와 Read/Write 파이프 핸들을
인자로 준다
• 인자로 넣은 Read/Write 파이프 핸들은 이후 이용할 것
이므로 잘 백업해 두어야 한다
• 보안설정 구조체의 주소는 더이상 기억해 둘 필요가 없
으므로 백업해 둔 레지스터는 자유롭게 이용 가능하다
• esi 이용 가능
60. WINDOWS PORT BINDING 셸 코드 – 파이프 – 실습2
• CreateProcess 함수에 넣을 구조체를 설정한다
• PROCESS_INFORMATION 구조체는 실행 후 정보를 받아오는 구조체
• 따로 설정할 것 없이 대충 적당한 주소만 넣어주면 됨
• STARTUPINFO는 실행할 프로그램에 설정을 해주는 구조체
• 먼저 GetStartupInfo 함수로 내용을 채운 뒤 설정을 더한다.
• dwFlags의 위치는 시작주소 + 0x2C
• 크기 : 4바이트
• 0x101을 값으로 설정
• hStdInput의 위치는 시작주소 + 0x38
• 크기 : 4바이트
• 위에서 생성한 핸들의 값을 넣어 줌
• hStdOutput의 위치는 시작주소 + 0x3C
• 크기 : 4바이트
• 위에서 생성한 핸들의 값을 넣어 줌 (write)
• hStdError의 위치는 시작주소 + 0x40
• 크기 : 4바이트
• 위에서 생성한 핸들의 값을 넣어 줌 (write)
• wShowWindow의 위치는 시작주소 + 0x30
• 크기 : 2바이트
• hide로, 0을 설정
61. WINDOWS PORT BINDING 셸 코드 – 파이프 – 실습2
• CreateProcess 함수를 호출
한다
• 백업해 둔 주소들을 이용
• 실행할 프로그램 문자열을 스택
에 저장하고, 이용
• 함수 호출 뒤에는 백업이 더이상
필요하지 않으므로 백업용으로
사용하던 레지스터를 이용할 수
있게 됨
• esi, ebx 사용 가능
62. WINDOWS PORT BINDING 셸 코드 – 파이프 – 실습2
• 파이프에서 데이터 가져오기
• Read 핸들을 이용하여 파이프 버퍼에 있는 데이터를 가져온다
63. WINDOWS PORT BINDING 셸 코드 – 파이프 – 실습2
• 파이프에서 데이터 가져오기
• 문자열 읽기로 읽을 수 있도록 null을
넣어주고, printf한다.
• 문자열 읽기를 위한 포맷스트링을
만들고, push했다.
• 정상 종료를 위해 맨 마지막에
ExitProcess를 호출해주자.
64. WINDOWS PORT BINDING 셸 코드 – 실습
• Port binding 셸 코드
• 파이프는 2개 필요하다
• 공격자가 입력한 명령어를 cmd의 stdin에 넣는 용도
• cmd의 결과(stdout)를 공격자에게 보내는 용도
• 대상 컴퓨터측 동작
• 공격을 당해 셸 코드를 실행하는 프로그램 : A라 하자
• A가 패킷을 read -> A가 파이프1 write -> cmd가 파이프 1 read -> cmd에서 처리 -> cmd가 파이프2 write -> A가 파이프2 read -> A가 패킷을 write
대상 컴퓨터
공격자
port
cmd
stdin stdout
port
<Client>
<Server>
65. WINDOWS PORT BINDING 셸 코드 – 실습
• Port binding 셸 코드
• 이제 포트를 열고 – 파이프를 연결하고 – cmd를 실행하고 – 원격으로 cmd를 이용하는 코드를 짜보자.
• 셸 코드 로직은 앞으로 나올 코드와 같다.
• 어셈블리로 정제, Universal 셸 코드로 만들기, NULL 없애기 과정을 거치면 실제로 사용 가능한 셸 코드가 완성된다.
69. WINDOWS PORT BINDING 셸 코드 – 실습
• Port binding 셸 코드
• 중요 부분
• PeekNamedPipe 함수는 이름있는/이름없는 파이프 버퍼를 복사하
는 기능을 한다
• 파이프 버퍼를 청소하지 않고
• ReadFile을 호출하면 데이터가 들어올 때까지 Block상태가 된다.
• ->동기 함수(blocking function)
• PeekNamedPipe는 바로 반환된다
• -> 이 함수의 결과에 따라 ReadFile을 호출하거나 하지 않으면 됨
71. WINDOWS REVERSE CONNECTION 셸 코드
• 포트 바인딩 셸 코드의 단점
• Exploit을 이용한 공격 대상은 주로 서버가 되는데, 대부분의 서버 앞에는 방화벽이 있음
• 막상 포트를 열어도 접속을 하지 못하는 경우가 생김
• Reverse Connection 셸 코드
• 대부분의 방화벽은 외부에서의 접속(Inbound)은 차단하지만, 내부에서 외부로 접속(Outbound)하는 것은 차단하지 않는다
• 이 점을 이용하여, 타겟 시스템(내부망)에서 공격자의 PC(외부망)으로, 거꾸로 접속하도록 하는 셸코드가 Reverse Connection 셸 코드
• 공격자가 서버, 타겟이 클라이언트
• Connect-back 셸 코드라 하기도 한다.
72. WINDOWS REVERSE CONNECTION 셸 코드
• 기본적으로 Port binding 셸 코드와 비슷한 형태
• 공격자가 server고, 대상 컴퓨터가 client가 된다는 점이 다름
대상 컴퓨터
공격자
port
cmd
stdin stdout
port
<Server>
<Client>
74. WINDOWS REVERSE CONNECTION 셸 코드 – 실습
• port binding 셸 코드에서 서버를 여는 부분을 삭제하고,
대신 외부 서버에 연결하는 코드를 넣었다
• 파이프 생성/cmd 실행 코드는 동일하다
• cmd 명령어를 전달하는 부분에서, accept 코드가 빠졌다.
77. LINUX 환경에서 셸 코드 제작
• 리눅스에서 사용 가능한 셸 코드를 작성하기 위해서는
리눅스 환경에서 셸 코드를 작성할 필요가 있다.
• gcc와 objdump를 이용
• 오른쪽은 셸을 실행하는 코드를 C언어 수준에서 쓴 것
• execve로 sh 프로그램을 실행
• 메인 함수 인자로 프로그램 이름을 넣을 수 있게 함
• 32비트를 기준으로 만들 것
• gcc에 옵션으로 m32를 준다.
• 동적 링킹된 코드는 복잡하다. 함수 코드를 직접 보고
중요한 코드를 떼어올 것이므로 찾기 쉽게 정적 링킹
한다.
• gcc에 옵션으로 static을 준다.
78. LINUX 환경에서 셸 코드 제작
• objdump에 옵션으로 d를 주면 디스어셈블한 코드를 뽑아준다.
• grep으로 원하는 부분만 잡는다
• A 옵션을 주고 뒤에 숫자를 써 주면, 찾은 곳으로부터 숫자만큼의 instruction을
뽑아준다.
• 필요한 코드를 찾아서 다시 어셈블리어로 코딩한다.
• 필요에 따라 많은 수정이 필요
79. LINUX 환경에서 셸 코드 제작 – 실습
• 필요한 문자열을 스택에 저장하고, 그 주소를 넘길 수 있도
록 할 것
• 문자열을 스택에 넣는 코드를 쓰기 위해 아스키코드를 구했다.
• push를 이용하여 스택에 문자열을 넣어주고, 문자열 주소를
백업해 둔다.
80. LINUX 환경에서 셸 코드 제작 – 실습
• sh의 메인 함수 인자로 줄 배열이 필요함
• 문자열 주소와, NULL 이 들어간 포인터 배열이 필요
• 배열을 저장하는 곳이 스택이라는 점에 주의
• NULL을 먼저 push, 그 다음 문자열 주소를 push
• 배열 주소를 백업해 둔다.
81. LINUX 환경에서 셸 코드 제작 – 실습
• execve는 시스템 콜에 속한다.
• 프로그램을 로딩하기 위해서는 OS가 필요하기 때문
• 한 번 직접 들어가서 코드를 보자
• 처음 execve를 호출할 때, 함수 인자로 3가지를 push한다.
• 실행할 프로그램 문자열 주소, sh의 메인 함수 인자, NULL
• push는 역순으로 0, 메인함수 인자 주소, 문자열 주소
• execve 함수 내부에서, 다시 적절한 레지스터에 인자 값을 배치해
준다.
• edx에 환경변수(여기선 NULL), ecx에 메인함수 인자 배열 주소, ebx에 실행할 프로그램
경로 주소
• eax에 0xb를 설정하고 함수 호출
• 호출한 내부 함수에서는 인자를 push한 뒤 시스템콜을 한다.
• push는 그냥 백업용임. 그냥 int $0x80하면 됨.
• 시스템콜 번호는 이미 eax에 설정되어 있음
82. LINUX 환경에서 셸 코드 제작 – 실습
• execve의 핵심 코드를 알아냈으니 구태여 execve를 호출할 필요 없이,
직접 시스템 콜을 이용할 수 있음
• (라이브러리 의존성을 없앴음)
• edx에 환경변수(여기선 NULL) 설정
• ecx에 메인함수 인자 배열 주소 설정(코드 처음부터 그렇게 설정했음)
• ebx에 실행할 프로그램 경로 주소 설정(코드 처음부터 그렇게 설정했음)
83. LINUX 환경에서 셸 코드 제작 – 실습
• 실행해 보면 잘 작동하는 것을 확인할 수 있다.
• 이제 objdump를 이용하여 기계어를 추출, 셸코드를 제작한다.
84. LINUX 환경에서 셸 코드 제작 – 실습
• 만든 셸 코드를 테스트하는 코드
• gcc에 옵션으로 –z execstack 을 써준다
• DEP 해제(stack에 있는 코드 실행 방지 끔)
• Data Execution Protection
• 리눅스는 윈도우처럼 동적 라이브러리에서 함수 주소를 찾는 universal 셸
코드를 만드는 대신 바로 시스템 콜을 이용하는 경우가 많다.
• 시스템 콜을 사용하는 경우 시스템 콜의 번호와 사용 방식에만 의존성이
있고, 라이브러리에 대한 의존성은 없음
• NULL문자를 없애고, 크기를 줄이는 작업을 하면 더욱 정제할 수 있다.
86. LINUX PORT BINDING 셸 코드
• 셸 코드에 추가로 필요한 기능
• 포트 열게 하기
• 포트로 들어오는 명령어를 셸에 전달하게 하기
• 출력을 포트로 전달하게 하기
대상 컴퓨터
공격자
port
sh
stdin stdout
port
<Client>
<Server>
87. LINUX PORT BINDING 셸 코드 – 포트 열기 – 실습
• 오른쪽 코드는 기본적인 서버 오픈 코드이다.
• gdb를 이용하여 핵심 코드의 디스어셈블 코드를 보고, 어셈블리어로 다시 써 보자
• 본격적인 루틴 전에, 스택을 위한 코드를 추가한다.
88. LINUX PORT BINDING 셸 코드 – 포트 열기 – 실습
• socket, bind, listen, accept, close 함수는 전부 시스템 콜이다.
• 102번 시스템 콜인 socketcall()을 이용하면 소켓과 관련된 시스템 콜을 이용할 수 있다.
• 인자로 socket 관련 함수 중 어떤 함수를 사용할 지를 선택하고, 해당 함수의 인자들을 받는다.
• Ex) EAX에 102를 설정하고, EBX에 2를 설정하고 ECX에 인자 포인터를 넣고 시스템 콜을 하면 bind 함수를 호출
89. LINUX PORT BINDING 셸 코드 – 포트 열기 – 실습
• socket 함수
• SOCK_STREAM이 1로 정의되어 있고, PF_INET이 2로 정의되어
있음을 알 수 있다.
• socket함수의 반환 값인 소켓 디스크립터는 eax에 있다.
90. LINUX PORT BINDING 셸 코드 – 포트 열기 – 실습
• socket 함수 내부
• EBX에 1 설정 (socketcall의 타입 설정)
• ECX에 socket 인자가 들어있는 주소를 넣고, 그 주소에는 2,
1, 0(socket 인자)가 들어간다.
• EAX에 0x66 (십진수로 102)를 설정한다.
• 이 상태로 시스템 콜을 하는 함수를 호출한다
• 여기서 사용하는 push는 단순히 레지스터 값을 백업해
두는 것일 뿐임
• 다시 복원하는 코드로 추론 가능
• ->결론
• EAX에는 시스템 콜 번호인 0x66
• EBX에는 socketcall의 타입 설정
• ECX에는 주소를 넣고, 해당 주소에는 함수의 인자를 넣
는다.
92. LINUX PORT BINDING 셸 코드 – 포트 열기 – 실습
• memset
• memset 호출 대신 직접 0으로 스택을 정리
하고, 서버 주소를 스택에 써 준다.
• 디스어셈블리 코드를 통해 주소 구조체의
크기가 0x10임을 알 수 있다.
93. LINUX PORT BINDING 셸 코드 – 포트 열기 – 실습
• 서버 주소 설정
• sin_family는 AF_INET이며 이 값은 2이고, 2바이트 크기
• sin_port는 직접 12345를 빅 엔디안으로 변환한 0x3930을 넣
으면 된다. 2바이트 크기
• sin_addr는 INADDR_ANY이며 이 값은 0이고, 4바이트 크기
• sin_addr가 가장 높은 주소, sin_family가 낮은 주소임을 주의
94. LINUX PORT BINDING 셸 코드 – 포트 열기 – 실습
• 서버 주소 설정
• memset과 설정을 어셈블리어로 썼다.
95. LINUX PORT BINDING 셸 코드 – 포트 열기 – 실습
• bind 함수
• 인자로 앞서 백업해 둔 소켓 디스크립터
와 서버 주소 구조체의 주소, 그리고 서버
주소 구조체의 크기를 준다.
• 구조체 크기는 0x10
• socket함수와 같이 시스템 콜 번호를 설정
하고, 인자 배열의 주소를 넘기면 된다.
97. LINUX PORT BINDING 셸 코드 – 포트 열기 – 실습
• listen 함수
• 인자로 앞서 백업해 둔 소켓 디스크립터와 개수를 넣는
다.
• 같은 방식으로 시스템 콜
98. LINUX PORT BINDING 셸 코드 – 포트 열기 – 실습
• listen 함수
• listen 코드를 어셈블리어로 썼다.
99. LINUX PORT BINDING 셸 코드 – 포트 열기 – 실습
• accept 함수
• accept함수에는 구조체 크기를 저장하는 int 크기의 변수 주소를 넣어야 한다.
• 스택에 공간을 확보하고 0x10을 써 줌
• 인자로 소켓 디스크립터, 클라이언트 주소 구조체가 저장될 주소, 구조체 크기 변수 주소를 넣는다.
100. LINUX PORT BINDING 셸 코드 – 포트 열기 – 실습
• accept 함수
• accept 코드를 어셈블리어로 썼다.
101. LINUX PORT BINDING 셸 코드 – DUP2
• 포트로 들어오는 입력을 sh에 전달하고, sh의 결과를 포트로 내보내 보자
• Windows와 다르게, dup2 시스템 콜을 이용하여 간단하게 stdin과 stdout을 조정할 수 있다.
대상 컴퓨터
공격자
port
sh
stdin stdout
port
<Client>
<Server>
102. LINUX PORT BINDING 셸 코드 – DUP2
• dup2
• 파일 디스크립터 복사본을 만든다.
• dup()는 커널에서 알아서 사용하지 않는 디스크립터 번호 중에 하나가 자동으로 지정되지만, dup2()는 프로그래머가 원하는 번
호를 지정할 수 있다.
• dup2(fd, stdout) 할 시, 원래 stdout으로 오던 자료가 fd로 들어 감
• 시스템 콜 63번
103. LINUX PORT BINDING 셸 코드 – DUP2 – 실습1
• 간단히 사용법을 알아보자
• stdout의 파일 디스크립터인 1이 fd가 가리키는 파일을 가리키
도록 했다.
• 파일디스크립터 1을 이용하여 나오는 출력은 전부 파일에 쓰
이게 된다.
104. LINUX PORT BINDING 셸 코드 – DUP2 – 실습2
• 앞에서 쓴 서버 코드에 dup2를 추가한다.
• gdb를 이용하여 핵심 코드의 디스어셈블 코드를 보고, 어셈블리어로 다시
써 보자
105. LINUX PORT BINDING 셸 코드 – DUP2 – 실습2
• dup2 함수
• 바로 위에서 호출한 accept 함수는 eax에 accept하여 만든 소켓
디스크립터를 저장하고 있다.
106. LINUX PORT BINDING 셸 코드 – DUP2 – 실습2
• dup2 함수
• dup2는 시스템 콜이다. 직접 들어가서 중요한 코드만 떼어내어 보자.
• 인자로 넣은 수정할 fd(stdin, 0으로 정의됨)는 ecx에, 소켓 파일디스크립터는 ebx에 저장된다. eax에는 0x3f(10진수로 63)이 들어간다.
107. LINUX PORT BINDING 셸 코드 – DUP2 – 실습2
• dup2 함수
• 앞서 쓴 코드의 맨 끝에 이어
쓴다.
• dup2함수의 핵심 코드만 떼어
내어 썼다.
• stdin과 stdout stderr 전부 다
dup2함수 적용
• 각각 파일 디스크립터 0, 1,
2로 정의되어 있음
108. LINUX PORT BINDING 셸 코드 – 이전에 만든 셸 코드와 합치기
• 셸을 여는 코드를 추가한다.
• 앞에서 만든 코드를 그대로 붙여 넣음
109. LINUX PORT BINDING 셸 코드 – 이전에 만든 셸 코드와 합치기
• 마지막으로 셸을 여는 코드 어셈
블리어를 붙여서 완성한다.
110. LINUX PORT BINDING 셸 코드 – 이전에 만든 셸 코드와 합치기
• nc를 이용하여 작동됨을 확인해보고, objdump를 이용하여 기계어를
떼어내어 이용해 보자.
• 포트가 열려 있는지 확인하는 법
• netstat –nap | grep 포트번호
• 특정 포트를 사용하는 프로그램 죽이기
• fuser –k –n tcp 포트번호
112. LINUX REVERSE CONNECTION 셸 코드
• 기본적으로 Port binding 셸 코드와 비슷한 형태
• 공격자가 server고, 대상 컴퓨터가 client가 된다는 점이 다름
• port binding에서 서버를 여는 코드를 서버에 접속하는 코드로 변경하면 된다.
대상 컴퓨터
공격자
port
sh
stdin stdout
port
<Server>
<Client>
116. METASPLOIT FRAMEWORK
• Metasploit이란?
• 침투 테스트를 위한 Framework
• 간단히 명령어를 조합하여 편하게 해킹이 가능하게 함
• 이미 만들어져 있는 모듈을 이용
• 특정 취약점에 대한 공격코드(페이로드), 셸 코드 등 제공
• Metasploit에서 사용 가능한 형태로 직접 모듈을 만들어 넣어두면 후에 편하게 재이용 가능
• 칼리 리눅스에 기본으로 설치되어 있음
• 다른 리눅스나 윈도우에도 설치 가능
• 이번에는 간단하게 셸 코드 제작과 인코딩에 관련된 내용만 알아볼 것
117. METASPLOIT FRAMEWORK를 이용한 셸 코드 제작
• Metasploit Framework를 이용한 셸 코드 제작
• 직접 제작은 아니고, 이미 만들어진 셸 코드 중 원하는 기능을 가진 셸 코드를 얻는 방법이다
• msfvenom을 이용
• 옛날 버전의 msfpayload와 msfencode가 합쳐진 것. 최신 버전의 Metasploit Framework를 설치하면 있다.
• 옛날 책에서 위의 프로그램을 이용한다면, 그 대신 msfvenom을 이용해야 한다.
• 각종 옵션을 설정하여 원하는 형태의 셸 코드를 얻을 수 있다.
118. METASPLOIT FRAMEWORK를 이용한 셸 코드 제작
• Metasploit Framework를 이용한 셸 코드 제작
• msfvenom –l 명령어를 입력하면 사용할 수 있는 페이로
드들의 리스트가 나온다
• 경로와 설명
• 이 중 원하는 것을 선택한다
• 앞에서 만들었던 Windows shell port binding 셸 코드
를 이용해 보자
• 칼리리눅스에는 기본 경로가 잡혀 있기 때문에 명령어
로 msfvenom 프로그램을 실행할 수 있다.
119. METASPLOIT FRAMEWORK를 이용한 셸 코드 제작
• Metasploit Framework를 이용한 셸 코드 제작
• msfvenom –p 로 payload를 설정한다
• 그 다음 줄에 위에서 찾은 경로를 적어준다
• 페이로드의 옵션으로 포트를 설정했다.
• -f로 format을 설정한다.
• 뒤에 c를 적음 : c언어에서 바로 이용할 수 있는 셸 코드
• python을 적으면 python 에서 바로 쓸 수 있는 형태로
나오고..
• 언어/실행파일 종류는 밑과 같음
• 복사해도 되지만, 보통 맨 뒤에 >code.c 등을 붙여 코드를
파일로 저장
120. METASPLOIT FRAMEWORK를 이용한 셸 코드 제작
• Metasploit Framework를 이용한 셸 코드 제작
• 위에서 만든 셸 코드를 실행해보자
• 사실 잘 안되는 경우도 많다
• OS 버전이나, 사소한 환경 등의 차이로 인해
• 애초에 잘못된 코드가 있을 가능성도 있음
121. METASPLOIT FRAMEWORK를 이용한 셸 코드 제작
• Windows에서도 같은 방법으로
Metasploit을 이용할 수 있다
• 오른쪽은 Windows power shell에
서 msfvenom을 실행한 것
122. METASPLOIT FRAMEWORK를 이용한 셸 코드 인코딩
• Metasploit Framework를 이용한 셸 코드 인코딩
• 셸 코드에 특정한 문자가 들어가지 않기를 원할 때, 인코딩을 이용할
수 있다
• null문자 없애기에 유용하게 이용할 수 있다
• 셸 코드에서 제거되어야 하는 문자를 bad char라고 부름
• Metasploit은 여러 인코더를 제공하기 때문에, 원하는 상황에 맞춰
골라 쓸 수 있다.
• null 문자를 없애는 용도 뿐만 아니라 많은 용도로 이용된다
• Ex) 백신 우회, 크기 줄이기 등
123. METASPLOIT FRAMEWORK를 이용한 셸 코드 인코딩
• Metasploit Framework를 이용한 셸 코드 인코딩
• -b 옵션을 이용해 bad char를 설정했다
• 셸 코드가 0x00과 0xff가 없도록 인코딩 되었다
124. METASPLOIT FRAMEWORK를 이용한 셸 코드 인코딩
• Metasploit Framework를 이용한 셸 코드 인코딩
• -e 옵션을 이용해 인코더를 지정했다
• fnstenv_mov 인코더는 부동소수점을 위한 명령어를 이용하도록
인코딩한다
126. 참고 사이트
• Windows 환경에서의 Buffer Overflow 공격 기법
• http://research.hackerschool.org/data/WBOF/WBF.htm
• http://dest.kr/c-c/windows-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EC%9D%98-buffer-overflow-
%EA%B3%B5%EA%B2%A9-%EA%B8%B0%EB%B2%95/
• PeekNamedPipe 함수와 ReadFile
• http://tksssch29.tistory.com/entry/cmdexe-%EC%99%80-pipe%EC%99%80%EC%9D%98-%ED%86%B5%EC%8B%A0
• Exploit Database – 미리 만들어진 셸 코드들
• https://www.exploit-db.com/shellcode/
• Metasploit 설치
• https://github.com/rapid7/metasploit-framework/wiki/Nightly-Installers
• msfvenom 기본 사용 정리
• http://blog.naver.com/PostView.nhn?blogId=knq1130&logNo=220621445199
127. 참고 사이트
• dup2 함수
• http://forum.falinux.com/zbxe/index.php?document_srl=520937&mid=C_LIB
• http://sosal.kr/186
128. 참고 서적
• 뇌를 자극하는 윈도우즈 시스템 프로그래밍
• Chapter 8 프로세스간 통신 2 _ 파이프 방식의 IPC
• 윈도우 시스템 해킹 가이드
• Part 3 쉘코드 원리와 작성