Byte-code Injection with Javassist
๐Ÿ“„

Byte-code Injection with Javassist

Created
Oct 28, 2020 01:48 PM
Tags
java
bci
ย 
ย 
๋งŒ์•ฝ ์ด๋ฏธ ๋ฐฐํฌํ•ด๋ฒ„๋ฆฐ ์ž๋ฐ” ์ฝ”๋“œ. ์ฆ‰, .class ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ?
ย 
Decompile๋„ ํ•˜๋‚˜์˜ ๋ฐฉ๋ฒ•์ด๊ฒ ์ง€๋งŒ... ์ด๋ณด๋‹ค๋Š” BCI(Byte-code Injection)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์กฐ๊ธˆ ๋” ํšจ์œจ์ ์ธ ๋ฐฉ๋ฒ•์ด๋‹ค.
ย 
Java๋Š” ๊ฐ๊ฐ์˜ ํด๋ž˜์Šค ํŒŒ์ผ์„ ๋ฉ”๋ชจ๋ฆฌ์— Loadํ•  ๋•Œ java.lang.ClassLoader ๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด Dynamicํ•˜๊ฒŒ, ํ•„์š”ํ•  ๋•Œ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
ย 
์ด๋ฅผ ์ด์šฉํ•˜์—ฌ ํด๋ž˜์Šค ํŒŒ์ผ์„ Modify ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋ฅผ BCI๋ผ๊ณ  ํ•œ๋‹ค.
ย 
์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์žˆ์ง€๋งŒ, ์—ฌ๊ธฐ์—์„œ๋Š” ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ํ•™์Šตํ•˜๊ธฐ ์‰ฌ์šด Javassist ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•˜์—ฌ BCI๋ฅผ ์–ด๋–ป๊ฒŒ ํ•˜๋Š”์ง€ ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.
ย 
๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ์–ด๋ ต์ง€ ์•Š๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ๊ฐ•๋ ฅํ•˜๊ณ  ๋‹ค์–‘ํ•œ API๋ฅผ ์ œ๊ณตํ•˜๊ธฐ์— ์ด๋ฅผ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค. ๊ฑฐ์˜ ๊ธฐ์กด์˜ Java ์ฝ”๋”ฉ๊ณผ ๋‹ค๋ฅผ ๊ฒƒ์ด ์—†๋Š” ์ˆ˜์ค€.
ย 
์˜ˆ์ œ๋ฅผ ํ•˜๋‚˜ ๋ณด์ž.
ย 
import javassist.ClassPool;
import javassist.CtClass;

ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("test.Rectangle");
ย 
์œ„ ์ฝ”๋“œ๋Š” javassist ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•˜์—ฌ test.Rectangle ์ด๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ Loadํ•œ ๊ฒƒ์ด๋‹ค.
ย 
์—ฌ๊ธฐ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋กœ test.Rectangle ํด๋ž˜์Šค์˜ Superclass๋ฅผ ์ง€์ •ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.
ย 
// cc := test.Rectangle class file (CtClass type)

cc.setSuperclass(cp.get("test.Point"));
cc.writeFile();
ย 
๊ฐ„๋‹จํ•˜์ง€ ์•Š์€๊ฐ€? ๋‹จ ๋„ค ์ค„์˜ ์ฝ”๋“œ๋กœ ์ด๋ฏธ ์ปดํŒŒ์ผ๊นŒ์ง€ ๋งˆ์นœ test.Rectangle ํด๋ž˜์Šค์— ๋Œ€ํ•ด, test.Point ๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ Loadํ•ด test.Rectangle ํด๋ž˜์Šค์˜ Superclass๋กœ ๋งŒ๋“ค์–ด์ฃผ๊ฒŒ ๋˜์—ˆ๋‹ค.
ย 
์ฐธ๊ณ ๋กœ ๋งˆ์ง€๋ง‰ writeFile() ์€ ์ˆ˜์ •์„ ๋งˆ์นœ test.Rectangle ํด๋ž˜์Šค๋ฅผ ๋‹ค์‹œ ์›๋ณธ ํด๋ž˜์Šค ํŒŒ์ผ์— Writeํ•œ๋‹ค๋Š” ์˜๋ฏธ. ๋”ฐ๋ผ์„œ ์œ„์˜ ์—ฐ์‚ฐ์„ ์ง„ํ–‰ํ•œ ์ดํ›„ test.Rectangle ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด, Superclass๋กœ test.Point ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ง„ test.Rectangle ํด๋ž˜์Šค๊ฐ€ Load๋  ๊ฒƒ์ด๋‹ค.
ย 

Reading and Writing Bytecode

ย 
์ด๋ ‡๊ฒŒ javassist.CtClass ๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด ํด๋ž˜์Šค ํŒŒ์ผ์„ ๋‚˜ํƒ€๋‚ด๊ฒŒ ๋˜๊ณ , ์ด ๊ฐ์ฒด๋Š” javassist.ClassPool ์ด๋ผ๋Š”, ํด๋ž˜์Šค ํŒŒ์ผ๋“ค์„ ๊ด€๋ฆฌํ•˜๋Š” Class Pool ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.
ย 
Class Pool. ๋‹ค์‹œ๋งํ•ด ์•ž์„œ Loadํ•œ ํด๋ž˜์Šค ํŒŒ์ผ(CtClass)๋“ค์„ ์บ์‹ฑ ํ•œ๋‹ค๋Š” ๋ง.
ย 
// cc := CtClass type Loaded Class file

byte[] buf = cc.toBytecode();
Class clazz = cc.toClass();
ย 
์ด์™€ ๊ฐ™์ด CtClass ๋Š” ํด๋ž˜์Šค ํŒŒ์ผ์— ๋Œ€ํ•ด ์ˆ˜์ •ํ•œ ๋’ค Bytecode ๋˜๋Š” java.lang.Class ํƒ€์ž…์˜ ๊ฐ์ฒด๋กœ Convert ๋˜ํ•œ ๊ฐ€๋Šฅํ•˜๋‹ค.
ย 
์ฐธ๊ณ ๋กœ ์•„์˜ˆ ํด๋ž˜์Šค๋‚˜ ์ธํ„ฐํŽ˜์ด์Šค ์ž์ฒด๋ฅผ ๋งŒ๋“ค์ˆ˜๋„ ์žˆ๋Š”๋ฐ ์ด๊ฑด ๋…ผ์™ธ. ์—ฌ๊ธฐ์„œ๋Š” BCI์— ๋Œ€ํ•ด์„œ๋งŒ ๋‹ค๋ฃจ๋„๋ก ํ•˜๊ฒ ๋‹ค.
ย 

Frozen Class

ย 
์œ„์™€ ๊ฐ™์ด Converting ๊ณผ์ •์„ ๊ฑฐ์นœ CtClass ๊ฐ์ฒด๋Š” Frozen Class๋ผ๊ณ  ๋ถˆ๋ฆฌ๋ฉฐ, ์ดํ›„ ํ•ด๋‹น ํด๋ž˜์Šค ํŒŒ์ผ์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค.
ย 
๋ฌผ๋ก  ์˜์›ํžˆ ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฒƒ์€ ์•„๋‹ˆ๊ณ , defrost() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ๋‹ค์‹œ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด ์ค„ ์ˆ˜ ์žˆ๋‹ค.
ย 
CtClass cc = cp.get("test.Rectangle");

cc.writeFile(); // frozen
cc.setSuperclass(/* ... */); // ERROR

cc.defrost(); // defrost
cc.setSuperclass(/* ... */); // OK
ย 

Class Search Path

ย 
์ง€๊ธˆ๊นŒ์ง€ ClassPool.getDefault() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ClassPool ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ›์•„์˜จ ๋’ค, ClassPool.get() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ํด๋ž˜์Šค ํŒŒ์ผ์„ Loadํ•˜๋Š” ๊ณผ์ •์„ ๋ณด์•˜๋‹ค. ๊ทธ๋Ÿผ ์ด ํด๋ž˜์Šค ํŒŒ์ผ์€ ์–ด๋””์„œ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ผ๊นŒ?
ย 
๊ธฐ๋ณธ์ ์œผ๋กœ ClassPool.getDefault() ๋ฉ”์„œ๋“œ๋Š” JVM๊ณผ ๋™์ผํ•œ ๊ฒฝ๋กœ์— ์œ„ํ•œ ํŒŒ์ผ์„ ๊ธฐ์ค€์œผ๋กœ ํด๋ž˜์Šค ํŒŒ์ผ์„ ํƒ์ƒ‰ํ•˜๊ฒŒ ๋œ๋‹ค. ๋”ฐ๋ผ์„œ, Tomcat๊ณผ ๊ฐ™์€ WAS(Web App Server)๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ํด๋ž˜์Šค ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜ ์•„์˜ˆ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.
ย 
pool.insertClassPath("/usr/local/javalib");

pool.insertClassPath(new URLClassPath(
	"www.javassist.org",
  80,
  "/java/",
  "org.javassist."
); // www.javassist.org:80/java/org/javassist

pool.insertClassPath(new ByteArrayClassPath("ClassName", buf));
ย 
๋”ฐ๋ผ์„œ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์œ„์™€ ๊ฐ™์ด Classpath๋ฅผ Insertํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ์›ํ•˜๋Š” ํด๋ž˜์Šค ํŒŒ์ผ์„ Loadํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์ •ํ•ด์ฃผ๋„๋ก ํ•˜์ž.
ย 

Class Loader

ย 
Java๋Š” java.lang.ClassLoader ๋ฅผ ์ด์šฉํ•ด ํด๋ž˜์Šค ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜จ๋‹ค๊ณ  ํ–ˆ๋‹ค.
ย 
์ด๋ฅผ ์ด์šฉํ•ด ํด๋ž˜์Šค ํŒŒ์ผ์„ Loadํ•  ๋•Œ ์ˆ˜์ •(BCI)ํ•  ์ˆ˜ ์žˆ์œผ๋‚˜, ์—ฌ๊ธฐ์„œ๋Š” ์ด๋ณด๋‹ค๋Š” ์กฐ๊ธˆ ๋” ์‰ฌ์šด ๋ฐฉ๋ฒ•์€ javassist.Loader ๋ฅผ ์ด์šฉํ•ด BCI๋ฅผ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.
ย 
์ฐธ๊ณ ๋กœ ์ง€๊ธˆ๊นŒ์ง€ ์ง„ํ–‰ํ–ˆ๋˜ ๋ฐฉ๋ฒ•์€ ํด๋ž˜์Šค ํŒŒ์ผ์„ ์ง์ ‘ ์ง€์ •ํ•ด์„œ Loadํ•œ ๋’ค BCI๋ฅผ ํ–ˆ๋˜ ๊ฒƒ์ด๊ณ , ์ง€๊ธˆ ๋ณผ ๋ฐฉ๋ฒ•์€ Class Loader๋ฅผ ํ†ตํ•ด Load๋˜๋Š” ํด๋ž˜์Šค ํŒŒ์ผ์— ๋Œ€ํ•ด BCI๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด ์ฐจ์ด๋ฅผ ์•Œ์•„๋‘์ž.
ย 
public class Main {
  public static void main(String[] args) throws Throwable {
		ClassPool cp = ClassPool.getDefault();
		Loader cl = new Loader(cp); // javassist.Loader

    // ์ง€๊ธˆ๊นŒ์ง€ ๋ณด์•˜๋˜ ๋ฐฉ๋ฒ•
		CtClass ct = cp.get("test.Rectangle");
    ct.setSuperclass(cp.get("test.Point"));

    // Class Loader๋ฅผ ์ด์šฉํ•ด ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•
    Class c = cl.loadClass("test.Rectangle");
    Object rect = c.getDeclaredConstructor().newInstance();
  }
}
ย 
๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š๋‹ค. ๋‹จ, ์œ„์™€ ๊ฐ™์ด javassist.Loader ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒฝ์šฐ ํด๋ž˜์Šค ํŒŒ์ผ์„ Loadํ•  ๋•Œ(loadClass()) BCI๋ฅผ ํ•˜๋„๋ก ์ผ์ข…์˜ ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•ด๋†“์„ ์ˆ˜ ์žˆ๋‹ค.
ย 
public interface Translator {
  public void start(ClassPool cp) throws NotFoundException, CannotCompileException;
  public void onLoad(ClassPool cp, String classname)
    throws NotFoundException, CannotCompileException;
}
ย 
์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์˜ ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์œ„์™€ ๊ฐ™์œผ๋ฉฐ, ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ด๋ฒคํŠธ์˜ ๋“ฑ๋ก์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
ย 
public class MyTranslator implements Translator {
  @Override
  public void start(ClassPool cp) throws NotFoundException, CannotCompileException { }

  @Override
  public void onLoad(ClassPool cp, String classname)
    throws NotFoundException, CannotCompileException {
    CtClass cc = cp.get(classname);
    cc.setModifiers(Modifier.PUBLIC); // modified class modifier

    // ...
  }
}
ย 
์ด๋Ÿฐ ์‹์ด๋‹ค. ๊ฐ ๋ฉ”์„œ๋“œ์˜ ์˜๋ฏธ๋Š” ๋‹ค์Œ์„ ์ฐธ๊ณ .
ย 
  • start() : Translator ๊ฐ€ ๋“ฑ๋ก๋  ๋•Œ ํ˜ธ์ถœ
  • onLoad() : javassist.Loader ๊ฐ€ ํด๋ž˜์Šค๋ฅผ Loadํ•˜๊ธฐ ์ „ ํ˜ธ์ถœ
ย 
๋”ฐ๋ผ์„œ ์ผ๋ฐ˜์ ์œผ๋กœ onLoad() ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค.
ย 
public class Main {
  public static void main(String[] args) throws Throwable {
    ClassPool cp = ClassPool.getDefault();
    Loader cl = new Loader(cp);

    Translator t = new MyTranslator();

    cl.addTranslator(cp, t);
    cl.loadClass("test.Rectangle"); // emit => run MyTranslator
  }
}
ย 
์‚ฌ์šฉ์€ ์œ„์™€ ๊ฐ™์ด Loader.addTranslator() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค.
ย 

Introspection and Customization

ย 
์ง€๊ธˆ๊นŒ์ง€๋Š” ํด๋ž˜์Šค์— ๋Œ€ํ•ด์„œ๋งŒ ๋‹ค๋ฃจ์—ˆ์œผ๋‚˜, ๋ฉ”์„œ๋“œ ์—ญ์‹œ ์ˆ˜์ •์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
ย 
๋ฉ”์„œ๋“œ๋ฅผ ํ‘œํ˜„ํ•˜๋Š” javassist.CtMethod ๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•˜๋ฉฐ, insertBefore() , insertAfter() , insertAt() ๊ณผ ๊ฐ™์€ API๋ฅผ ์ด์šฉํ•ด ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ BCI๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. ๋‹จ, ๋“ค์–ด๊ฐ€๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” String ์ด๋‚˜, ๋‹ค์Œ ์„ธ ๊ฐ€์ง€ ํ˜•ํƒœ๋งŒ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
ย 
  • Inline: System.outPrintln("Hello");
  • Block: { System.out.println("Hello"); }
  • Statement: if (i < 0) { i = -1; }
ย 
๋˜ํ•œ ๋‹ค์Œ์˜ Special Character๋ฅผ ์ด์šฉํ•ด BCI์˜ ํƒ€๊นƒ์ด ๋˜๋Š” ๋ฉ”์„œ๋“œ์˜ Parameters์— ๋Œ€ํ•œ ์ฐธ์กฐ ์—ญ์‹œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
ย 
  • $0 : this Context
  • $1 , $2 , ... : Parameters
  • $_ : Return Value
ย 
๋‹ค๋ฅธ ๊ฒƒ๋“ค๋„ ๋งŽ์œผ๋‚˜, ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์€ ์œ„์™€ ๊ฐ™๋‹ค. ์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ๋Š” ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ฐธ๊ณ .
ย 
// cm := CtMethod type object

cm.insertAfter(String.join("\n",
  "System.out.println(\"Hello!\");",
  "if ($1 == true) {",
  "  System.out.println(\"Hello2\");",
  "}"
);
ย 
Field ์—ญ์‹œ ์ƒ์„ฑ์ด ๊ฐ€๋Šฅ. ์ด ๋•Œ๋Š” CtField.make() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•œ๋‹ค.
ย 
// cc := CtClass type Object

cc.addField(CtField.make("private boolean isValid = true;", cc));
ย 
์‹ค์ œ ์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ํด๋ž˜์Šค ํŒŒ์ผ ๋‚ด์— BCI ๋˜๋Š” ๊ฒƒ์ด๋‹ค.
ย 
์—ฌ๊ธฐ๊นŒ์ง€๊ฐ€ javassist ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•œ ํŠœํ† ๋ฆฌ์–ผ์ด๋ฉฐ, ๊ต‰์žฅํžˆ ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•๊ณผ API๋ฅผ ์ œ๊ณตํ•˜๋‹ˆ ํ•„์š”ํ•˜๋‹ค๋ฉด ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜๋„๋ก ํ•˜์ž.

Loading Comments...