Burp suite는 free 버전과 pro 버전으로 나뉘어져 있고 둘의 차이는 굉장히 큽니다.
burp 1.5? 1.6? ... 음 1.5 버전같네요. 아무튼 Larry Lau가 매번 버전마다 BurpLoader.jar를 만들어 배포하고 있습니다.
이 BurpLoader.jar는 말그대로.. 크랙 파일인데요 어떤 방식으로 Burp Pro를 로드했는지, BurpLoader.jar에 악성코드나 Exploit 코드는 없는지 궁금증에 분석을 시작했었습니다.
물론 만족할만한 결과를 얻지는 못했네요. (저와 같은 생각을 했던 분들을 보며 좀 더 배우는 시간이 되었지요)
Analysis BurpLoader.jar Part 1
Analysis BurpLoader.jar Part 2
Analysis BurpLoader.jar Part 3
Analysis point
일단 가장 분석의 목적이 어떻게 Pro를 로드했는지, BurpLoader.jar 파일에 악의적인 코드는 없는지 확인하는 것 입니다.
첫번째 목적은 취약점 분석과 비슷한 형태로 접근하려하고 두번째는 악성코드 분석 느낌으로 접근하려 합니다.
일단 취약점은.. 천천히 봐야하니 악성코드부터 생각해보았습니다. 어떤 포인트들이 있을건지
1. 백도어 여부?
2. 프록시에 쌓인 데이터, 또는 취약점 데이터를 다른 서버로 전송하지 않는지?
3. Exploit 코드로 추가적인 프로그램이나 명령행을 다운로드 하는지?
저는 jd-gui를 자주 사용합니다. 안드로이드 악성코드 분석에서도 많이 사용했었기 때문에 편리해서 그런 것 같네요. 거기에 Eclipse와 JAD 조합도 사용하죠.
일단 바로 jar 파일을 jd-gui로 보겠습니다.
음 ... ldc 부분에 난독화로 인해 ERROR가 발생하고, 우리가 확인할 수 있는 코드는 매우 한정적이네요(없어요 거의)
그래서 필요한게 Procyon-decompiler입니다. jd-gui와는 다르게 난독화를 풀어서 볼 수 있어서 분석에 조금 더 용이하지요.
물론 Eclipse에서 jad 로드 후 jad로 디컴파일해서 보시는 것도 좋은 방법이죠. (그냥 jad로 푸셔도 되긴하느넫... 뒷감당은 알아서)
procyon 다운로드 링크
https://bitbucket.org/mstrobel/procyon/downloads/
-jar 옵션을 주어서 BurpLoader.jar를 풀어줍니다.
# procyon -jar BurpLoader.jar
procyon으로 풀었지만.. 아직 ldc 부분에 난독화된 Bytecode가 들어있습니다. 저와 같은 생각을 한 다른 분들의 글을 보면 ConstantValue Code! 부분에서 ZKM8.0.1E 알고리즘으로
난독화 된다는 걸 알았다고 하는데.. 전 상위버전인 1.7이라 그런가 도저히 못찾겠네요.
h r err Ljava/io/PrintStream;
t u � v java/io/PrintStream x println
z 0
ConstantValue Code! } }
아무튼 그럼 저부분이라도 빼고 봐야겠지요.Code Analysis
일단 프로키온으로 푼 데이터를 보면 초기에 jd-gui 보단 훨씬 많은 코드가 남아있습니다.------------------
//
// Decompiled by Procyon v0.5.30
//
package larry.lau;
import java.lang.reflect.Field;
import java.io.InputStream;
import java.security.CodeSource;
import java.awt.HeadlessException;
import java.awt.GraphicsEnvironment;
import java.util.prefs.Preferences;
import java.awt.Component;
import javax.swing.JOptionPane;
import java.security.MessageDigest;
import javax.swing.UIManager;
public class BurpLoader
{
public static final String readme0 = "Burp Suite is an integrated platform for performing security testing of web applications. ";
public static final String readme1 = "If you like it, please try Free edition or <b>buy</b> Pro edition.";
public static final String readme2 = "This loader is Free and CAN NOT be used for Commercial purposes!";
public static final String readme3 = "If you bought it somewhere else, you should take action against the seller.";
public static final String readme4 = "No exploiting and no malware in my code. Shaby is boring!";
public static final String readme5 = "Usage:";
public static final String readme6 = "1. you need a burpsuite_pro jar file.";
public static final String readme7 = "2. add burpsuite_pro jar into classpath then run burploader";
public static final String readme8 = "<ul><li>java -jar BurpLoader.jar</li>";
public static final String readme9 = "<li>java -cp BurpLoader.jar;burpsuite_pro.jar larry.lau.BurpLoader</li>";
public static final String readme10 = "<li>java -cp BurpLoader.jar:burpsuite_pro.jar larry.lau.BurpLoader</li></ul>";
public static final String readme11 = "3. To Support headless mode, add -Djava.awt.headless=true into jvm arguments.";
public static final String readme12 = "4. Any suggestions, let me know.";
private static final String[] a;
private static final String[] b;
private static final String[] c;
private static final String[] d;
private static final String[] e;
public static boolean f;
public static boolean g;
private static final String[] z;
static {
//
// This method could not be decompiled.
//
// Original Bytecode:
//
// 0: bipush 12
// 2: anewarray Ljava/lang/String;
// 5: dup
// 6: iconst_0
// 7: ldc "]\u001fAp\"d\""
// 9: jsr 216
// 12: aastore
// 13: dup
// 14: iconst_1
// 15: ldc "|5\u001d"
// 17: jsr 216
// 20: aastore
// 21: dup
// 22: iconst_2
// 23: ldc "s$Zmn"
...... // 결국 풀지못한 ldc의 존재
//
// java.lang.IllegalStateException: Expression is linked from several locations: Label_1611:
// at com.strobel.decompiler.ast.Error.expressionLinkedFromMultipleLocations(Error.java:27)
// at com.strobel.decompiler.ast.AstOptimizer.mergeDisparateObjectInitializations(AstOptimizer.java:2592)
// at com.strobel.decompiler.ast.AstOptimizer.optimize(AstOptimizer.java:235)
// at com.strobel.decompiler.ast.AstOptimizer.optimize(AstOptimizer.java:42)
// at com.strobel.decompiler.languages.java.ast.AstMethodBodyBuilder.createMethodBody(AstMethodBodyBuilder.java:214)
// at com.strobel.decompiler.languages.java.ast.AstMethodBodyBuilder.createMethodBody(AstMethodBodyBuilder.java:99)
// at com.strobel.decompiler.languages.java.ast.AstBuilder.createMethodBody(AstBuilder.java:757)
// at com.strobel.decompiler.languages.java.ast.AstBuilder.createMethod(AstBuilder.java:655)
// at com.strobel.decompiler.languages.java.ast.AstBuilder.addTypeMembers(AstBuilder.java:532)
// at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeCore(AstBuilder.java:499)
// at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeNoCache(AstBuilder.java:141)
// at com.strobel.decompiler.languages.java.ast.AstBuilder.createType(AstBuilder.java:130)
// at com.strobel.decompiler.languages.java.ast.AstBuilder.addType(AstBuilder.java:105)
// at com.strobel.decompiler.languages.java.JavaLanguage.buildAst(JavaLanguage.java:71)
// at com.strobel.decompiler.languages.java.JavaLanguage.decompileType(JavaLanguage.java:59)
// at com.strobel.decompiler.DecompilerDriver.decompileType(DecompilerDriver.java:317)
// at com.strobel.decompiler.DecompilerDriver.decompileJar(DecompilerDriver.java:238)
// at com.strobel.decompiler.DecompilerDriver.main(DecompilerDriver.java:138)
//
throw new IllegalStateException("An error occurred while decompiling this method.");
}
public static void main(final String[] array) {
final boolean g = BurpLoader.g;
try {
try {
final String s = BurpLoader.z[3];
Class.forName(s);
UIManager.installLookAndFeel(BurpLoader.z[0], s);
}
catch (ClassNotFoundException ex9) {}
final Class<?> forName = Class.forName(BurpLoader.z[9]);
try {
final CodeSource codeSource = forName.getProtectionDomain().getCodeSource();
final MessageDigest instance = MessageDigest.getInstance(BurpLoader.z[1]);
final InputStream openStream = codeSource.getLocation().openStream();
final byte[] array2 = new byte[1024];
int read = 0;
MessageDigest messageDigest = null;
while (true) {
Label_0108: {
boolean f = false;
Label_0096: {
Label_0090: {
try {
if (!g) {
break Label_0108;
}
final boolean b = BurpLoader.f;
if (b) {
break Label_0090;
}
break Label_0090;
}
catch (ClassNotFoundException ex) {
throw ex;
}
try {
final boolean b = BurpLoader.f;
if (b) {
f = false;
break Label_0096;
}
}
catch (ClassNotFoundException ex2) {
throw ex2;
}
}
f = true;
}
BurpLoader.f = f;
messageDigest.update(array2, 0, read);
}
if ((read = openStream.read(array2)) > 0) {
continue;
}
openStream.close();
messageDigest = instance; // 아까 받아온 BurpLoader의 MD
if (g) {
continue;
}
break;
}
StringBuffer sb = null;
Label_0192_Outer:
while (true) {
final byte[] digest = messageDigest.digest();
sb = new StringBuffer();
int n = 0;
while (true) {
while (true) {
Label_0195: {
try {
if (!g) {
break Label_0195;
}
sb.append(String.format(BurpLoader.z[11], digest[n] & 0xFF));
}
catch (ClassNotFoundException ex3) {
throw ex3;
}
++n;
}
if (n < digest.length) {
continue Label_0192_Outer;
}
break;
}
if (!g) {
break;
}
continue;
}
}
int equals = 0;
Label_0259: {
Label_0234: {
int n2;
try {
n2 = (equals = (BurpLoader.z[8].equals(sb.toString()) ? 1 : 0));
if (g) {
break Label_0259;
}
if (n2 == 0) {
break Label_0234;
}
break Label_0234;
}
catch (ClassNotFoundException ex4) {
throw ex4;
}
try {
if (n2 == 0) {
JOptionPane.showMessageDialog(null, BurpLoader.z[5], BurpLoader.z[7], 0);
System.exit(-1);
}
}
catch (ClassNotFoundException ex5) {
throw ex5;
}
}
equals = 0;
}
int n3 = equals;
while (true) {
Label_0329: {
if (!g) {
break Label_0329;
}
final Field declaredField = Class.forName(BurpLoader.z[2] + BurpLoader.b[n3]).getDeclaredField(BurpLoader.c[n3]);
declaredField.setAccessible(true);
declaredField.set(null, BurpLoader.a[n3]);
++n3;
}
if (n3 < BurpLoader.b.length) {
continue;
}
break;
}
Preferences preferences = null;
Label_0365_Outer:
while (true) {
final Preferences node = Preferences.userRoot().node(BurpLoader.z[4]);
// 각 OS별 설정값 로드
int n4 = 0;
while (true) {
while (true) {
Label_0411: {
try {
if (!g) {
break Label_0411;
}
}
catch (ClassNotFoundException ex6) {
throw ex6;
}
if (!BurpLoader.e[n4].equals(preferences.get(BurpLoader.d[n4], null))) {
node.put(BurpLoader.d[n4], BurpLoader.e[n4]);
}
++n4;
}
if (n4 < BurpLoader.d.length) {
continue Label_0365_Outer;
}
break;
}
preferences = node;
if (!g) {
break;
}
continue;
}
}
preferences.flush();
//
// BurpLoader.z[6] > burp 메인부분
forName.getDeclaredMethod(BurpLoader.z[6], String[].class).invoke(null, array);
}
catch (Throwable t) {
t.printStackTrace();
int headless = GraphicsEnvironment.isHeadless() ? 1 : 0;
Label_0518: {
int n5 = 0;
Label_0517: {
try {
n5 = headless;
if (g) {
break Label_0518;
}
if (n5 != 0) {
break Label_0517;
}
}
catch (ClassNotFoundException ex7) {
throw ex7;
}
try {
GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
}
catch (HeadlessException ex10) {
headless = 1;
}
catch (InternalError internalError) {
headless = 1;
}
}
try {
if (n5 != 0) {
System.err.println(BurpLoader.z[10]);
if (!g) {
return;
}
}
}
catch (ClassNotFoundException ex8) {
throw ex8;
}
}
JOptionPane.showMessageDialog(null, BurpLoader.z[10], BurpLoader.z[7], 0);
}
}
catch (ClassNotFoundException ex11) {
JOptionPane.showMessageDialog(null, BurpLoader.z[5], BurpLoader.z[7], 0);
System.exit(-1);
}
}
}
Runtime Analysis
코드분석은 여기까지하고 실행 과정에서 어떤 것들이 있나 하나하나 까보기 시작했습니다.먼저.. Process Explorer로 실행 후 액션을 보겠습니다.
스냅샷에선 일단 별다른건 없었고 레지스트리 내 키 값만 조금 추가되었습니다. 그리고.. IPv6 관련해서 하나 세팅된 게 있구요.
음 흐름 상 어떤것들이 있었나 보니.. 프로그램 실행 후 특정 IP와 데이터 통신이 있습니다. 주기적으로 게속 통신하는데, 어떤걸 의미하는지 찾아봐야겠지요.
WireShark로 패킷덤프를 떠봅니다.
https://ec2-54-246-133-196.eu-west-1.compute.amazonaws.com 쪽으로 연결됩니다. 해당 페이지로 직접 접근하면 PostSwigger로 Redirection 되는데,
이게 Larry껀지 Burp껀지 확인이 필요했습니다. 먼저 해당 도메인은 PostSwigger 쪽 인증서 사용합니다. 그래서 저는 2가지 정도의 추측을 했습니다.
추측1: 원래 PostSwigger꺼
추측2: 인증과정 우회를 위해 임시 서버 사용(키탈취)
일단 whois 결과에선 별다른게 없었기 때문에 원래 burp에서 발생하는 요청과 비교하여 찾아보려했습니다. 그 결과..
동일하게 요청이 나가고 원래 Burp에서 발생하는 요청으로 볼 수 있죠. 이 친구 역할이 인증 정보에 대해 갱신하는 내용이라면 재미있는 장난을 해볼 수 있겠네요.
일단 현재까지는 BurpLoader.jar에서 Malware나 Exploit의 흔적은 발견할 수 없었습니다. 다만 난독화로 풀지 못한 ldc 값의 내용이 관건이 될 것 같네요.
Final
어떤 취약점을 사용했는지?A: 정확하게 판단되지는 않으나 라이선스 키를 로드하고 서버와 체크하는 과정에서 포인트가 있는 것 같습니다. 알려주진 않겠지만 Larry가 제일 잘 아는 내용이겠죠.
1. 백도어 여부?
A: 정확하진 않지만 BurpLoader 내 코드를 봐선 발생하기 어려운 부분입니다. 또한 동적 테스팅에서도 별다른 징후가 없었고 만약 있었다고 하면
다른분들이 쉽게 찾아내지 않았을까 합니다. 단정짓기는 어려우나 현재까지 분석 내용으로는 보이진 않았습니다. (안전하다는건 아니죠)
2. 프록시에 쌓인 데이터, 또는 취약점 데이터를 다른 서버로 전송하지 않는지?
A: 실행 및 동작중에 원격지와 통신하는 기록이 있긴합니다. 다만 portswigger 소유의 사이트로 보이고 일반적인 burp도 동일하게 동작하기 때문에
라이센스나 업데이트 관련해서 주기적으로 체크하는 로직이 아닐까 합니다.
3. Exploit 코드로 추가적인 프로그램이나 명령행을 다운로드 하는지?
A: 사실 제일 걱정된 부분입니다. 이런 내용은 짧은 코드로도 영향력이 나타날 수 있기 때문에 위험성도 높은데가가 코드 내 ldc 부분을 풀지 못한게 가장 컸던 것 같네요.
일단 ldc에는 실제 공격에 사용된 데이터(Burp쪽 취약점)이 담겨있을 것 같지만 한 두 줄 더 차이난다고 눈에 쉽게 드러나지는 않으니 확신할 수 없는 부분입니다.
Part2에서 조금 더 들여다보았습니다. 참고해주시길!
http://www.hahwul.com/2017/06/hacking-analyzing-burploaderjar-in-burp_20.html
Reference
http://0xd0m7.blogspot.com/2016/04/analizing-burploaderjar-larry-lau.htmlhttps://www.blackh4t.org/archives/1793.html
HAHWULSecurity engineer, Gopher and H4cker! |
저도 크랙버전 쓰면서 의심을 갖고 사용했었는데 이런 일상(?)분석글이어서 그런지 더 공감가네요 ㅎㅎ 잘 읽었습니다.
ReplyDelete아무래도 Larry 본인은 Exploit이나 Malware가 없다고 이야기해도, 우리가 직접 눈으로 확인하지 않는 이상 불안함이 떠나진 않죠.
Delete재미있게 읽으신 것 같으니 다행이네요. 감사합니다 :)
http://www.hahwul.com/2017/06/hacking-analyzing-burploaderjar-in-burp_20.html
ReplyDeletePart3
ReplyDeletehttp://www.hahwul.com/2017/12/hacking-analyzing-burploaderjar-in-burp.html