-
5. 바이트코드 조작마술프로그래밍언어/Java(중급) 2020. 3. 26. 17:48
Moja.java
public class Moja {
public String pullOut() {
return "";
}
}
Masulsa.java
public class Masulsa {
public static void main(String[] args) {
System.out.println(new Moja().pullOut());
}
}
아무것도없는 moja클래스를 호출하여 "Rabbit"을 콘솔에 찍히게 한다.
public class Masulsa{ public static void main(String[] args){ try{ new ByteBuddy().redefine(Moaj.class) //모자클래스 재정의 .method(named("pullOut")).intercept(FixedValud.value(fixedValue:"Rabbit!")); //pullOut라는 함수를 가로채서 고정된값을 강제로 리턴시킨다 .make().savveIn(new File("모자클래스의 폴더패키지경로 지정" )); }catch(IOException e){ e.printStackTrace(); } } //System.out.println(new Moja(),pullout()); //클래스 로딩이 먼저되므로 같이하면 안된다 }
Loading a Calss issue
클래스 로더시에 풀패키지 경로가 로더 까지 포함되있다
즉 모자 클래스를 애플리케이션 클래스로더가 읽고 또 바이트 버디를 로딩할때 바이트 버디가 만들어주는 클래스로더로 모자 클래스
하나 더 읽을때 모자라는 클래스는 하나지만 JVM에서는 두개나마찬가지이다. 풀패키지 이름은같지만 다른 클래스여서 호환이 안된다.
코드를 조작하지 않고 MOJA에서 rabbit 꺼내기
-
Code 수정
- 이미 코드를 실행할때 변경전 모자 클래스파일을 읽어 인터럽트 하더라도 다시 모자클래스를 로딩하지 않는다
- 클래스 파일을 직접 참조하지 않고 문자열에서 참조하기
- 클래스 로딩 순서에 너무 의존적이다
public class Masulsa{ public static void main(String[] args){ CalssLoader classLoader = Masulsa.class.getClassLoader(); TypePoll typePool = TypePool.Default.of(classLoader); try{ new ByteBuddy().redefine( typePool.describe(s:"me.whiteship.Moda"),resolve() //모자라는 클래스를 읽지않는다 ,ClassFileLocator.ForClassLoader.of(classLoader)) .method(named("pullOut")).intercept(FixedValud.value(fixedValue:"Rabbit!")); .make().savveIn(new File("모자클래스의 폴더패키지경로 지정" )); //클래스파일을 변경시킨후 }catch(IOException e){ e.printStackTrace(); } } //System.out.println(new Moja(),pullout()); // 읽어온다 }
-
순서에 너무 의존적이다
-
다른 프로젝트에서 조작(Agent)
- console : mvn clean (바이트코드삭제 후 )
Agent
-
바이트코드를 조작해줄 MasulsaAgent 생성
public class MasulsaAgent{ public static vod premain(String agentaArgs, Instrumentation inst){ //바이트버디 의존성 사용 new AgentBuilder.Default() //기본 빌더 생성 .type(ElementMatchers.any()) //가져와서 .transform(new AgentBuilder.Transformer(){ //변경시킨다 @Override public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription type{ return builder.method(named("pullOut")).intercept(FixedValue.value(fixedValue:"Rabbit!")); // 이름이 pullOut라는 함수를 가로채서 고정값을 rabbit로 변경시킨다. } }).installOn(inst);//변경한것 instrumentation 에 적용 } }
-
Agent를 Jar로 패키징하기
-
특정값들을 넣어줘야되는데 manifest를 조작할 수 있는 플러그인을 maven manifest엔트리 안에다가 넣어준다.
Javaagent JAR 파일 만들기
https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html
- 붙이는 방식은 시작시 붙이는 방식 premain
- 런타임 중에 동적으로 붙이는 방식 agentmain이 있다. Instrumentation을 사용한다.
pom.xml <dependencies> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.10.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.2</version> <configuration> <archive> <index>true</index> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <mode>development</mode> <url>${project.url}</url> <key>value</key> <Premain-Class>me.whiteship.MasulsaAgent</Premain-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>
Transparent(기존코드를 건드리지 않는)
클래스로딩시 java agent를 거쳐서 변경된 바이트코드를 읽어오기 때문에 변경된 바이트코드가 메모리에 들어가고 그대로 동작한다. 바이트코드가 바끼지않아도 메모리 내부에서 읽어올때 이미 바껴져있다.
바이트코드 조작 라이브러리
-
ASM: https://asm.ow2.io/
-
Javassist: https://www.javassist.org/
-
ByteBuddy: https://bytebuddy.net/#/
'프로그래밍언어 > Java(중급)' 카테고리의 다른 글
프록시 패턴 (0) 2020.04.24 6. Spring에서 바이트 코드 조작 활용 예 (0) 2020.03.26 4. 바이트코드 조작 (0) 2020.03.26 3. 클래스 로더 (0) 2020.03.26 2. JVM 구조 (0) 2020.03.26 -