4. 코드 인젠션이란?
• 실행중인 대상 프로세스에 코드를 삽입한 후 실행하는 기법
• 일반적으로 CreateRemoteThread API를 이용하기 때문에Thread 인젝션이라고도 함
• 코드는 쓰레드 프로시저(쓰레드를 만들 때 인자로 넣는 함수, 쓰레드 메인)으로 넣고, 인자는 쓰
레드의 파라미터로 전달한다.
• 코드와 데이터를 각각 인젝션 해주는 것
<injector> <대상
프로그램>
필요한 문자열
함수 포인터
내가 만든 코드
이용
inject!
5. DLL INJECTION VS CODE INJECTION
• DLL injection은 DLL 자체를 프로세스에 올린다.
• DLL의 코드에서 사용하는 모든 데이터는 DLL의 데이터 영역에 있다.
• Ex) 문자열 등...
• -> 따로 데이터를 injection할 필요가 없다.
• Code injection은 코드 뿐만 아니라 코드에서 이용하는 데이터도 같이 인젝션 해 주어야 한다.
• 코드 인젝션을 사용하는 이유
• 메모리를 조금만 차지한다.
• DLL 인젝션보다 작은 공간을 차지한다.
• 흔적을 찾기 어렵다
• DLL 인젝션은 해당 프로세스 메모리에 흔적을 남기기 때문에 간단히 인젝션 여부를 알 수 있다.
• 코드 인젝션은 쉽게 흔적을 남기지 않는다. (알아내는 방법이 있긴 하다)
• 기타
• 코드 인젝션은 DLL 파일이 필요하지 않다. 인젝션하는 프로그램만 필요하다(injector)
• DLL injecton은 규모가 크고 복잡한 일을 수행할 때, Code injection은 규모가 작고 간단한 일을 수행할 때 사용한다.
6. CODE INJECTION – 원격 쓰레드를 이용 – 고급 언어로 프로그래
밍
• 쓰레드 인자로 넣을 구조체의 형식이다.
• 이 구조체 모양대로 내용을 써서 대상 프로그
램의 메모리에 써 둘 것이다.
• 쓰레드를 만들 때, 이 구조체를 저장해 둔 대상
프로그램의 가상메모리 주소를 인자로 준다.
• 대상 프로그램은 자신의 가상 메모리에 저장된 데이터를
이용하여 일을 할 수 있다.
7. • 함수 포인터를 이용하기 쉽도록 재정의하는 코드
이다.
CODE INJECTION – 원격 쓰레드를 이용 – 고급 언어로 프로그래
밍
8. • 위에 있는 두 줄은 함수 선언이다.
• 아래는 메인 함수이다.
• 인자로 PID를 입력 받는다.
CODE INJECTION – 원격 쓰레드를 이용 – 고급 언어로 프로그래
밍
9. • 인젝션할 코드이다.
• 인자로 받은 주소를 캐스팅한다.
• 미리 저장해 둔 함수 포인터(LoadLibraryA와 GetProcAddress)
와 문자열(user32.dll, MessageBoxA) 이용하여
user32.dll(MessageBoxA API가 저장된 DLL)을 열고,
MessageBoxA 함수 주소를 얻는다.
• 미리 저장해 둔 문자열을 이용하여 MessageBoxA함수에 인
자를 넣고 함수를 호출한다.
• kernel32.dll은 대부분의 프로그램이 이용하고, kenel32.dll과
같은 시스템 라이브러리는 모든 프로세스가 같은 가상메모
리에 매핑하기 때문에 injector에서 주소를 구해 넣어도 괜찮
았다.
• kernel32.dll을 이용하지 않는 dll도 있으니 code injection하기 전에
확인해야 한다.
CODE INJECTION – 원격 쓰레드를 이용 – 고급 언어로 프로그래
밍
10. • 본격적으로 코드를 인젝션하는 함수이다.
• 일단 대상 프로세스에 넣을 구조체(쓰레드 인자로 이용될)
에 내용을 적는다.
• 아래 구조체 모양을 참고해 보자.
• pFunc은 함수 포인터(주소)가 들어갈 공간이다.
• LoadLibraryA함수와 GetProcAddress함수의 주소를 구
해 넣었다.
• 이들은 MessageBoxA를 호출하기 위해서 구한 것이
다.
• szBuf는 문자열들이 들어갈 공간이다.
• 이들은 LoadLibraryA의 인자로 줄 DLL 이름과
GetProcAddress의 인자로 줄 함수 이름, 그리고
MessageBoxA를 호출할 때 인자로 줄 문자열들의 이
름이다.
• 다른 API를 이용하고 싶다면 그 API가 속한 DLL과 함수
CODE INJECTION – 원격 쓰레드를 이용 – 고급 언어로 프로그래
밍
11. • 프로세스를 열고 방금 내용을 채워 넣은 구조체를 그대로 써 준
다.
CODE INJECTION – 원격 쓰레드를 이용 – 고급 언어로 프로그래
밍
12. • 그 다음 인젝션할 코드(함수)의 크기를 구하고, 그 크기만큼 메모
리를 확보하여 내용을 쓴다.
• 인젝션할 코드는 이 함수 바로 다음에 올 것이다.
CODE INJECTION – 원격 쓰레드를 이용 – 고급 언어로 프로그래
밍
13. • 방금 넣은 코드의 주소는 pRemoteBuf[1]이고, 저장한 구조체의 주소는 pRemoteBuf[0]이
다.
• 이들을 인자로 넣어 CreateRemoteThread함수를 호출한다.
CODE INJECTION – 원격 쓰레드를 이용 – 고급 언어로 프로그래
밍
14. •주의!
• 컴파일은 반드시 Release mode로 한다.
• Debug모드로 하면 추가적으로 생성되
는 코드가 있다.(추가적으로 불리는 함
수가 있다)
• 따라서 Debug모드로 컴파일한 코드
를 인젝션하면 대상 프로그램에는 없
는 함수를 호출하기 때문에 에러가 나
고, 프로그램이 종료된다.
CODE INJECTION – 원격 쓰레드를 이용 – 고급 언어로 프로그래
밍
15. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 어셈블리어를 이용하여 인젝션할 코드를
생성하면 조금 더 자유로운 형태로 이용
가능하다.
• 문자열(데이터)과 코드를 함께 넣을 수 있다.
• 올리디버거로 간편하게 만들 수 있다.
• 아무 EXE나 열고 빈 칸에 옆과 같은 내용을 쓰자
• 단, call 주소는 상황에 맞게 쓴다.
• 또는 인라인 어셈블리를 이용해도 됨
17. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 인자로 받은 struct 주소를 가져온다.
• 이번에는 struct에 함수 주소(LoadLibraryA와
GetProcAddress)만 넣었다.
• 이 주소를 하드코딩 하지 않는 이유는
ASLR이라는 기법 때문이다.
• ASLR : Address Space Layout
Randomization.
• 시스템 DLL은 모든 프로그램이 같은
주소의 가상메모리에 올라온다. 하지만
재부팅하면 그 ‘같은 주소’가 될 주소가
바뀐다. 따라서 함수 주소는 매번 구해서
넣는다.
• ESI에는 LoadLibraryA, ESI+4에는
18. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• LoadLibraryA함수를 호출하는 코드이다.
• 사용할 dll 문자열은 user32.dll이다.
19. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• user32.dll 문자열을 스택에 저장한다.
• 리틀 엔디안으로
• 문자열 뒷부분부터 거꾸로
• 맨 마지막에 push esp를 해서 문자열의 맨
처음 부분이 저장된 주소를 스택에
넣는다.
20. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 순서와 리틀 엔디안에 주의한다.
• 문자열 맨 끝에 null (0)이 들어가는 것에
주의한다.
21. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• ESI(인자로 받은 주소)에 저장된 구조체의 첫
번째 멤버인 LoadLibraryA 함수 주소를
이용하여 함수를 호출한다.
22. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• GetProcAddress함수를 호출하는 코드이다.
• 사용할 함수 이름 문자열은 MessageBoxA이다.
• GetProcAddress함수는 WINAPI 함수 호출
규약을 이용한다.
• WINAPI 함수 규약은 stdcall 함수 호출
규약이다.
• -> 함수 인자를 역순으로 push한다
23. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 조금 전과 같은 방법으로, MessageBoxA
문자열을 스택에 저장하고, 주소를 넘긴다.
24. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• LoadLibraryA함수의 반환 값인 user32.dll의
핸들을 push한다.
25. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• ESI+4에 저장된, 구조체의 두 번째 값인
GetProcAddress함수를 호출한다.
26. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 이제 MessageBoxA를 호출할 것이다.
• 문자열은 2, 3번째 인자이다.
• 단순한 push방법을 이용하기엔 골치
아프다.
• push방법 말고 다른 방법을 이용해본다.
27. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 먼저 uType에 해당하는 인자를 push한다.
28. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 만드는 중에는 일단 call 00400000(뒤에 아무
숫자, 짧지 않은 길이의)를 입력해 둔다.
• call을 하면 함수 call 바로 아래 주소(다음에
실행할 코드 주소)를 스택에 push한 뒤
점프한다.
• 사실 여기서 사용하는 주소는 상대
주소이다.
• 기계어에는 자신으로부터 얼마나 떨어진
곳으로 점프하라 라는 정보가 있다.
• 올리 디버거가 알아서 상대 주소로 계산해서
기계어를 만들어 준다.
• 어셈블리어로는 그냥 주소를 써 줘도
된다.
29. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 상대 주소
계산법
JMP 100
EIP : 50
(EIP는 다음에 실행할 코드 주소를
가리킨다.)
100
<어셈블리어
표현>
E9 B0 00 00 00
EIP : 50
(EIP는 다음에 실행할 코드 주소를
가리킨다.)
100
<기계어 표현>
시작주소 45 시작주소 45
• 00000050 + 000000Bo = 100
• 리틀엔디안으로 B0000000
• 주의 : 작은 바이트를 이용하는
short jmp라는 명령어도 있다.
일단은 그냥 점프를 이용하기
위해 4바이트의 주소를 넣자.
30. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 상대 주소
계산법
EIP : 105
(EIP는 다음에 실행할 코드 주소를
가리킨다.)
JMP 45
<어셈블리어
표현>
<기계어 표현>
시작주소 100, 코드 크기 5바이트
시작주소 45
EIP : 105
(EIP는 다음에 실행할 코드 주소를
가리킨다.)
E9 40 FF FF FF
시작주소 100, 코드 크기 5바이트
시작주소 45
• 105 – c0 = 45
• -c0 = 4바이트로 FFFFFF40
• 00000105 + FFFFFF40 = 45
• 리틀 엔디안으로 40FFFFFF
31. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 수정하려는 줄을 클릭한 후 오른쪽키-binary-
edit으로 출력하고자 하는 문자열을 입력한다.
• 뒤에서 두 번째 인자인 메시지 박스 제목을
입력한다.
32. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 맨 끝 null을 입력하는 것을 잊지 말자
33. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 위의 call 목적지가 문자열 바로 다음을
가리키도록 수정한다.
34. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 똑같은 방식으로, 인쇄하고자 하는 문자열을
넣는다.
38. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 파일을 저장하고 헥사 에디터로 연다.
• 기계어를 얻기 위함이다.
39. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 기계어를 복사해서 배열에 넣을 형태로 수정한다.
• 메모장으로 노가다하든 에디터를 쓰든...
• NotePad++같이 매크로를 이용할 수 있는 프로그램을 추천함
• 이것이 쉘코드 제작의 기본이다.
40. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 이번 구조체에는 함수 주소만을 넣는다.
• ThreadProc함수 대신 쉘코드를 넣는다.
45. CODE INJECTION – 원격 쓰레드를 이용 – 어셈블리어를 이
용
• 쉘코드를 0xFF 식의 char 배열로 만들기보다는
위와 같이 스트링으로 쓰는게 더 정석적이다.
• 코드상에서 char 배열 초기화를 제외한
부분은 달라진 것이 없다.
• 취향 따라 쓴다..
46. TIP
• jmp를 다른 방식으로 표현한다면?
• jmp 401000를 아래와 같이 표현할 수 있다.
• push 401000
• ret
• Call을 다른 방식으로 표현한다면?
• push 다음 주소
• jmp 함수 주소
• 이렇게 같은 코드를 다른 방식으로 쓸 수도 있다.
47. 인라인 패치
• 인라인 코드 패치, 인라인 패치
• 원하는 코드를 직접 수정하기 어려울 때 코드 케이브(Code cave)라고 하는 패치 코드를 삽입한 후 실행해 프
로그램을 패치시키는 기법
• 패킹 혹은 암호화 때문에 파일을 직접 수정하기 어려운 경우 많이 사용되는 기법
• 혹은 수정한 코드가 아래 코드를 침범하여 끼워 넣기 애매한 경우에도 이용할 수 있다.
• 매번 프로세스 메모리의 코드를 패치하기 때문에 인라인 코드 패치라고 부른다.
• 파일 자체에서 코드를 수정하는 것이 아니라 파일을 실행한 후 수정하는 것
• 복호화가 끝난 후 가는 OEP를 조작하여 끼워 넣은 코드로 이동하게 한다. 끼워 넣은 코드는 복호화 된 기존 코드에서 수정하고 싶은 부분을 수
정한다.
• 파일 수정을 이용한 Code injection, 그리고 이를 이용한 기존 코드 패치 방법이다.
49. 인라인 패치
일반적인 코드 패치 인라인 패치
대상 파일 파일 & 메모리
횟수 1번 파일에는 1번만
메모리는 실행될 때마다
방법 Direct
(원하는 위치에 직접 패치)
Indirect
(코드 케이브를 미리 설치한 후
메모리에서 원하는 영역이 복
호화 되었을 때 패치)
50. 인라인 패치
• 패치할 프로그램이다.
• result가 3이 아닌 값을 출력하도록 해보자.
• 패치의 효과를 체험할 수 있도록 UPX 패킹을
적용하자.
51. 인라인 패치
• UPX의 디코딩 루틴을 지나 OEP로 점프하는 코드를 찾았다.
• 이 주소는 앞으로 임의로 넣은 코드의 주소로 바꿀 것
• 임의로 넣은 코드는 디코딩 되어 메모리에 올라온 코드를 수정(패치)하는 일
을 한다.
52. 인라인 패치
• 메인문이다.
• printf를 호출하기 전을 수정하고자 한다.
• 끼워 넣을 코드는 맨 밑 코드영역의 null padding 자리에 넣을 것이다.
• 드래그한 코드는 jmp 코드로 수정할 것이다.
• 정상 작동하기 위해서는 이 코드도 필요하므로 복사해 둔다
• 임의의 코드를 실행하면 드래그한 코드가 jmp 406AA0로 수정될 것(406AA0에는 printf 결과를 조작하기 위한 코드를 끼워
넣을 것)
53. 인라인 패치
• 디코딩 루틴 이후 OEP로 점프하는 코드를 아래 추가한 코드로 점프하도록 변경
• 이 점프로 인해 실행되는 코드 : 기존 main 함수의 printf 전 코드를 jmp 코드로 변경하는 코드
• 바뀐 코드로 인해 아래쪽에 추가한 코드가 실행된다.
• jmp코드로 수정하기 위해 지워진 코드를 넣어 정상적으로 작동하도록 한다.
• 추가 하고싶은 코드를 넣어 임의로 변경한다 (이 경우, printf 인자를 조작하는 일)
• 다시 기존 코드로 점프하여 실행을 이어간다.
54. 인라인 패치
• 성공!
• OEP를 수정할 때, 그 점프하는 코드마저 패킹이나 암호화가 적용
되어 있다면, 그 알고리즘을 알아내야 한다.
• 그래야 패킹/암호화를 풀었을 때 제대로 점프할 수 있다.
• 일부러 약간 돌아서 코드를 실행하게 했다.
• 여건이 된다면 다시 아래로 jmp하지 않고 바로 코드만 수정해도Ok.