AHK终于可以赚钱了,AutoHotkey源码破解和反破解全整理

AutoHotkey脚本源码保护问题,一直以来,相信是很多AutoHotkey脚本作者头疼的事情。因为无论编译不编译,分享脚本就相当于与分享了源码,近来官方也加强了脚本源码的保护,真心香啊!ahk脚本终于可以自由分享,且不用担心核心数据、算法被轻松拿走了,下面我整一下编译、反编译及相关办法的全部资料,相信你看了本文,定会有满满的收获(早想写,鉴于之前的形势不允许,或者说没有办法禁止反编译脚本!)。

为什么大家迫切需要源码保护?

1.无法保护专用资源,比如影子输入法中云输入的管道是专为影子输入法设计和使用的,没有源码保护机制,我们分享也是源码一并分享,这样这个管道就成为一个公开的管道了,用的人一多,避免不了的会出现卡顿,甚至无法使用的情况。

2.无法分享付费脚本,对于用户来说,能不付费为什么要付费,这里不是要批评谁,贪婪是人的本性之一,因为无法保护源码,所有的付费机制的根基就没有,这也成为了很多脚本作者的隐痛,辛辛苦苦写的脚本却没有任何收益……

3.无法防止别人的篡改,关于这个我就不多说了……

4.……

很多,不一一罗列了。

传统情况,脚本作者可以如何保护自己的脚本源码呢?

这里分两个流派!

1.源码加密派:

典型的代表人物有feiyue、Welt等

他们的方法就是把源码通过管道加密,执行的时候再通过管道解密出来,这种方法对于一般的小白用户是有效的,但是对于大部分autohotkey老油条是无效的。

原因1:内存转存法可以破解这种方法;

原因2:管道是用明文写在脚本中的,对于有系统编程基础的人来说逆管道操作也是易如反掌的事情。

2.编译加壳派:

典型的代表人物有TAC109等

官方提供了编译,正常的Ahk2Exe编译过程将创建一个可执行文件,其中包含AutoHotkey解释器,脚本及其包含的任何文件。所以只是编译,跟直接分享源码没有什么区别,用记事本就可以反编译脚本,获得源码。所以很多朋友又想到了加壳的办法,所以暂且我就叫它编译加壳派吧。同样这个方法对于小白用户是有效的,但是对于大部分autohotkey老油条是无效的。

原因1:加壳和破解,工具很多;

原因2:内存转存法可以破解这种方法。

综上,我们知道autohotkey源码保护的痛点,有两个:

第一:源码是明文读到内存里的;

第二:源码是明文放在编译文件中的。

新办法

针对上述的两个核心痛点下功夫,成功可期!

这个办法TAC109给搞出来了,虽然不是很完美,但是我觉得对于大部分用户够用了,这就是最新的内存加壳派的诞生。

ScriptGuard是保护反编译的脚本的新方法。与其他一些方法不同,它不使用任何嵌入式密码,并且ScriptGuard始终可以100%有效地发挥作用。

ScriptGuard包含以下两个安全措施:

  • ScriptGuard1-编译后的脚本开始执行后,立即从内存中删除脚本源代码。
  • ScriptGuard2-为脚本提供脚本混淆和反破壳。

ScriptGuard1

ScriptGuard1依赖于以下事实:已编译的AutoHotkey脚本分两个阶段处理(所有脚本也是如此):

  1. 首先,对源脚本进行语法检查,将其加载到内存结构中并进行优化
  2. 然后从内存中的优化副本执行加载的脚本。

正常的Ahk2Exe编译过程将创建一个可执行文件,其中包含AutoHotkey解释器,脚本及其包含的任何文件。运行编译的可执行文件时,它会在执行优化结果之前以上述方式读取和分析所包含的脚本。执行开始后,可执行文件中包含的原始脚本现在不再需要,可以安全地从内存中删除。

这就是ScriptGuard1所做的。在优化副本开始执行的同时,它会从内存中擦除原始脚本。由于脚本此时不在内存中,因此内存检查方法将不再能够找到原始脚本。

此外,如果编译可执行文件分发之前MPRESS或UPX压缩,嵌入可执行脚本将不可见之前执行。因此,只有一个很小的时间窗口,其中包含的脚本是可见的。可执行文件已解压缩之后以及嵌入式脚本执行之前。由于这是一个连续的过程,因此几乎没有机会以任何方式从可执行文件中提取源脚本。

现在是代码(在脚本中包含#Include)

; ------------------------------  ScriptGuard1  --------------------------------
ScriptGuard1()                    ; Hides AutoHotkey source in compiled scripts
{ ; By TAC109, Edition: 23Aug2020 ; To use just include this code in your script
  static _ := ScriptGuard1()      ; Is automatically actioned when script starts
  local ahk:=">AUTOHOTKEY SCRIPT<", pt:=rc:=sz:=0
  if A_IsCompiled                 ; See bit.ly/ScriptGuard for more details
  { if (rc:=DllCall("FindResource",  "Ptr",0, "Str",ahk, "Ptr",10, "Ptr"))
    && (sz:=DllCall("SizeofResource","Ptr",0, "Ptr",rc,  "Uint"))
    && (pt:=DllCall("LoadResource",  "Ptr",0, "Ptr",rc,  "Ptr"))
    && (pt:=DllCall("LockResource",  "Ptr",pt,"Ptr"))
    && (DllCall("VirtualProtect", "Ptr",pt, "Ptr",sz, "UInt",0x40, "UInt*",rc))
      DllCall("RtlZeroMemory", "UInt",pt, "Int",sz) ; Wipe script from memory
    else MsgBox 64,,% "Warning: ScriptGuard1 not active!`n`nError = "
      . (A_LastError=1814 ? ("Resource Name '" ahk "' not found.`nTo fix, see "
      . "the 'Example 1' comments at http://bit.ly/BinMod.") : A_LastError)
} }                               ; For additional security, see bit.ly/BinMod
; ------------------------------------------------------------------------------

该代码应被复制或下载,并存储在一个名为 ScriptGuard1.ahk。可以使用#include语句将其并入您的脚本中(在开始处或附近)。另外,此ScriptGuard1代码可以直接复制到您的脚本中。

当直接运行包含ScriptGuard1的脚本(无需先编译)时,此代码将无效。但是,在已编译的脚本中,当脚本开始运行时,该代码将自动执行。(不需要用户的脚本直接调用ScriptGuard1()函数。)

ScriptGuard1可以与任何版本的AutoHotkey 1.1一起使用,但不能与AutoHotkey_H一起使用。(如果将MsgBox代码更改为有效的v2语法,它也将与版本2一起使用。)BinMod的用户应检查该文档的示例1,以获取更多信息。

ScriptGuard2

ScriptGuard2通过对编译脚本时生成的AutoHotkey解释器进行少量更改,为ScriptGuard1提供了额外的安全性。使用此功能完全是可选的,但可以为ScriptGuard1提供一些额外的安全性。ScriptGuard2仅适用于使用32位* .bin文件的编译。

ScriptGuard2随AutoHotkey v1.1.33.00或更高版本附带的Ahk2Exe版本一起提供。对于早期版本的AutoHotkey,请从此处获取最新的Ahk2Exe beta。

此外,必须使用版本日期为2020年8月23日或更晚的BinMod,并且需要在脚本中由; @ Ahk2Exe-PostExec编译器指令调用。

通过提供特定的 / ScriptGuard2BinMod的参数。BinMod文档的示例3提供了更多详细信息。

以下是BinMod的源码:

;
;@Ahk2Exe-SetVersion     2020.08.23     ; Edition: 23 August 2020
;@Ahk2Exe-SetCopyright   TAC109
;@Ahk2Exe-SetCompanyName TAC109
;@Ahk2Exe-SetProductName BinMod
;@Ahk2Exe-SetDescription Binary file editor - see Ahk2Exe's PostExec directive


/*
 BinMod is a simple, fast binary file editor written by TAC109, designed to be
 called from Ahk2Exe's 'PostExec' compiler directive. (Use Ahk2Exe included with
 AutoHotkey v1.1.33+, or for earlier versions get the latest Ahk2Exe beta from
 https://www.autohotkey.com/boards/viewtopic.php?f=6&t=65095).
 
-------------------------  Installation Instructions  --------------------------
                           ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
 Compile BinMod.ahk using the bin file "Unicode 32bit.bin" or "ANSI 32bit.bin".
 Place the resulting BinMod.exe file in the compiler directory that contains
 Ahk2Exe.exe (usually "C:Program FilesAutoHotkeyCompiler").
------------------------------  Usage examples  --------------------------------
                                ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
 With the examples below, the user can make alterations to the compiled program
 to disguise that it is an AutoHotkey compiled script, & also improve security.
1. To replace ">AUTOHOTKEY SCRIPT<" with " DATA" (for example) in the .exe,
    add the following three lines to your script:
    
    ;@Ahk2Exe-Obey U_au, = "%A_IsUnicode%" ? 2 : 1 ; .Bin file ANSI or Unicode?
    ;@Ahk2Exe-PostExec "BinMod.exe" "%A_WorkFileName%"
    ;@Ahk2Exe-Cont  "%U_au%2.>AUTOHOTKEY SCRIPT<. DATA              "
   Note: In the example above, the 3rd line replacement field must be upper-case
    and space-filled to give a total of 19 characters in order for the compiled
    program to work correctly. This field should start with a space to avoid
    RCData collating problems with any 'FileInstall' commands in the script.
   Users of ScriptGuard1 (bit.ly/ScriptGuard) should add a 1 to the 3rd line:
    ;@Ahk2Exe-Cont  "1%U_au%2.>AUTOHOTKEY SCRIPT<. DATA              "
2. To change the "AutoHotkeyGUI" class to "My_String" (for example) add the next
    line to example 1 (or replace the 3rd line above if not needed):
    ;@Ahk2Exe-Cont  "%U_au%.AutoHotkeyGUI.My_String"
   Note: In this example, if the replacement field is shorter than 13
    characters it will be automatically padded with 0x00's (nul's).
3. Additional ScriptGuard security can be gained for 32-bit compiles by adding
    the next line to example 1 (or replace the 3rd line above if not needed):
    ;@Ahk2Exe-Cont  /ScriptGuard2     ; See bit.ly/ScriptGuard for more details.
4. To set the current date and time as the compile time in the generated .exe,
    add one of the following lines to example 1 (or replace the 3rd line above
    if not needed):
    ;@Ahk2Exe-Cont  /SetDateTime     ; Set current local date and time, or
    ;@Ahk2Exe-Cont  /SetUTC          ; Set current UTC date and time
5. To prevent the use of "UPX -d" to de-compress a UPX-compressed .exe
    add the following line to your script:
    ;@Ahk2Exe-PostExec "BinMod.exe" "%A_WorkFileName%" "11.UPX." "1.UPX!.", 2
   Note: In this example, there are empty replacement fields, so the matched
    strings will be completely replaced with 0x00's (nul's).
----------------------------  Parameters in detail  ----------------------------
                              ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
 Parameters are as follows (the first is mandatory, the rest are optional):
   1. FileName of file to be modified. (The file will stay the same length.)
   2. One or more parameters specifying changes to be made, with fields thus:
     a) "1" - match once with 1 byte/character binary file portion.
        "2" - match once with 2 byte/character binary file portion.
        These can be mixed and/or repeated to match more than once.
     b) Simple case-sensitive string for matching.
     c) Simple string to replace matched string.
        If shorter than b, it will be padded with 0x00's.
        If longer than b, an error message will be shown.
     Use the same separator between fields a, b, and c (e.g. "." or "`,").
     Parameter 2 can be repeated if required.
     Parameters containing spaces must be enclosed in double quotes.
   3. "/SetDateTime" or "/SetUTC" (without the quotes). This parameter can 
      occur once anywhere after the 1st parameter, and sets the compile time
      to the current date and time (local or UTC) in the generated .exe.
   4. "/ScriptGuard2" (without the quotes). This parameter can occur once
      anywhere after the 1st parameter. See bit.ly/ScriptGuard for more details.
      
--------------------------------------------------------------------------------
 The searching technique used was inspired by these posts:
   https://www.autohotkey.com/boards/viewtopic.php?f=76&t=13155 by gwarble,
   AutoHotkeySC.bin String Patcher for AHK_L 32 Unicode by SKAN 16-Nov-2010,
   autohotkey.com/board/topic/80585-how-to-manipulate-binary-data-with-pointers
*/





; ================================  Program  ===================================
#NoEnv                               ; For performance & future compatibility
#Warn                                ; For catching common errors
#MaxMem 4095                         ; Handle large files
SetBatchLines -1                     ; Run at full speed

global hFile := 0, Bin, B := {1:"UChar", 2:"UShort", 4:"UInt", 8:"UInt64"}
Mes:=">AHK WITH ICON<", Adr1:=Adr2:=Adr3:=Bit:=ErNo:=PEz:=0

if (A_PtrSize = 8)
	ErrMes("Cannot run on 64-bit AutoHotkey.exe!`n`nPlease compile as 32-bit.")

if 0 < 2
	ErrMes("Not enough parameters (minimum 2)!")

FileName := %true%                          ; 1st parameter is file name
FileGetSize Sz,  %FileName%
VarSetCapacity(Bin, Sz)
FileRead Bin, *c %FileName%
if ErrorLevel
	ErrMes("File cannot be opened!`n`n""" FileName """")

hFile:=DllCall("_lopen", "AStr",FileName, "Int",0x2) ; Open file for alteration

Loop % %false%                              ; Number of parameters
{ IfEqual A_Index, 1, continue              ; Skip filename
	Par := %A_Index%                          ; Get parameter
	
	if Par in /SetDateTime,/SetUTC,/ScriptGuard2 ; Need .exe for these parameters
		if ng(,2)!=0x5A4D || (Adr1:=ng(0x3C,2)) > StrLen(Bin)-4 || ng(Adr1)!=0x4550
		|| !(Bit:={0x014C:1,0x8664:2}[ng(Adr1+4,2)]) ; 1=32-bit, 2=64-bit
			ErrMes("File is not a valid .EXE for '" Par "'!`n`n""" FileName """")
			
	if par in /SetDateTime,/SetUTC            ; Set current date & time into .exe
	{ Date := par="/SetUTC" ? A_NowUTC : A_Now
		Date -= 1970, s                         ; Works until 19 Jan 2038! MS to fix
		VarSetCapacity(Rplc1,4), NumPut(Date,Rplc1,0,B.4)
		DllCall("_llseek", "UPtr",hFile, "UInt",Adr1+8,  "Int",0)
		DllCall("_lwrite", "UPtr",hFile, "UInt",&Rplc1, "UInt",4)
		continue
	}

	if (Par = "/ScriptGuard2" && !PEz)        ; Process ScriptGuard2 once
	{	Slen:=StrLen(Mes), VarSetCapacity(Srch1,Slen)  , StrPut(Mes,&Srch1,"UTF-8")
		Type:=3          , VarSetCapacity(Srch2,Slen*2), StrPut(Mes,&Srch2,"UTF-16")

		PEz := ng(Adr1+0x14,2) + 0x18           ; PE header fixed size
		if (Bit = 2)
		{ ErrMes("/ScriptGuard2 procedure is not available for 64-bit compiles.",0)
			continue
		}
		while --Type && !Adr3
		{ Loop % ng( Adr1+6, 2)                 ; Loop through sections
			{ Adr2 := Adr1+PEz + (A_Index-1)*0x28 ; Adr section
				if (Off:=InBuf(&Bin+ng(Adr2+0x14),ng(Adr2+8),&Srch%Type%,Slen*Type))>=0
				{ Adr3 := Off + (Slen+1)*Type + ng(Adr2+0xC) + (Bit=1?ng(Adr1+0x34):0)
					break 2
		} } }
		if (Adr3=0 || ng( ng(Adr2+0x14)+Off+(Slen+1)*Type, 8) 
		!= [0x6F6E20646C756F43,0x6C0075006F0043][Type])
			ErrMes("Could not perform /ScriptGuard2 procedure. (B1)",0)
			
		else {
			VarSetCapacity(Srch1,5,0x68), NumPut(Adr3,Srch1,1,"UInt")
			VarSetCapacity(Rplc1,1,0xB8)
			Loop % ng( Adr1+6, 2)                 ; Loop through sections again
			{ Adr2 := Adr1 + PEz + (A_Index-1)*0x28, Off := 0 ; Adr section
				if ng(Adr2+0x24)&0x20               ; Code section?
				{ while (Off > -1)
					{ if (Off:=InBuf(&Bin+ng(Adr2+0x14), ng(Adr2+8),&Srch1,5,++Off)) >= 0
						&& (ErNo:=1) && ng(Adr3:=ng(Adr2+0x14)+Off+5,1) = 0xE8
						&& (ErNo:=2) && ng(Adr3+5,2) = 0xC483
						{ DllCall("_llseek", "UPtr",hFile, "UInt",Adr3,    "Int",0)
							DllCall("_lwrite", "UPtr",hFile, "UInt",&Rplc1, "UInt",1)
							Adr2 := 0
							break 2
			} } } }
			if (Adr2)
				ErrMes("Could not perform /ScriptGuard2 procedure. (B" ErNo+2 ")",0)
		}
		continue
	}                                         ; Process text replacements
	while [1,1][Sep := SubStr(Par,A_Index,1)] ; Get separator after '1's and '2's
		continue
	Pfld := StrSplit(Par, Sep)                ; Split parameter into fields
	if (Pfld.MaxIndex() != 3 || Pfld.1 = "" || StrLen(Pfld.3) > StrLen(Pfld.2))
		ErrMes("Invalid parameter!`n`n""" Par """")

	Slen := StrLen(Pfld.2)                    ; Setup search & replace variables
	VarSetCapacity(Srch1, Slen,   0), StrPut(Pfld.2, &Srch1, "UTF-8")
	VarSetCapacity(Srch2, Slen*2, 0), StrPut(Pfld.2, &Srch2, "UTF-16")
	VarSetCapacity(Rplc1, Slen,   0), StrPut(Pfld.3, &Rplc1, "UTF-8")
	VarSetCapacity(Rplc2, Slen*2, 0), StrPut(Pfld.3, &Rplc2, "UTF-16")

	Loop % StrLen(Pfld.1)                     ; For each type of search
	{ Type := SubStr(Pfld.1, A_Index, 1)      ; Scan for search item
		If (Off := InBuf(&Bin, Sz, &Srch%Type%, Slen*Type )) < 0
			ErrMes("String not found!`n`n""" Pfld.2 """")

		Loop % Slen*Type                        ; Alter buffer & file
			NumPut(NumGet(Rplc%Type%, A_Index-1, B.1), Bin, Off+A_Index-1, B.1)

		DllCall("_llseek", "UPtr",hFile, "UInt",Off, "Int",0)
		DllCall("_lwrite", "UPtr",hFile, "UInt",&Rplc%Type%, "UInt",Slen*Type)
}	}

DllCall("_lclose", "UPtr",hFile)            ; Close file & finish
ExitApp 0

; ==============================  Subroutines  =================================
InBuf(hayP, hayS, neeP, neeS, sOff=0)       ; Search buffer; returns offset
{ Static Buf      ; InBuf() by wOxxOm @ www.autohotkey.com/forum/topic25925.html
	If (!VarSetCapacity(Buf))                 ; MCode
	{ h :=  "5589E583EC0C53515256579C8B5D1483FB000F8EC20000008B4D108B451829C129D9"
. "410F8EB10000008B7D0801C78B750C31C0FCAC4B742A4B742D4B74364B74144B753F93AD93F2"
. "AE0F858B000000391F75F4EB754EADF2AE757F3947FF75F7EB68F2AE7574EB628A26F2AE756C"
. "382775F8EB569366AD93F2AE755E66391F75F7EB474E43AD8975FC89DAC1EB02895DF483E203"
. "8955F887DF87D187FB87CAF2AE75373947FF75F789FB89CA83C7038B75FC8B4DF485C97404F3"
. "A775DE8B4DF885C97404F3A675D389DF4F89F82B45089D5F5E5A595BC9C2140031C0F7D0EBF0"
		VarSetCapacity(Buf, StrLen(h)//2)
		Loop % StrLen(h)//2
				NumPut("0x" SubStr(h,2*A_Index-1,2), Buf, A_Index-1, "Char")
	}
	Return DllCall(&Buf, "UInt",hayP, "UInt",neeP, "UInt",hayS, "Int",neeS
										 , "UInt",sOff)
}

ErrMes(Mes, Err:=1)                         ; Show error/warning message
{ MsgBox % Err ? 16 : 49,, % (Err ? "Error: " : "Warning: ") Mes
			. (Err ? "" : "`n`nPress 'OK' to continue, 'Cancel' to abandon.")
	IfMsgBox Cancel
		Err := 1                                ; Exit if cancel from warning msg
	if (hFile && Err)
		DllCall("_lclose", "UPtr",hFile)        ; Close file if open & error
	if (Err)
		ExitApp 1                               ; Exit if error
}

ng(Offset = 0, Size = 4)                    ; Shorten NumGet
{ return NumGet(Bin, Offset, B[Size])
}
; ===============================  Debugging  ==================================
VarOut(Name, ByRef var, len, f=0)           ; Variable to file; check value with
{	f := FileOpen(A_ScriptDir "Bin_" Name ".var", "w", "UTF-8-RAW") ;  hex viewer
	f.RawWrite(var, len)
	f.Close()
}
Hex(d)
{ return Format(" {:#X}",d)             ; Format hex for display
}
; ==============================  End of file  =================================

我已经测试过了,新方法可行!

人已赞赏
AHKV1学习

ScriptGuard:帮助保护编译的脚本免于反编译

2020-8-24 14:32:00

学习

AHK 源码加密器 v3.1 -FeiYue

2020-9-1 13:33:07

8 条回复 A文章作者 M管理员
  1. 好强 感觉还少了一个编译后的软件授权工具

  2. 这个更厉害了,论坛真的是大神辈出啊

  3. 统一的唯一的文件夹搜索框用于全文搜索文件(脚本、图片、文档、表格、音频、视频、等等、等等)

    • 这个网站已经提供了,右上角的收索框和右侧边条都可以全文检索

  4. 觉得,

    ahk缺少一个
    类似Windows7本地文件夹的
    总的
    统一的
    经过索引的
    可以全文搜索检索正文的
    支持批量下载和上传的
    可以付费提问和付费答疑的

    文件夹搜索框

    • 即将上线的圈子功能,可以提供付费提问和付费答疑

  5. ahk脚本和ahk中文网已经进入了一个新的时代,新的进程,新的空间。 ✗咧嘴笑✗

    • 谢谢你的肯定

个人中心
今日签到
有新私信 私信列表
搜索