
Easy Anti-Cheat 커널 드라이버를 리버스 엔지니어링하여 메모리 할당을 가로채 위반 패킷을 억제하는 우회 기법으로, 보고서 복호화 루틴과 분석 노트를 포함합니다.
내 코드가 아님. 저장용일 뿐임 https://www.unknowncheats.me/forum/anti-cheat-bypass/503052-easy-anti-cheat-kernel-packet-fucker.html
이것은 Easy Anti-Cheat Kernel Packet Fucker (줄여서 EACKPFucker)이다. 이게 뭐냐고? 기본적으로, 커널 모드 드라이버를 통한 패킷들이 그들에게 전송되지 않게 된다는 뜻이며, 이는 곧 너의 pasta pasta 2023 kdmapper FUD bypass를 아무 문제 없이 사용할 수 있다는 의미다.
좋아, 내 속옷까지 벗겨놨네. 이게 대체 어떻게 작동하는 거야? 단순히 주소 하나를 바꾸는 것만으로. 자, 이제 EAC가 실제로 어떻게 작동하는지 깊이 파헤쳐 보자.
처음부터, Easy Anti-Cheat는 너를 밴하기 위해 실제로 너의 데이터를 확보해야 한다. 이 패킷들은 그들의 Hydra 채널을 통해 전송되며 암호학적으로 안전하다. 이 bypass에 대해 알아야 할 건 이게 전부다, 이 부분에 대해 더 자세히는 다루지 않겠다.
랜덤 위반 사례를 통해 그들의 커널 드라이버 내부에서 이것이 어떻게 작동하는지 살펴보자:

이거 너에게도 취약해 보이지 않나? 내 눈에는 확실히 그렇게 보인다.
첫 번째 함수를 살펴보자: kalloc_rt
흠, 알겠다. alloc_pool_with_tag로 들어가 보자
ExAllocatePoolWithTag를 동적으로 임포트한다. 흠mmm... 누군가 그 qword를 자신이 수정한 malloc 함수로 바꾸면 무슨 일이 일어날지 궁금하군... (그래, 작동한다 -- 그리고 쓰기 가능한 섹션을 수정하는 것이기 때문에, EAC는 전혀 눈치채지 못한다)
좋아, 이제 우리는 메모리 할당을 통제할 수 있다. 멋지다! 이걸로 뭘 할 수 있을까?
잘 물어봤다! 요점은 이거다: 커널 모드에서 오는 모든 패킷은 33096i64 크기다.. 그리고 앞서 봤듯이, 메모리가 할당되지 않으면 EAC는 그냥.. 위반을 무시한다.
좋아, 누군가 단순히.. 이렇게 한다고 해보자:



---------------------------------------------------------------------------------------------------------------------------------------```C++ // report encryption looks like this (some parts may vary for each report, i believe they use key1, key2, key3 to use only 1 function for decryption) static report_t* encrypt(uint8_t* data, uint64_t size) {
report_t* packet = (report_t*)malloc(sizeof(report_t));
if (!packet) return nullptr;
uint32_t seed = 0x80BE5ED5 * ((uint64_t)&data >> 2);
memset(&packet->key1, 0, 0x8200);
packet->raw_size = 0;
packet->key1 = 0x66259F86; // gives key4?
packet->key2 = 0x21EBA81; // gives key5?
packet->key3 = 0xACE987AF; // gives key6?
packet->key4 = 0x50BFC583; // gives seed
packet->key5 = 0x3C61A927; // gives dynamic_key (from end)
packet->key6 = 0x70881859; // gives actual payload size
uint8_t* raw_data = packet->raw;
uint64_t raw_size = 24;
uint8_t* payload_data = packet->payload;
uint64_t payload_size = 0;
uint32_t dynamic_key = seed ^ 0x6957FDB6;
while (payload_size < size && payload_size < 0x8000) {
uint32_t a = (dynamic_key << 0xD) ^ dynamic_key;
uint32_t b = (a >> 0x11) ^ a;
uint32_t c = (b << 0x5) ^ b;
uint32_t d = _rotr(c, 2);
uint8_t shift = 8 * (payload_size & 3);
payload_data[payload_size] = data[payload_size] ^ (d >> shift);
dynamic_key = data[payload_size] ^ d;
payload_size++;
raw_size++;
}
uint64_t aligned_size = (raw_size + 0xFF) & ~0xFF; // align up by 0x100
while (raw_size < aligned_size) {
dynamic_key *= 0x80BE5ED5;
raw_data[raw_size++] = dynamic_key;
}
packet->key4 ^= seed;
packet->key5 ^= dynamic_key;
packet->key6 ^= payload_size;
packet->raw_size = raw_size;
return packet;
}
// thus my decryption looks like this static void decrypt(report_t* packet) {
uint32_t seed = packet->key4 ^ 0x50BFC583;
uint32_t dynamic_key = seed ^ 0x6957FDB6;
//uint32_t dynamic_key = packet->key5 ^ 0x3C61A927;
uint8_t* payload_data = packet->payload;
uint32_t payload_size = packet->key6 ^ 0x70881859;
for (uint32_t i = 0; i < payload_size; i++) {
uint32_t a = (dynamic_key << 0xD) ^ dynamic_key;
uint32_t b = (a >> 0x11) ^ a;
uint32_t c = (b << 0x5) ^ b;
uint32_t d = _rotr(c, 2);
uint8_t shift = 8 * (i & 3);
payload_data[i] ^= (d >> shift);
dynamic_key = payload_data[i] ^ d;
}
}
after dumping some reports and decrypting: