제가 시간만 된다면 약간의 데모스럽게 소스라도 올려드리는데..
좀 여의치 않군요.
일단 생각한 방안입니다.
예전부터 필요한 기능이라 생각했는데
이래저래 고민 끝에 해결방안이 나왔습니다.
현재 ATCode의 후킹 방식은
한 함수 내에 후킹 지점부터 리턴 포인트까지 복사하여 복귀 루틴만 살리는 형식으로 보이는데
단점이라면 그 리턴포인트까지 복사할때의 부분에 다시금 또다른 후킹을 걸면
무시가 된다는 점입니다.
따라서 이런 형식에서 제일 알맞은 후킹 지점은
함수의 첫 부분으로
자체디버깅에서도 확인하는 곳들로 보입니다.
뭐 이런저런 서론은 제끼고
본론입니다.
그런 함수의 첫지점내에는 반드시 리턴포인트가 [esp] 내에 있습니다.
말 그대로 그 지점을 참조하는 콜스택들이 여러개가 있다면
지정한 리턴포인트나 복귀할 리턴포인트 를 지정해두고
지정한 리턴포인트 : [esp]-0x5(콜의 크기, 포인터 콜일경우 크기가 -6)
복귀할 리턴포인트 : [esp]
해당되는 곳에서 온 경우에만
참조하여 대사를 번역하는 형식입니다.
위 방식은 반드시 리턴포인트가 [esp] 에 있는
콜의 첫번째 위치로 가정하며
필요하게된 이유는
여러개의 콜스택이 쌓인 곳에 후킹이 불가피하고
특정 여러 지점에서 하려니 ATCode 상의 후킹 방식 때문에 한 콜내에 여러 후킹이 불가능
따라서
후킹한 콜내에서 분리하거나 판별하여 걸게되면
처음 제시한 단점이 보안되는 형태가 됩니다.
^-^
이해가 되셨으리라 생각하지만
부족한 설명이 있을 수 있으므로
물어봐주시기 바랍니다.
whoami
- 2012.04.01
- 13:39:17
음 언제나 그렇듯이 피시님 (.. 에로게임님으로 불러드려야 하나요? 줄여서 에게님...(퍽))글은 난해한 면이 있군요..;;
일단 확실히 하고 넘어갈 것은 ATCode 에는 후킹방식이라는 것이 없습니다. ATCode 는 후킹이 필요할 때
ATCAPI (아랄트랜스 컨테이너 API...제가 만든 단어입니다만 ㅎ).. 그러니까 http://lab.aralgood.com/4350 에서 소개된
컨테이너 제공 함수를 불러주는 것이고 실제 후킹은 ATCTNR.DLL 에서 하게 되는 것이니까요.
어쨌든, 이 후킹 방식 (편의상 후킹모드1이라고 합니다)의 문제점에 대해서는 예전부터 많은 고려가 있어왔습니다.
그래서 후킹모드2 가 개발되었고 0.2용 테스트 버전이 한번 만들어졌으며.. 소스공개가 안되어 있습니다만
아마도 0.3은 후킹모드2로 만들어져 있었던 것으로 보입니다.
하지만 이 후킹모드2는 문제점이 있었는데, 특히 루프문에 잘못 사용하면 게임이 뻗는 경우가 생겼습니다.
그것이 아마도 0.2에서 작동하는 코드가 0.3에서 작동하지 않는 이유인 듯 합니다만 글쎄요.
참고로 후킹모드 0 이랄까.. 0xCC INT3 을 사용해서 디버그 모드로 붙어 사용하는 전통적인 방법이 하나 더 있습니다만
이것은 전통적인 방법이다보니 안티 디버거가 잡아낸다는 문제가.. oTL
서론은 여기까지 하고.. 본론이랄까 잘 이해가 안가는 부분에 대해서.
1. 일단은 피시님께서는 후킹모드1 을 유지한 상태에서 ATCode 만 고치는 해결책을 말씀하신 것인가요?
아니면 다른 후킹모드를 고안해 내신 건가요?
2. 말씀하신 방법은 반드시 후킹위치가 함수 첫지점 (그러니까.. 55 8B EC 로 보통 시작되는) 에서만 가능한 것이죠?
3. 지정한 리턴포인트와 복귀할 리턴포인트에 대해 이해가 잘 안가는데요..
예를 들어...
1000 ...
...
1005 call 2000 // 함수 호출 포인트 1
100A ... // 함수 호출 포인트 1의 리턴지점
...
1100 ...
...
1105 call 2000 // 함수 호출 포인트 2
110A ... // 함수 호출 포인트 2의 리턴지점
[.......................]
2000 jmp 3000 // 후킹된 함수 첫 위치. 원래는 55 8B EC 등으로 시작했겠지만 후킹되어 3000 으로 점프한다.
...
2100 ret
[.......................]
3000 ... // ATCTNR 의 후킹 캐치부분. 여기서 각 레지스터를 백업한다.
...
3050 call ATCode.PointCallback // ATCode 로 제어를 넘긴다. ATCode 부분은 생략.
...
3100 jmp 4000 // ATCTNR 후킹 끝. 백업된 레지스터를 복원 후 원래 함수 복사본으로 점프.
[.......................]
4000 ... // 원래 함수 (2000) 의 복사본.. 55 8B EC ;;
...
4100 ret // 1005 에서 call 했는가 1105 에서 call 했는가에 따라 100A 나 110A 로 돌아감.
이런 코드가 있다고 하겠습니다.
여기서 3000에 넘어온 상태에서 -라기 보다는 ATCode로 넘어온 상태에서 (결국 3000 에서의 상태를 백업받는
것이므로 3000부터 ATCode가 시작된다고 봐도 됨) [esp] 에는 복귀할 리턴포인트 (100A 혹은 110A) 가 있는 것이 맞습니다.
그럼.. 지정한 리턴포인트라는 개념은 무엇이죠? 함수 호출 포인트 (1005 혹은 1105) 를 말씀하시는 건가요?
이 지정한 리턴포인트의 사용처는 무엇인가요?
4. 위의 예에서, 현재 말씀하시는 것이 ATCode 내에서 복귀할 리턴포인트에 따라 다른 처리를 하고 싶으시다는 뜻인가요?
쉽게 말해...
if ([esp] == 0x100A) { 처리 1;}
else if ([esp] == 0x110A) { 처리 2;}
이런 식으로 하고 싶으시다는 건가요?
좀 더 자세하게.. 그러니까 토끼가 이해할 수 있을 정도로 ^^ 설명해 주시면 감사하겠습니다.
EroGame
- 2012.04.01
- 15:42:19
말 실수 했군요.
ATCTNR 에서 넘어갔을때를 말한건데;;
1. 후킹모드 2 는 제대로 디버거로 본적이 없어서 얼마나 다른지는 모르겠군요.
다만 후킹모드 1은 진작에 다 까봤기 때문에 심각하게 문제가 많은걸 알고 있었습니다.
제가 위에 말한 문제점이 후킹모드 2에서 해결 되었는지는 제가 알 길이 없지만..말이지요.
아마 해결 안되었다고 가정하고 얘기했습니다.
그리고 지금 해결 방법은 후킹모드 1의 후킹에 있어서 제일 큰 문제점의 보안이 되겠구요.
2. 함수 첫위치가 보통? 으로 지정되있던건 본적이 없군요.
일단 생각하는 의미는 맞습니다.
위 설명대로하면 2000 위치에서 후킹했다는 걸로 되겠지요.
3.위 내용을 근거로 설명하면
call 2000 을 가리키는 위치가 여럿있습니다.
1005, 1105, ex) 1205, 1305, 1405 ......
따라서 2000 에 후킹을 걸면
저 많은 위치에서 들어오게 됩니다.
그러면 해당 후킹위치에서는
2000 에서 [esp] 가 가리키는 것은
위 수많은 지점중에 들어왔던곳을 복귀하기 위한 리턴포인트(100A, 110A, 120A....)가 있겠지요.
일단 여기까지 설명하고 이해를 위해서 4번 가겠습니다.
4. if ( [esp] == {0x100A, 0x130A,...} ) { 번역 }
else 무시
말 그대로 여러 call 2000 중에서 특정 장소에서 들어온 값들만 번역하게 넘기는 겁니다.
포함하지 않는 녀석들은 그냥 다시 원래대로 실행하면 됩니다.
위 기능이 필요하게 된 이유는
현 후킹모드 1 에서 아래와 같은 경우 후킹이 씹히게 됩니다.
1000 ...
...
1005 call 2000 // 함수 호출 포인트 1
100A ... // 함수 호출 포인트 1의 리턴지점
...
1015 call 2000 // 함수 호출 포인트 2
101A ... // 함수 호출 포인트 2의 리턴지점
1020 ret
위 내용을 하나의 함수로 판단했을때
후킹모드 1 이 싹 가져갑니다.
이때 후킹할 지점은 2지점.
1005, 1015 이고 그 둘의 사이는 코드로 보아도 그리 멀지 않습니다.
중간 내용도 기껏해야 점프나 간단한 분기문만 있다고 했을때입니다.
이렇게 후킹하게 될 경우
1005 는 제대로 후킹되어 번역이 되는데 반면에
복귀는 100A 지점이 아닌
복사된 4005 쯤으로 날라가겠지요.
그러면 자연히 1015 의 위치는 씹히게 됩니다.
만약 이런 상태에서 저부분을 무리하게 후킹하려면
4015 라는 가상 위치에 후킹하게 되고 2중으로 돌아가게 됩니다.
이건 생각만해도 말도 안되는 개념이고,
실제 해봤는데 안전성이 매우 취약합니다. 따라서 무시하겠습니다.
그래서 위와 같은 같은 함수내에서
특정 함수를 중복 참조 했을때의 문제점을 해결하기 위해서는
2000 위치를 후킹해야 하는데.
만약 이 위치를 가리키는게 약 30개 정도 된다고 하고
거기서는 참조하면 무조건 에러가 나오는 위치도 존재할 때도 있습니다.
이럴 경우 2000 의 후킹하고 필터로 걸러내기에는 많은 무리가 되거나
번역하지 않아야 하는 문장이 왔을때도
따로 걸러내기가 힘듭니다.
그래서 3번에서 말한 방법으로
2000 위치에서 리턴 위치를 기준으로 번역하냐 마냐를
AT코드의 추가적인 값으로 판단하자 이겁니다.
여기까지는 이해되셨으리라 생각됩니다.
그리고 부가적인 문제는 그 기준을 정해야 하는 리턴 값을.
정말 리턴값으로 할것이냐(100A, 101A ...)
혹은 그 위치로 들어가는 (1005,1015) 값으로 할것이냐는
whoami 씨의 몫으로 던지고자 합니다.
아, 그리고 저는 피시로 불러주셔도 상관없어요. ^-^
whoami
- 2012.04.02
- 21:02:28
1. 후킹모드2 는 아랄님이 처음 SVN에 커밋한 소스를 보면 알 수 있습니다만...
동작방식은 간단히 요약하면 이렇습니다.
- 후킹장소에 점프명령(5바이트) 으로 지워지는 명령을 별도 메모리에 백업
- 후킹 처리 후 백업된 명령으로 점프
- 백업된 명령 실행 후 원본의 다음 명령으로 점프
그래서 후킹된 곳이 같은 루틴 내에 있더라도 정상적으로 작동합니다...만,
루프시에 jmp 로 돌아가는 부분에다 후킹을 해버리면 문제가 발생합니다.
예)
1000 - 1003 (명령1)
1004 - 1006 (명령2)
1007 jmp 1004
이런 코드를 후킹하면
1000 - 1004 jmp ATCTNR.후킹처리루틴
1005 - 1006 (명령2의 조각)
1007 jmp 1004 (어디로 점프해야 하나?)
2. 함수 첫위치는..
55 push esp
8B EC mov ebp, esp
보통 이렇게 시작한다는 것이었습니다; 아닌 것들도 꽤 많지만요.
4. 일단 이해는 했습니다만 좀 고민해봐야 할 문제로군요.. 또 옵션을 추가해 넣어야 하나.
어차피 손대려 해도 다른 일이 앞에 쌓여있어서 바로 시작할 수가 없군요.
KiriKiri 도 봐야 하고 커스텀딕 요청도 있었고.... 그 후에나 손을 봐야 할 것 같습니다.
아랄
- 2012.04.03
- 19:19:36
네, 당시 저도 그 문제로 고민했었는데..
문제의 점프코드를 죄다 코드조각으로 점프시키도록 하는 방법도 있지만
그렇게 코드를 역어셈블하고 점프코드를 탐색하는 기능을 만드는 비용이 발생빈도에 비해서 과연 합리적인가 하는 딜레마에 빠졌었드랩니다.
마찬가지로 커널 드라이버를 만들어 올릴까 하는 생각도 했지만
그것도 노력대비 성과는 아주 형편없고 루트킷에 대한 제약도 점점 강화되고 있는 추세라...
(드라이버에 왠 인증서를 ;;)
이래저래 고민하다가 결국 0.3을 방치하기에 이르렀다는 슬픈 전설이 ㅎㅎ
그나저나 히데님이 얼릉 홈피복구 도와주러 오셨음 좋겠네요 으으.. ㅜㅜ
저는 사고만 치고 다니네요 흑흑
시간만 난다면 제가 손보는데 ㅡㅡ;