순수 C\#훅 기능 에 대한 상세 한 설명 실현

8355 단어 hook이루어지다
Hook.Net 방법 에 사용 할 라 이브 러 리 를 발표 합 니 다.코드 의 양 이 많 지 않 고 완전한 C\#코드 가 실현 되 며 비교적 재 미 있 는 기능 입 니 다.여러분 과 함께 토론 하고 싶 습 니 다.
설치:설치-패키지 DotNetDetour
원본 코드:http://xiazai.jb51.net/201701/yuanma/DotNetDetour_jb51.rar
1.이 걸 왜 하고 싶 은 지
hook 하면 모두 가 낯 설 지 않 을 것 입 니 다.바로 함수 의 집행 절 차 를 바 꾸 고 실행 해 야 할 함 수 를 다른 함수 에 가서 실행 하 게 하 는 것 입 니 다.이것 은 매우 유용 하고 재 미 있 는 기능 입 니 다.(예 를 들 어 함수 매개 변수 정 보 를 얻 고 함수 집행 절 차 를 바 꾸 며 함수 집행 시간 을 계산 하 는 등)소프트 중의 주 방 어 를 죽 이 는 원 리 는 바로 hook 입 니 다.hook 차단 함 수 를 통 해 매개 변수 정 보 를 얻어 위험 행위 여 부 를 판단 하지만 이런 프로그램 은 대부분이 C++입 니 다.지금까지 저 는 hook.net 함수 가 가능 한 라 이브 러 리 를 실현 하고 싶 었 습 니 다.인터넷 에서 많이 검색 되 었 지만 이상 적 이지 않 아서 스스로 실현 하고 싶 었 습 니 다.
2.실현 원리
저 는 inline hook 방식 을 사 용 했 습 니 다.저 는.net 가상 컴퓨터 와 일부 내부 의 구조 에 대해 잘 모 르 고 어떤 것들 은 문 서 를 찾 지 못 하기 때문에 원생 코드 의 inline hook 방식 으로 이 루어 졌 습 니 다.
먼저 inline hook 의 기본 원 리 를 말씀 드 리 겠 습 니 다.함수 수정 전 5 바이트 명령 을 통 해 jmp xxxxxx 로 이 루어 집 니 다.예 를 들 어 C\#방법:

windbg 디 버 깅 으로 방법 정보 보기:

jit 가 있 는 원본 코드 보기:

여기 주소(0x008c 0640)는 MethodInfo.MethodHandle.GetFunction Pointer().Topoiter()방법 으로 얻 을 수 있 습 니 다.
여기까지 와 서 우 리 는 push ebp 에서 시 작 된 5 개의 바이트 가 jmp 도약 명령 을 수정 하고 우리 자신의 함수 에 뛰 어 들 면 hook 의 목적 을 달성 할 수 있다 는 것 을 알 게 되 었 다.그러나 우리 의 함수 가 실 행 된 후에 우리 가 실행 절 차 를 차단 하려 고 하지 않 는 다 면 우 리 는 최종 적 으로 원 함 수 를 다시 호출 해 야 하지만 원 함 수 는 이미 수정 되 었 다.이것 이 생각 나 는 방법 은 수 정 된 5 바이트 명령 을 복원 하 는 것 입 니 다.그러나 이것 은 또 다른 문 제 를 일 으 킬 수 있 습 니 다.바로 우리 가 회복 할 때 마침 다른 스 레 드 가 이 함수 에 호출 되면 프로그램 이 무 너 지 거나 함수 호출 을 빠 뜨 린 다 는 것 입 니 다.수정 할 때 다른 스 레 드 를 멈 추고 달 리 는 CPU 가 이 5 바이트 를 실행 한 후에 명령 을 회복 하 는 것 이 좋 은 방법 일 수도 있 지만 실현 하기 쉽 지 않 고 성능 에 영향 을 줄 것 같 아서 저 는 이런 방법 을 포기 했 습 니 다.
그러면 어떻게 해야만 수정 전의 함 수 를 호출 할 수 있 습 니까?저 는 먼저 C 에서 누 드 함 수 를 쓰 는 방식 이 라 고 생각 합 니 다.즉,자신 이 어 셈 블 리 로 원 함 수 를 조합 해서 실행 하 는 것 입 니 다.
원 함수 전 5 바이트 명령+jmp 점프 명령
그러나 이것 도 불가능 하 다.똑똑 한 사람 은 그림 에서 보 여 준 함수 의 앞 5 바이트 가 완전한 어 셈 블 리 명령 이 아니 라 서로 다른 함수,길이 가 다르다 는 것 을 알 게 되 었 다..net 의 함 수 는 일부 원생 함수 처럼 mov edi,edi 와 같은 바로 5 바이트 의 명령 을 미리 남 겨 두 지 않 는 다.내 가 먼저 생각 한 것 은 함수 의 모든 어 셈 블 리 명령 을 복사 하여 새로운 함 수 를 만 드 는 것 이다.그러나 이렇게 하면 문제 가 생 길 수 있다.E8,E9 와 같은 상대 적 인 점프 명령 은 명령 주소 가 바 뀌 면 점프 하 는 위치 도 바 뀌 고 프로그램 이 무 너 지기 때문에 이것 도 불가능 하 다.
여기까지 와 서 저 는 좀 짜증 이 났 습 니 다.왜냐하면 저 는 hook 의 모든 함 수 를 원 하 는 것 입 니 다.특정한 함수 가 아니 라 함수 입구 의 명령 이 다 릅 니 다.어떻게 해 야 합 니까?저 는 5 바이트 이상 의 최소 완전한 어 셈 블 리 명령 의 길 이 를 계산 해 야 합 니까?
이 사고방식 에 따라 최종 적 으로 C 로 쓴 어 셈 블 리 라 이브 러 리(BlackBone)를 찾 았 는데 그 중에서 비슷 한 방법 을 제 공 했 습 니 다.제 가 조금 수정 한 후에 사용 해 봤 는데 정말 좋 습 니 다.어 셈 블 리 지령 의 길 이 를 정확하게 구 할 수 있 습 니 다.예 를 들 어
push ebp
mov ebp,esp
mov eax,dword ptr ds:[33F22ACh]
값 을 구 하 는 것 은 9 입 니 다.그러면 제 가 구 하 는 값 의 동태 에 따라 하나의 함 수 를 연결 하면 됩 니 다.하하,여기까지 와 서 실현 하 는 차이 가 많 지 않 은 것 같 습 니 다.그러나 64 위 에서 저 에 게 또 한 방 먹 일 줄 은 몰 랐 습 니 다.예전 의 원 함수 명령 은 다음 과 같이 쓸 수 있 습 니 다.
5 바이트 이상 의 최소 전체 어 셈 블 리 명령+jmp 점프 명령 은 우리 의 원 함 수 를 구성 할 수 있 습 니 다.
그러나 우 리 는 C\#에서 어 셈 블 리 를 실행 하려 면 Marshal.AllocH Global 로 비 위탁 관리 공간 을 분배 해 야 한 다 는 것 을 알 고 있 습 니 다.이렇게 분 배 된 주소 와 우리 가 넘 어야 할 원 함수 의 주 소 는 64 비트 에서 2GB 주소 범 위 를 초과 하고 일반적인 점프 명령 은 실현 할 수 없 기 때문에 ret 명령 으로 이 루어 질 것 이 라 고 생각 했 습 니 다.64 비트 주 소 는 직접 push 를 할 수 없습니다.그래서 마지막 으로 다음 과 같은 어 셈 블 리 를 작성 합 니 다.
push rax
mov rax,target_addr
push rax
mov rax,qword ptr ss:[rsp+8]
ret 8
일부 C\#함수 의 첫 줄 은 rax 레지스터 의 값 을 수정 하 는 것 이기 때문에 먼저 rax 를 저장 하고 스 택 에 밀어 넣 은 후에 복원 할 수 밖 에 없습니다.여기 서 어 셈 블 리 작업 이 훨씬 편리 합 니 다.그 전에 다른 것 을 실현 하고 IL 명령 을 사 용 했 지만 dup 라 는 스 택 꼭대기 요 소 를 복사 하 는 명령 만 있 었 지만 스 택 에 있 는 비 스 택 꼭대기 요소 값 을 가 져 오 라 는 명령 을 받 지 못 했 습 니 다.그 러 니까 어 셈 블 리 가 원활 하 잖 아.쓰 고 싶 은 대로 쓰 면 다 이 루어 져.
마지막 으로 이 원 함수 의 호출 과정 입 니 다.동적 연결 함수 이기 때문에 생각 나 는 것 은 Marshal.GetDelegate ForFunction Pointer 를 의뢰 로 전환 하여 실 행 했 습 니 다.나중에 잘못 되 었 습 니 다.제 가 어 셈 블 리 를 했 지만 이 어 셈 블 리 는 C\#방법 jit 후의 어 셈 블 리 입 니 다.이것 은 C 방법 으로 컴 파일 된 어 셈 블 리 가 아 닙 니 다.비 위탁 관리 지침 을 위탁 으로 바 꾸 는 방식 으로 함 수 를 실행 하 는 것 은 불필요 한 조작 을 많이 추가 할 것 입 니 다.예 를 들 어 위탁 관리 유형 과 비 위탁 관리 유형의 전환 이지 만 제 가 맞 춘 함 수 는 이런 과정 이 필요 하지 않 습 니 다.이것 은 어떻게 해 야 합 니까?C\#일반 함 수 를 호출 하 는 방식 으로 만 호출 할 수 있 을 것 같 습 니 다.이것 은 어떻게 실현 할 수 있 습 니까?사실은 매우 쉽 습 니 다.빈 껍질 함수 만 쓰 면 됩 니 다.그리고 이 함수 의 방법 표 의 원생 명령 지침 을 수정 하면 됩 니 다.구체 적 인 방법 은 다음 과 같 습 니 다.

*((ulong*)((uint*)method.MethodHandle.Value.ToPointer() + 2)) = (ulong)ptr;
method 는 빈 껍데기 함수 의 MethodInfo 이 고 ptr 는 동적 으로 연 결 된 원 함수 의 주소 입 니 다.
자,여기 서 핵심 기능 을 기본적으로 완 성 했 습 니 다.가장 처리 하기 어 려 운 것 은 바로 이 원 함수 호출 입 니 다.제 완전한 64 비트 원 함수 명령 조합 이 실현 되 었 습 니 다.코드 가 매우 적 습 니 다.다음 과 같 습 니 다.

byte[] jmp_inst =

{

 0x50,            //push rax

 0x48,0xB8,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90, //mov rax,target_addr

 0x50,            //push rax

 0x48,0x8B,0x44,0x24,0x08,       //mov rax,qword ptr ss:[rsp+8]

 0xC2,0x08,0x00          //ret 8

};

 

protected override void CreateOriginalMethod(MethodInfo method)

{

 uint oldProtect;

 var needSize = NativeAPI.SizeofMin5Byte(srcPtr);

 byte[] src_instr = new byte[needSize];

 for (int i = 0; i < needSize; i++)

 {

  src_instr[i] = srcPtr[i];

 }

 fixed (byte* p = &jmp_inst[3])

 {

  *((ulong*)p) = (ulong)(srcPtr + needSize);

 }

 var totalLength = src_instr.Length + jmp_inst.Length;

 IntPtr ptr = Marshal.AllocHGlobal(totalLength);

 Marshal.Copy(src_instr, 0, ptr, src_instr.Length);

 Marshal.Copy(jmp_inst, 0, ptr + src_instr.Length, jmp_inst.Length);

 NativeAPI.VirtualProtect(ptr, (uint)totalLength, Protection.PAGE_EXECUTE_READWRITE, out oldProtect);

 RuntimeHelpers.PrepareMethod(method.MethodHandle);

 *((ulong*)((uint*)method.MethodHandle.Value.ToPointer() + 2)) = (ulong)ptr;

}

3.라 이브 러 리 개발 에 사용 되 는 언어
전에 내 가 말 했 듯 이 나의 이 라 이브 러 리 는 완전히 C\#로 이 루어 진 것 이다.그러나 그 중에서 C 가 쓴 어 셈 블 리 라 이브 러 리 가 사용 되 었 다.그래서 나 는 C\#로 그 라 이브 러 리 를 다시 한 번 썼 다.말하자면 간단 하 다.C 의 코드 를 붙 였 다.C\#unsafe 코드 를 사용 하여 10 분 동안 고 쳤 으 면 좋 겠 다.진심으로 편리 하지 않다.왜냐하면 C\#는 지침 과 구조 체 를 지원 하 는 것 이 고 기본 유형 이 매우 풍부 하 다.여기 C\#좋아요 눌 러 줘 야 돼!
4.구체 적 으로 사용
사용 은 매우 간단 합 니 다.먼저 콘 솔 프로그램 을 새로 만 들 고 클래스 를 추가 합 니 다.계승 인터페이스 IMethodMonitor,Get 은 자신의 함수 입 니 다.Ori 는 원 함수 가 실 행 될 때 동적 으로 생 성 됩 니 다.Get 에서 하고 싶 은 모든 일 을 할 수 있 습 니 다.

public class CustomMonitor : IMethodMonitor //         IMethodMonitor  

{

 [Monitor("TargetNamespace", "TargetClass")] //  hook          ,  

 public string Get() //            

 {

  return "B" + Ori();

 }

 

 [MethodImpl(MethodImplOptions.NoInlining)]

 [Original] //     

 public string Ori() //            

 {

  return null; //        ,      

 }

}

그리고 목표 함 수 를 정의 합 니 다.예 를 들 어

public string Get()

 {

 return "A";

 }
마지막 으로 Monitor.Install()을 호출 하여 모니터 를 설치 합 니 다.예 를 들 어:

Console.WrtieLine(Get());

Monitor.Install()

Console.WrtieLine(Get());
Get 출력 을 처음 호출 하 는 값 은"A"이 고,두 번 째 는"BA"입 니 다.
물론 이 라 이브 러 리 는 hook 일 뿐 이지 만 hook 은 보통 dll 주입 으로 협조 해 야 합 니 다.hook 자체 프로 세 스 가 의미 가 없 기 때문에 hook 다른 사람의 프로 세 스 가 의미 가 있 습 니 다.저 는 나중에.net 프로그램 원 격 주입 에 사용 할 라 이브 러 리 를 발표 할 것 입 니 다.주입 한 것 은.net 의 dll 입 니 다.C+가 아 닙 니 다.
자,이렇게 많은 이 야 기 를 했 습 니 다.사실은 이 라 이브 러 리 코드 의 양 이 많 지 않 지만 주로 자신 이 연구 한 성과 입 니 다.많은 것 을 스스로 생각해 낸 것 이기 때문에 이 과정 이 재 미 있 고 고수 가 개선 방안 을 지적 해 주 기 를 바 랍 니 다.왜냐하면 현재 이런 방법 은 기능 을 실 현 했 지만 좋 지 않다 고 생각 합 니 다.hook.net 가상 컴퓨터 의 방식 으로 실현 하 는 것 이 더 간단 할 것 같 거나 인터넷 에 이미 만들어 진 해결 방안 을 찾 지 못 했 습 니 다.한 마디 로 하면 벽돌 을 던 져 옥 을 끌 어 올 리 는 것 입 니 다.여러분 들 이 공동으로 토론 하 시기 바 랍 니 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기