cheat-engine/Cheat Engine/bin/autorun/java.lua

755 lines
21 KiB
Lua

JAVACMD_STARTCODECALLBACKS=0
JAVACMD_STOPCODECALLBACKS=1
JAVACMD_GETLOADEDCLASSES=2
JAVACMD_DEREFERENCELOCALOBJECT=3
JAVACMD_GETCLASSMETHODS=4
JAVACMD_GETCLASSFIELDS=5
JAVACMD_GETIMPLEMENTEDINTERFACES=6
JAVACODECMD_METHODLOAD=0
JAVACODECMD_METHODUNLOAD=1
JAVACODECMD_DYNAMICCODEGENERATED=2
JAVACODECMD_TERMINATED=255
JAVA_TIMEOUT=500000 --500 seconds
function getFieldFromType(type, field, infloopprotection)
if type==nil then return nil end
if infloopprotection==nil then
infloopprotection=0
else
infloopprotection=infloopprotection+1
if infloopprotection>20 then
return nil
end
end
type=type:gsub("<.->(.-)","<%1>") --replace the <xxx> part with <>
local Struct=JavaStructs[type]
if (Struct==nil) or (Struct[field]==nil) then
return getFieldFromType(JavaTypes[type].Alternate, field, infloopprotection) --check the super type if that one has fields
else
return Struct[field]
end
end
function getKlassFromObject(object)
return readQword(object+getFieldFromType("oopDesc","_metadata._klass").Offset)+JavaTypes["oopDesc"].Size
end
function CollectJavaSymbolsNonInjected(thread)
JavaStructs={}
JavaTypes={}
local s,s2
s=readPointer("jvm.gHotSpotVMStructs")
if (s==nil) or (s==0) then
return --invalid JVM
end
VMStructEntryTypeNameOffset=readInteger("jvm.gHotSpotVMStructEntryTypeNameOffset")
VMStructEntryFieldNameOffset=readInteger("jvm.gHotSpotVMStructEntryFieldNameOffset")
VMStructEntryTypestringOffset=readInteger("jvm.gHotSpotVMStructEntryTypestringOffset")
VMStructEntryIsStaticOffset=readInteger("jvm.gHotSpotVMStructEntryIsStaticOffset")
VMStructEntryOffsetOffset=readInteger("jvm.gHotSpotVMStructEntryOffsetOffset")
VMStructEntryAddressOffset=readInteger("jvm.gHotSpotVMStructEntryAddressOffset")
VMStructEntryArrayStride=readInteger("jvm.gHotSpotVMStructEntryArrayStride")
--[[
const char* typeName; // The type name containing the given field (example: "Klass")
const char* fieldName; // The field name within the type (example: "_name")
const char* Typestring; // Quoted name of the type of this field (example: "Symbol*";
// parsed in Java to ensure type correctness
int32_t isStatic; // Indicates whether following field is an offset or an address
uint64_t offset; // Offset of field within structure; only used for nonstatic fields
void* address; // Address of field; only used for static fields
// ("offset" can not be reused because of apparent SparcWorks compiler bug
// in generation of initializer data)
--]]
while readString(readQword(s+VMStructEntryTypeNameOffset))~=nil do
local a,b,c,d;
a=readString(readPointer(s+VMStructEntryTypeNameOffset),255)
b=readString(readPointer(s+VMStructEntryFieldNameOffset),255)
c=readString(readPointer(s+VMStructEntryTypestringOffset),255)
d=readPointer(s+VMStructEntryIsStaticOffset)
if a and b and c then
if JavaStructs[a]==nil then
JavaStructs[a]={}
end
JavaStructs[a][b]={}
JavaStructs[a][b].Typestring=c
if d==0 then
JavaStructs[a][b].Offset=readPointer(s+VMStructEntryOffsetOffset)
else
JavaStructs[a][b].Address=readPointer(s+VMStructEntryAddressOffset)
end
--if d~=0 then
-- print(a.." - "..b.." - "..c.." : "..string.format("%x ( %x )",readPointer(s+VMStructEntryAddressOffset), readPointer(readPointer(s+VMStructEntryAddressOffset)) ))
--else
-- print(a.." - "..b.." - "..c.." : "..string.format("%x",readPointer(s+VMStructEntryOffsetOffset)))
--end
end
s=s+VMStructEntryArrayStride
end
--print("--------------------------------------------------------------------------------")
s2=readPointer("jvm.gHotSpotVMTypes")
VMTypeEntryTypeNameOffset=readInteger("jvm.gHotSpotVMTypeEntryTypeNameOffset")
VMTypeEntrySuperclassNameOffset=readInteger("jvm.gHotSpotVMTypeEntrySuperclassNameOffset")
VMTypeEntryIsOopTypeOffset=readInteger("jvm.gHotSpotVMTypeEntryIsOopTypeOffset")
VMTypeEntryIsIntegerTypeOffset=readInteger("jvm.gHotSpotVMTypeEntryIsIntegerTypeOffset")
VMTypeEntryIsUnsignedOffset=readInteger("jvm.gHotSpotVMTypeEntryIsUnsignedOffset")
VMTypeEntrySizeOffset=readInteger("jvm.gHotSpotVMTypeEntrySizeOffset")
VMTypeEntryArrayStride=readInteger("jvm.gHotSpotVMTypeEntryArrayStride")
while readString(readPointer(s2+VMTypeEntryTypeNameOffset))~=nil do
local a,b,isInteger, isOop, size;
a=readString(readPointer(s2+VMTypeEntryTypeNameOffset),255)
b=readString(readPointer(s2+VMTypeEntrySuperclassNameOffset),255)
isOop=readInteger(s2+VMTypeEntryIsOopTypeOffset)
isInteger=readInteger(s2+VMTypeEntryIsIntegerTypeOffset)
size=readInteger(s2+VMTypeEntrySizeOffset)
if a then
local _a,_b
_a=a:gsub("<.->(.-)","<%1>")
JavaTypes[_a]={}
JavaTypes[_a].Size=size
if b then
_b=b:gsub("<.->(.-)","<%1>")
JavaTypes[_a].Alternate=_b
end
end
local r=''
if a then
r=r..a
end
if b then
r=r.." - "..b
end
--print(r.." (size="..size..")")
s2=s2+VMTypeEntryArrayStride
end
-- print("-------------------------------------------------")
s=readPointer("jvm.gHotSpotVMIntConstants")
VMIntConstantEntryNameOffset=readInteger("jvm.gHotSpotVMIntConstantEntryNameOffset")
VMIntConstantEntryValueOffset=readInteger("jvm.gHotSpotVMIntConstantEntryValueOffset")
VMIntConstantEntryArrayStride=readInteger("jvm.gHotSpotVMIntConstantEntryArrayStride")
while readString(readPointer(s+VMIntConstantEntryNameOffset))~=nil do
local name,value
name=readString(readPointer(s+VMIntConstantEntryNameOffset))
value=readInteger(s+VMIntConstantEntryValueOffset)
--print(name.."="..string.format("%x",value))
s=s+VMIntConstantEntryArrayStride
end
--print("-------------------------------------------------")
s=readPointer("jvm.gHotSpotVMLongConstants")
VMLongConstantEntryNameOffset=readInteger("jvm.gHotSpotVMLongConstantEntryNameOffset")
VMLongConstantEntryValueOffset=readInteger("jvm.gHotSpotVMLongConstantEntryValueOffset")
VMLongConstantEntryArrayStride=readInteger("jvm.gHotSpotVMLongConstantEntryArrayStride")
while readString(readPointer(s+VMLongConstantEntryNameOffset))~=nil do
local name,value
name=readString(readPointer(s+VMLongConstantEntryNameOffset))
value=readQword(s+VMLongConstantEntryValueOffset)
--print(name.."="..string.format("%x",value))
s=s+VMLongConstantEntryArrayStride
end
--Fetch the interpreter functions
local InterpreterFunctionList=getFieldFromType('AbstractInterpreter', '_code').Address
local BufferOffset=getFieldFromType('StubQueue', '_stub_buffer').Offset
local QueueEndOffset=getFieldFromType('StubQueue', '_queue_end').Offset
local InterpreterCodeletSizeOffset=getFieldFromType('InterpreterCodelet', '_size').Offset
local InterpreterCodeletDescriptionOffset=getFieldFromType('InterpreterCodelet', '_description').Offset
local InterpreterCodeletHeaderSize=JavaTypes['InterpreterCodelet'].Size
local InterpreterCodeletHeaderSizeAligned
InterpreterCodeletHeaderSizeAligned=InterpreterCodeletHeaderSize
if targetIs64Bit() then
--increase InterpreterCodeletHeaderSizeAligned so it's dividable by 32
if (InterpreterCodeletHeaderSizeAligned % 32)~=0 then
InterpreterCodeletHeaderSizeAligned=(InterpreterCodeletHeaderSizeAligned+32) - (InterpreterCodeletHeaderSizeAligned % 32)
end
else
--increase InterpreterCodeletHeaderSizeAligned so it's dividable by 16
if (InterpreterCodeletHeaderSizeAligned % 16)~=0 then
InterpreterCodeletHeaderSizeAligned=(InterpreterCodeletHeaderSizeAligned+16) - (InterpreterCodeletHeaderSizeAligned % 16)
end
end
StubQueueAddress=readPointer(InterpreterFunctionList)
BufferStart=readPointer(StubQueueAddress+BufferOffset)
BufferEnd=BufferStart+readInteger(StubQueueAddress+QueueEndOffset)
CurrentPos=BufferStart
while (CurrentPos<BufferEnd) do
local CodeletSize=readInteger(CurrentPos+InterpreterCodeletSizeOffset)
local Description=readString(readInteger(CurrentPos+InterpreterCodeletDescriptionOffset))
local Codestart=CurrentPos+InterpreterCodeletHeaderSizeAligned
--print(string.format("%x = %s", Codestart, Description))
JavaSymbols.addSymbol("","jInterpreter_"..Description,Codestart, CodeletSize-InterpreterCodeletHeaderSizeAligned)
CurrentPos=CurrentPos+CodeletSize
end
JavaHotSpotFieldsLoaded=true
end
function javaInjectAgent()
if (JavaSymbols==nil) then
JavaSymbols=createSymbolList()
else
JavaSymbols.clear()
end
createNativeThread(CollectJavaSymbolsNonInjected)
if (javapipe ~= nil) then
javapipe.destroy() --this will cause the pipe listener to destroy the java event server, which will stop the javaeventthread (so no need to wait for that)
javapipe=nil
end
local alreadyinjected=false
if javaInjectedProcesses==nil then
javaInjectedProcesses={}
local oldstate=errorOnLookupFailure(false)
local address=getAddress('CEJVMTI64.dll')
if (address~=nil) and (address~=0) then
javaInjectedProcesses[getOpenedProcessID()]=true
alreadyinjected=true
print("opened a process with the JVMTI agent already running")
end
errorOnLookupFailure(oldstate)
else
--check if already injected
alreadyinjected=javaInjectedProcesses[getOpenedProcessID()]==true
end
if (alreadyinjected==false) then
autoAssemble([[
globalalloc(bla,1024)
globalalloc(cmd,16)
globalalloc(arg0,256)
globalalloc(arg1,256)
globalalloc(arg2,256)
globalalloc(result,4)
globalalloc(pipename,256)
cmd:
db 'load',0
arg0:
db 'F:\svn\Cheat Engine\bin\autorun\dlls\CEJVMTI64',0
arg1:
db 0
arg2:
db 0
pipename:
db '\\.\pipe\cejavapipe',0
bla:
sub rsp,8
sub rsp,30
mov rcx,cmd
mov rdx,arg0
mov r8,arg1
mov r9,arg2
mov [rsp],cmd
mov [rsp+8],arg0
mov [rsp+10],arg1
mov [rsp+18],arg2
mov [rsp+20],pipename
call jvm.JVM_EnqueueOperation
mov [result],eax
add rsp,38
ret
createthread(bla)
]])
javaInjectedProcesses[getOpenedProcessID()]=true
end
--wait till attached
local timeout=getTickCount()+JAVA_TIMEOUT
while (javapipe==nil) and (getTickCount()<timeout) do
javapipe=connectToPipe('cejavadc_pid'..getOpenedProcessID())
end
if (javapipe==nil) then
return 0 --failure
end
java_StartListeneningForEvents()
JavaSymbols.register() --make these symbols available to all of cheat engine
return 1;
end
function JavaEventListener(thread)
--this code runs in another thread
local EVENTCMD_METHODLOAD=0
local EVENTCMD_METHODUNLOAD=1
local EVENTCMD_DYNAMICCODEGENERATED=2
local JavaEventPipe
local timeout=getTickCount()+JAVA_TIMEOUT --5 seconds
while (JavaEventPipe==nil) and (getTickCount()<timeout) do
JavaEventPipe=connectToPipe('cejavaevents_pid'..getOpenedProcessID())
end
if (JavaEventPipe==nil) then
return --failure
end
while true do
local command=JavaEventPipe.readByte()
if command==EVENTCMD_METHODLOAD then --methodload
local size1, size2, size3,ssize,classname, methodname, methodsig
local method=JavaEventPipe.readQword()
local code_size=JavaEventPipe.readDword()
local code_addr=JavaEventPipe.readQword()
size1=JavaEventPipe.readWord()
if (size1>0) then
classname=JavaEventPipe.readString(size1)
else
classname=''
end
size2=JavaEventPipe.readWord()
if (size2>0) then
methodname=JavaEventPipe.readString(size2)
else
methodname=''
end
size3=JavaEventPipe.readWord()
if (size3>0) then
methodsig=JavaEventPipe.readString(size3)
else
methodsig=''
end
local endpos=classname:match'^.*();'
if endpos~=nil then
classname=string.sub(classname,1,endpos-1)
end
local name=classname.."::"..methodname..methodsig
JavaSymbols.addSymbol("",classname.."::"..methodname,code_addr,code_size)
--print(string.format("s1=%d s2=%d s3=%d (cn=%s mn=%s ms=%s)", size1,size2,size3, classname, methodname, methodsig))
--print(string.format("Methodload: %s - (%x) %x-%x", name, method, code_addr, code_addr+code_size))
--
elseif command==EVENTCMD_METHODUNLOAD then --methodunload
local method=JavaEventPipe.readQword()
local code_addr=JavaEventPipe.readQword()
print("EVENTCMD_METHODUNLOAD")
JavaSymbols.deleteSymbol(code_addr)
--
elseif command==EVENTCMD_DYNAMICCODEGENERATED then --DynamicCodeGenerated
local ssize
local address=JavaEventPipe.readQword()
local length=JavaEventPipe.readDword()
ssize=JavaEventPipe.readWord()
local name=JavaEventPipe.readString(ssize)
--print(string.format("DynamicCode: %s - %x-%x", name, address, address+length))
JavaSymbols.addSymbol("",name,address,length)
--
elseif command==JAVACODECMD_TERMINATED then
--print("eventserver terminated")
break
elseif command==nil then
--print("Disconnected")
break
else
print("Unexpected event received") --synchronize isn't necesary for print as that function is designed to synchronize internally
break --unknown command
end
end
JavaEventPipe.destroy();
end
function java_StartListeneningForEvents()
javapipe.lock();
javapipe.writeByte(JAVACMD_STARTCODECALLBACKS)
--the javapipe will now be frozen until a javaeventpipe makes an connection
createNativeThread(JavaEventListener);
javapipe.unlock();
end
function java_getLoadedClasses()
javapipe.lock()
javapipe.writeByte(JAVACMD_GETLOADEDCLASSES)
local classcount=javapipe.readDword()
local classes={}
if (classcount==nil) then
return nil
end
if classcount>0 then
local i=0
local length
for i=1,classcount do
classes[i]={}
classes[i].jclass=javapipe.readQword() --this is a pointer to a pointer to java.lang.class. To get the offset where klass is stored use getFieldFromType("java_lang_Class", "_klass_offset") (The klass contains a _fields field which points to a array which contains the offset of the fields. Might be useful)
length=javapipe.readWord()
classes[i].signature=javapipe.readString(length);
length=javapipe.readWord()
classes[i].generic=javapipe.readString(length);
end
end
javapipe.unlock()
return classes
end
function java_dereferenceLocalObject(object)
javapipe.lock()
javapipe.writeByte(JAVACMD_DEREFERENCELOCALOBJECT)
javapipe.writeQword(object)
javapipe.unlock()
end
function java_cleanClasslist(classlist)
local i
for i=1, #classlist do
java_dereferenceLocalObject(classlist[i].jclass)
end
end
function java_getClassMethods(class)
javapipe.lock()
javapipe.writeByte(JAVACMD_GETCLASSMETHODS)
javapipe.writeQword(class)
local count=javapipe.readDword()
local i
local result={}
local length
for i=1,count do
result[i]={}
result[i].jmethodid=javapipe.readQword()
length=javapipe.readWord()
result[i].name=javapipe.readString(length)
length=javapipe.readWord()
result[i].signature=javapipe.readString(length)
length=javapipe.readWord()
result[i].generic=javapipe.readString(length)
end
javapipe.unlock()
return result
end
function java_getClassFields(class)
javapipe.lock()
javapipe.writeByte(JAVACMD_GETCLASSFIELDS)
javapipe.writeQword(class)
local count=javapipe.readDword()
local i
local result={}
local length
for i=1,count do
result[i]={}
result[i].jfieldid=javapipe.readQword()
length=javapipe.readWord()
result[i].name=javapipe.readString(length)
length=javapipe.readWord()
result[i].signature=javapipe.readString(length)
length=javapipe.readWord()
result[i].generic=javapipe.readString(length)
end
javapipe.unlock()
return result
end
function java_getImplementedInterfaces(class)
result={}
javapipe.lock()
javapipe.writeByte(JAVACMD_GETIMPLEMENTEDINTERFACES)
javapipe.writeQword(class)
local count=javapipe.readDword()
for i=1,count do
result[i]=javapipe.readDword()
end
javapipe.unlock()
return result
end
function miJavaActivateClick(sender)
javaInjectAgent()
end
function javaForm_treeviewExpanding(sender, node)
local allow=true
print("javaForm_treeviewExpanding "..node.level)
if node.Level==0 then
if node.Count==0 then
--expand the class this node describes
local jklass=node.Data
local methods=java_getClassMethods(jklass);
local fields=java_getClassFields(jklass);
local interfaces=java_getImplementedInterfaces(jklass);
local i
node.add('---Implemented interfaces---');
for i=1, #interfaces do
local name
if interfaces[i]>0 then
name=java_classlist[interfaces[i]].signature
else
name='???'
end
node.add(string.format("%d : %s", interfaces[i], name))
end
node.add('---Fields---');
for i=1, #fields do
node.add(string.format("%x: %s: %s (%s)", fields[i].jfieldid, fields[i].name, fields[i].signature,fields[i].generic))
end
node.add('---Methods---');
for i=1, #methods do
node.add(string.format("%x: %s(%s) %s", methods[i].jmethodid, methods[i].name, methods[i].signature, methods[i].generic))
end
--java_getClassFields(jklass);
end
end
return allow
end
function miJavaDissectClick(sender)
--I coudl also implement the same method as mono, but as an example I'll be creating the form with code only
if (javaForm==nil) then
javaForm={}
javaForm.form=createForm()
javaForm.form.Borderstyle=bsSizeable
javaForm.treeview=createTreeview(javaForm.form)
javaForm.treeview.align=alClient
javaForm.treeview.OnExpanding=javaForm_treeviewExpanding
end
if (java_classlist~=nil) then
java_cleanClasslist(java_classlist) --prevent a memory leak
end
java_classlist=java_getLoadedClasses()
if (java_classlist~=nil) then
local i
for i=1,#java_classlist do
local node=javaForm.treeview.Items.Add(string.format("%d(%x) : %s (%s)", i, java_classlist[i].jclass, java_classlist[i].signature, java_classlist[i].generic ))
node.Data=java_classlist[i].jclass
node.HasChildren=true
end
end
javaForm.form.show()
end
function java_OpenProcessAfterwards()
local usesjava=false
local m=enumModules()
local i
java_classlist=nil
for i=1, #m do
if m[i].Name=='jvm.dll' then
usesjava=true
break
end
end
if usesjava then
if (miJavaTopMenuItem==nil) then
local mfm=getMainForm().Menu
local mi
miJavaTopMenuItem=createMenuItem(mfm)
miJavaTopMenuItem.Caption="Java"
mfm.Items.insert(mfm.Items.Count-1, miJavaTopMenuItem) --add it before help
mi=createMenuItem(miJavaTopMenuItem)
mi.Caption="Activate java features"
mi.OnClick=miJavaActivateClick
miJavaTopMenuItem.Add(mi)
mi=createMenuItem(miJavaTopMenuItem)
mi.Caption="Dissect java"
mi.Shortcut="Ctrl+Alt+J"
mi.OnClick=miJavaDissectClick
miJavaTopMenuItem.Add(mi)
end
end
end
function java_OpenProcess(processid)
if java_oldOnOpenProcess~=nil then
java_oldOnOpenProcess(processid)
end
synchronize(java_OpenProcessAfterwards) --call this function when the whole OpenProcess routine is done (next sync check)
end
function javaAA_USEJAVA(parameters, syntaxcheckonly)
--called whenever an auto assembler script encounters the USEMONO() line
--the value you return will be placed instead of the given line
--In this case, returning a empty string is fine
--Special behaviour: Returning nil, with a secondary parameter being a string, will raise an exception on the auto assembler with that string
if (syntaxcheckonly==false) and (javaInjectAgent()==0) then
return nil,"The java handler failed to initialize"
end
return "" --return an empty string (removes it from the internal aa assemble list)
end
function java_initialize()
--register a function to be called when a process is opened
if (java_init1==nil) then
java_init1=true
java_oldOnOpenProcess=onOpenProcess
onOpenProcess=java_OpenProcess
registerAutoAssemblerCommand("USEJAVA", javaAA_USEJAVA)
end
end
java_initialize()