Nyctalopia - minimalistic flow obfuscator

Probléma: Hogyan tudod megvédeni a szellemi tulajdont másodrendű életformákkal, script kiddie-kkel szemben?

Motiváció

Én hiszek az open source felfogásban, és magam is hozzájárulok a szabad szoftverek fejlődéséhez. Nem pártolom az olyan pici-puha szoftvercégeket, amik titkolózással kompenzálják a gyengébb minőségű műszaki megoldásokat (kulturális szösszenet). Egy műszaki megoldás értékét nem az adja, hogy a szerzőn kívül senki nem ismeri a megvalósítás részleteit; ha a megoldás jó, úgy akkor is értékes marad, ha a "műhelytitkok" napvilágra kerülnek. További kapcsolódó írásokat itt találsz.

A tudás hatalom, és akinek jó ötletei, szabadalmai vannak, ügyes megoldásokat kreál, az megérdemli, hogy jól megéljen belőlük. Amennyiben úgy dönt, hogy a szellemi tulajdonát, műveit nyilvánosságra hozza, úgy rendelkezhet arról, hogy ki, hogyan, milyen célra, milyen megszorításokkal használhatja fel azt. Erre valók a szabadalmak és a licenszek.

Vannak sajnos olyanok, akik mások munkáját, tulajdonát kevésbé tisztelik, mások műveit lekoppintják, és sajátjukként feltüntetve értékesítik. Annak ellenére, hogy azok mondjuk szerzői jogvédelem, pl. open source licensz alatt állnak. Nyilván ilyenkor lehet pereskedni, de nem mindig érdemes. Egy ilyen precedens a mozgatórugója ennek a virrasztásnak (1 napos projektnek).

Műszaki háttér

A Java forráskód jvm bytecode-ra fordul, ami felépítésében hasonlít a gépi kódhoz, viszont nem optimalizált. Maga a jvm olvassa fel ezt a köztes formát, és fordítja le gépi kód utasításokra az adott hardverhez. Ennek a koncepciónak az előnye a platform függetlenség, hátrányaként sokszor a futási sebességet róják fel. Járulékos tulajdonsága viszont az is, hogy mivel a java compliler-nek viszonlag egyszerű transformációkat kell csak végrehajtania, a java bytecode-ot sokkal könnyebben vissza lehet forgatni Java forrássá, mint mondjuk a gépi kódot C-re. Számos hatékony java-decompiler található odakint, az ilyen grafikus eszközök használata már egyáltalán nem igényel szakértelmet.

Vannak olyan eszközök, amik a bytecode-ot úgy transzformálják, hogy különböző részeket - például debug-információt, dead code-ot - eltávolítanak, és a belső változók nevét lerövidítik. Ennek eredménye képpen kisebb, valamivel gyorsabb bytecode-ot kapunk. Folyományként viszont a visszaforgatás nehezebbé válik, és a visszafejtett állomány nehezebben lesz olvasható. Ezeket az eszközöket obfuscator-nak, vagy bytecode optimizer-nek hívjuk. Használatuk nem jelent különösebb védelmet a visszafejtés ellen. Léteznek olyan, feljettebb obfuscator-ok, amik a vezérlési folyamot is megkeverik, így valamelyest megváltoztatják a program futását olyan módon, hogy a program azért ugyanazt tegye; ezeket control flow obfuscator-nak nevezzük.

Alapvető elméleti tudás birtokában belátható, hogy nem lehet tetszőleges bytecode-ot java forrássá alakítani, mint ahogy az is, hogy egy adott bytecode-hoz végtelen olyan különböző bytecode megadható, ami "ugyanazt csinálja".

Célkitűzés

Adott java bytecode-ot szeretném úgy transzformálni, hogy a program működése ne változzon, viszont a visszaforgatás hibát adjon, vagy olyan kimenetet generáljon ami nem érvényes java, és nem lehet túl könnyen azzá alakítani. Fontos szempont volt, hogy minél kevesebb módosítással minél jobb eredményt érjek el. A kísérleti nyuszi a véleményem szerint jelenleg legjobban használható jd-gui decompiler volt.

Fontos tény, hogy kellő tudás és ráfordítás mellett mindig rekonstruálható, megérthető lesz a program. A bizonytalanságon/titkolózáson alapuló biztonság bár elriaszthatja a gyengébbeket, a felkészült támadót nem képes megállítani, maximum lelassítani. A cél most a paraziták elleni védelem.

Hogyan?

Először is érteni kell a compiler technológiához, és a jvm bytecode-hoz. Mivel minimális módon szeretném csak módosítani a bytecode-ot, olyan utasítás-sorozatot kerestem, amit bizonyos helyekre be tudok szúrni anélkül, hogy komplex elemzést (control flow analízist) kellene végrehajtanom az egyes metódusokon. Idempotens, nem intrúzív megoldás, ami viszont kellően összezavarja a decompilert.

			
/*
 * Nyctalopia - minimalistic flow obfuscator
 * 
 * Copyright 2011 Tibor Bősze <tibor.boesze@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package hu.lithium.nyctalopia;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

class ObfuscatingClassAdapter extends ClassAdapter {

	ObfuscatingClassAdapter(ClassVisitor cv) {
		super(cv);
	}

	@Override
	public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
		MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
		if (mv != null) {
			mv = new ObfuscatingAdapter(mv);
		}
		return mv;
	}

	static class ObfuscatingAdapter extends MethodAdapter implements Opcodes {

		ObfuscatingAdapter(MethodVisitor mv) {
			super(mv);
		}

		@Override
		public void visitInsn(int opcode) {
			if (RETURN >= opcode && opcode >= IRETURN) {
				Label l = new Label();
				Label k = new Label();
				mv.visitLabel(k);
				mv.visitInsn(ICONST_0);
				mv.visitJumpInsn(IFEQ, l);
				mv.visitJumpInsn(GOTO, k);
				mv.visitLabel(l);
				mv.visitInsn(opcode);
				mv.visitInsn(POP);
				mv.visitInsn(ATHROW);
			} else {
				mv.visitInsn(opcode);
			}
		}

	}

}
			
		

Jellemző és mókás, hogy egy adott java class transformációjának kiötlése, megvalósítása, a megfelelő minta kiválasztása és tesztelése kevesebb időt vett igénybe, és kevesebb kódsor lett, mint amit a jar felolvasása, háztartás és egyéb melléktevénységek igényeltek. A jar elkészítésénél a *.class fájlok módosításán túl még egy-két apró optimalizáció is történik, mint például directory entry-k kihagyása, tömörítés, és a manifest fájl kibővétése egy "itt jártam" tipusú attribútummal.

			

/*
 * Nyctalopia - minimalistic flow obfuscator
 * 
 * Copyright 2011 Tibor Bősze <tibor.boesze@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package hu.lithium.nyctalopia;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

public class JarProcessor {

	public final static String copyright = "Nyctalopia - minimalistic flow obfuscator\n"
		+ "Copyright 2011 Tibor Bősze <tibor.boesze@gmail.com>\n"
		+ "Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)\n";
	
	private static byte[] processClass(InputStream is) throws IOException {
		ClassWriter w = new ClassWriter(ClassWriter.COMPUTE_MAXS);
		new ClassReader(is).accept(new ObfuscatingClassAdapter(w), 0);
		return w.toByteArray();
	}

	private static JarOutputStream createJarOutStream(JarInputStream input, String path) throws IOException {
		Manifest m = input.getManifest() == null ? new Manifest() : input.getManifest();
		Attributes attrs = m.getMainAttributes();
		if (!attrs.containsKey(Attributes.Name.MANIFEST_VERSION.toString())) {
			attrs.putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
		}
		attrs.putValue("Created-By", "1.0 (Nyctalopia)");
		
		JarOutputStream output = new JarOutputStream(new FileOutputStream(path), m);
		output.setLevel(9);

		return output;
	}
	
	private static void transformJarContent(JarInputStream in, JarOutputStream out) throws IOException {
		byte[] buf = new byte[4096];
		
		JarEntry entry = null;
		while((entry = in.getNextJarEntry()) != null) {
			if (entry.isDirectory() || JarFile.MANIFEST_NAME.equalsIgnoreCase(entry.getName())) {
				System.out.println("skipping " + entry.getName());
				continue;
			}
			
			JarEntry newEntry = new JarEntry(entry);
			newEntry.setMethod(JarEntry.DEFLATED);
			newEntry.setCompressedSize(-1);
			
			if (entry.getName().endsWith(".class")) {
				System.out.println("weaving " + entry.getName());
				byte[] byteCode = processClass(in);
				out.putNextEntry(newEntry);
				out.write(byteCode);
				out.closeEntry();
			} else {//copy
				System.out.println("copying " + entry.getName());
				out.putNextEntry(newEntry);
				int c = 0;
				while ((c = in.read(buf)) != -1) {
					out.write(buf, 0, c);
				}
				out.closeEntry();
			}
		}
	}
	
	
	public static void main(String[] args) throws IOException {
		System.out.println(copyright);
		
		if (args.length != 2) {
			System.out.println("Usage: <input_jar> <output_jar>");
			return;
		}

		JarInputStream in = null;
		JarOutputStream out = null;

		try {
			in = new JarInputStream(new FileInputStream(args[0]));
			out = createJarOutStream(in, args[1]);
			
			transformJarContent(in, out);
			
		} finally {
			if (in != null) {
				in.close();
			}
			if (out != null) {
				out.close();
			}
		}
	}

}			
		
© 2003-2020 lithium.io7.org
Erre a weblapra a Creative Commons Nevezd meg! - Így add tovább! 3.0 Unported Licensz vonatkozik.