S2 EUSKALHACK Self-Defenseless
S2 EUSKALHACK Self-Defenseless
S2 EUSKALHACK Self-Defenseless
EUSKALHACK IV
SYSTEM
User
AV UI AV Service
AV Kernel Module
ARCHITECTURE
SYSTEM
User
AV UI AV Service
AV Kernel Module
SELF-DEFENSE
IS SELF-DEFENSE A SECURITY BOUNDARY?
• Symantec
• CVE-2017-6331
• Avast
• CVE-2017-8307
• CVE-2017-8308
• Kaspersky
• Bypass from 2007:
„Kaspersky Lab does not consider this to be a vulnerability: it is not an error in
our code, but an obscure method for manipulating standard Windows routines
to circumvent our self-defense mechanisms.”
KASPERSKY
• No political agenda here…
• Self-defense bypass != vulnerability
• My original bypass still works
• Some experience from previous research
• Well-known components
• Configurability
• Only AV that caught my previous exploits while they
were 0-day :P
• I found bypasses ofc. ;)
• Research target: KFA
• Was released around the time my research began
• Reusable components (KIS, KES, Secure Connection…)
PRIOR WORK
2008 SOURCE LEAK
• Kaspersky source code appeared on
the Internet in 2011
• Leaked by former employee
• KASPERSKY.AV.2008.SRCS.ELCRABE.RAR
• Source code was from 2008
• I did not use it of course
• That would be illegal…
• "It also contains fragments of an obsolete
version of the Kaspersky anti-virus engine,
which has been radically redesigned and
updated since the source code was stolen"
ANTIVIRUS DEBUGGING
• Use VM's
• Preferably with a good API for snapshot-revert
• Airgap
• Unwanted updates
• Unwanted leaks
• More deterministic
• Script everything
• Everything is slow, speed up where we can
• pykd rocks!
ANTIVIRUS DEBUGGING
• You may be allowed to disable self-
defense
• Kaspersky has an option for this
• User-mode sometimes works
• Snapshot!
• Use a Kernel Debugger like proper adults! avp_info=pykd.dbgCommand("!process 0 0 avp.exe")
avp_eprocess=avp_info.split(" ")[1]
• Need to switch to user process context - slow! pykd.dbgCommand(".process -r -i -p %s; " % avp_eprocess)
SYSTEM
User
AV UI AV Service
AV Kernel Module
DIA CÍME
REVERSE ENGINEERING
KASPERSKY
• 32-bit application
• WOW64 is hard, use a 32-bit OS for
testing
• __fastcall calling convention
• First two params in ECX and EDX,
rest on stack
• Many RE tools can't handle this…
• “Real-life” complexity
• Module sizes in order of MBs
• Structures/exports imitating OO design
• Wide set of x86 instructions
(killing RE tools)
KASPERSKY
TARGET: IPC COMPONENT
• PRRemote.DLL
• + PRCore.DLL
• "Prague"
• Common IPC interface among multiple
products
• KFA, KES, Secure Connection, etc.
• Today's agenda:
High level message processing (~ OSI Layer 5)
• Needed for upper layer analysis
• Tip of the iceberg
COMMUNICATION
PRREMOTE.DLL
• Implements RPC functionality "Hijacking debug output:
1) allocate new memory buffer ($dump)
2) [$dump] <- pointer referencing the beginning of
• Functionality for both client and server data inside the buffer
3) [$dump]+0x10 Size of data DWORD, data starts at 0x18
• Debug strings 4) err_logger expects dst buffer in ECX, so put $dump
there when the function starts
• … the reverser's best friends 5) Log information put inside $dummy when err_logger
exits. Size of data is at $dump+8
• Timestamp DWORD
WORD
zeroC
version
• Length == 0x32 WORD len_out
DWORD len_in
DWORD session0
DWORD session1
WORD unk
DWORD hash0
DWORD hash1
DWORD time0
DWORD time1
};
MESSAGE CHECKS
• Four DWORD's are needed to accept the message for
further parsing
• 2 DWORD's as "session"
• 2 DWORD's as "integrity key"
• Current IDs/keys are stored in global structures in both
the high priv. (avp.exe) and low priv. (avpui.exe)
processes
• With self-defense bypass the secrets can be obtained
• Other options:
• Brute-force
• Pre-auth messages
• ???
DIA CÍME
BUGS
CODE REVIEW
• Remember that length check?
• It goes like this:
Attacker controlled
FUZZING
CONTROLLED MEMCPY
• The memcpy() in use wasn't identified as a library
function
• memcpy() doesn't open a stack frame
• Caller has stack canary
• Leak through arbitrary sized FNV preimage?
• Destination is a stack array right before the canary
• Can we do anything interesting with full control over the
array?
PROCEDURE CALLS
PROCEDURE CALLS
• We are in the old rpc_send_receive_server() now!
• Called from rpc_send_receive_server3()
• So much for "radical redesign"…
• func_addr is chosen from different function pointer
tables
• User chooses the table
• User chooses the offset
• Offset is bounds checked
FUNCTION TABLES
Typical function in the table:
void f(int param_1,int param_2,int param_3,int param_4,int param_5,int param_6,int param_7)
{
if (param_2 == -0xf000) {
(**(code **)(*(int *)DWORD_100739a0 + 0x14))(0xffff1000,param_3,param_5);
return;
}
(**(code **)(*(int *)(param_1 + 4) + 0x124))
(param_1,param_2,param_3,param_4,param_5,param_6,param_7,0xffffffff);
return;
}
EXPLOITATION
EXPLOITATION
THE GOOD THE BAD
• We are local… • Stack canaries
• ASLR ineffective • Thanks Tavis…
• Arbitrary computation • DEP
(dynamic shellcode, ROP, etc.)
• Losing session+keys at respawn
• AVP respawns
• Heap entropy still exists
• Pokemon exception handling
• Randomizing things before it was cool…
EIP CONTROL
• 4th WORD after header holds flags
• Needs proper setting to reach the table based call
• Next DWORD is the table offset
• What on Earth is this?
undefined4 __cdecl call_param2(undefined4 param_1,int param_2)
{
int iVar1;
iVar1 = (**(code **)(*(int *)(DWORD_10077ad4 + 4) + 0x58))(DWORD_10077ad4,param_2);
if (-1 < iVar1) {
(**(code **)(*(int *)(param_2 + 4) + 0x5c))(param_2);
}
return 0;
}
EIP CONTROL
• Looks like a method call on a global object
• Implementation in PRCORE.DLL
• The real deal is reached after multiple calls
• my_struct_checker()
iVar1 = *in_FS_OFFSET;
*(undefined **)in_FS_OFFSET = local_14;
while (ctr4 != 0) {
*char_out = *ptr;
ctr4 = ctr4 + -1;
char_out = char_out + 1;
ptr = ptr + 1;
}
*in_FS_OFFSET = iVar1;
return 0;
}
STRUCT CHECKER
• I used dynamic analysis + VM snapshots to keep
heap addresses constant
• If it works, it's not stupid!
• These functions get hit all the time
• Must single-step from rpc_send_receive_server()
• Struct checker performs basic sanity checks
• Param2 has to survive multiple dereferences
• Provide self-referencing pointers
STRUCT CHECKER
• Sent 20K packages with self-referencing pointers,
then the trigger packet
• Still based on predictable heap addresses + VM snapshots
• Checks passed -> EIP overwritten \o/
• EIP value read from an address after the checked
struct values -> Possible to control!
• How?
Landing zone
Spray
…
ROP CHAIN
ESI ->
Trigger SELFPTR SELFPTR SELFPTR SELFPTR
Spray SELFPTR SELFPTR SELFPTR SELFPTR
SELFPTR SELFPTR SELFPTR SELFPTR
SELFPTR SELFPTR SELFPTR SELFPTR
Spray ROP1 ROP1 ROP1 ROP1
ROP1 ROP1 ROP1 ROP1
ROP1 ROP1 ROP1 ROP1
Spray ROP1 ROP1 ROP1 ROP1
DEMO
DIA CÍME
OUTRO
COORDINATED DISCLOSURE?
If these are your priorities…
COORDINATED DISCLOSURE?
If these are your priorities…
COORDINATED DISCLOSURE?
If these are your priorities…
COORDINATED DISCLOSURE?
If these are your priorities…
COORDINATED DISCLOSURE?
If these are your priorities…
@buherator
@SilentSignalHU