首页
社区
课程
招聘
[原创]Apk伪加密实现与破解JAVA源码
发表于: 2013-7-5 18:52 43565

[原创]Apk伪加密实现与破解JAVA源码

2013-7-5 18:52
43565

伪加密的apk有那么难么?了解原理就没啥可怕的。放出java代码,python的早就有了。

package com.rover12421.apkutil;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.zip.ZipError;

import static com.rover12421.apkutil.ZipConstants.*;

public class ApkUtilTool {
	
    private FileChannel ch; // channel to the zipfile
    private FileChannel fc;

    /**
     * 修复zip伪加密状态的Entry
     * @param inZip
     * @param storeZip
     * @throws IOException
     */
    public void FixEncryptedEntry(File inZip, File fixZip) throws IOException {
    	changEntry(inZip, fixZip, true);
	}
    
    /**
     * 修复zip伪加密状态的Entry
     * @param inZip
     * @param storeZip
     * @throws IOException
     */
    public void FixEncryptedEntry(String inZip, String fixZip) throws IOException {
    	FixEncryptedEntry(new File(inZip), new File(fixZip));
    }
    
    /**
     * 修改zip的Entry为伪加密状态
     * @param inZip
     * @param storeZip
     * @throws IOException
     */
    public void ChangToEncryptedEntry(File inZip, File storeZip) throws IOException {
    	changEntry(inZip, storeZip, false);
    }
    
    /**
     * 修改zip的Entry为伪加密状态
     * @param inZip
     * @param storeZip
     * @throws IOException
     */
    public void ChangToEncryptedEntry(String inZip, String storeZip) throws IOException {
    	ChangToEncryptedEntry(new File(inZip), new File(storeZip));
    }
    
    /**
     * 更改zip的Entry为伪加密状态
     * @param inZip
     * @param storeZip
     * @param fix	ture:修复伪加密 false:更改到伪加密
     * @throws IOException
     */
    private void changEntry(File inZip, File storeZip, boolean fix) throws IOException {
    	FileInputStream fis = new FileInputStream(inZip);
		FileOutputStream fos = new FileOutputStream(storeZip);
		
		byte[] buf = new byte[10240];
    	int len;
    	while ((len = fis.read(buf)) != -1) {
			fos.write(buf, 0, len);
		}
    	
    	ch = fis.getChannel();
    	fc = fos.getChannel();
    	
    	changEntry(fix);
    	
    	ch.close();
    	fc.close();
		
		fis.close();
		fos.close();
    }
    
	// Reads zip file central directory. Returns the file position of first
    // CEN header, otherwise returns -1 if an error occured. If zip->msg != NULL
    // then the error was a zip format error and zip->msg has the error text.
    // Always pass in -1 for knownTotal; it's used for a recursive call.
    private void changEntry(boolean fix) throws IOException {
    	END end = findEND();
    	
        if (end.cenlen > end.endpos)
            zerror("invalid END header (bad central directory size)");
        long cenpos = end.endpos - end.cenlen;     // position of CEN table

        // Get position of first local file (LOC) header, taking into
        // account that there may be a stub prefixed to the zip file.
        long locpos = cenpos - end.cenoff;
        if (locpos < 0)
            zerror("invalid END header (bad central directory offset)");

        // read in the CEN and END
        byte[] cen = new byte[(int)(end.cenlen + ENDHDR)];
        if (readFullyAt(cen, 0, cen.length, cenpos) != end.cenlen + ENDHDR) {
            zerror("read CEN tables failed");
        }

        int pos = 0;
        int limit = cen.length - ENDHDR;
        while (pos < limit) {
            if (CENSIG(cen, pos) != CENSIG)
                zerror("invalid CEN header (bad signature)");
            int method = CENHOW(cen, pos);
            int nlen   = CENNAM(cen, pos);
            int elen   = CENEXT(cen, pos);
            int clen   = CENCOM(cen, pos);
            
            if (fix) {
            	if ((CEN***(cen, pos) & 1) != 0) {
                	byte[] name = Arrays.copyOfRange(cen, pos + CENHDR, pos + CENHDR + nlen);
                	System.out.println("Found the encrypted entry : " + new String(name) + ", fix...");
                	//b[n] & 0xff) | ((b[n + 1] & 0xff) << 8
                	cen[pos+8] &= 0xFE;
//                	cen[pos+8] ^= CEN***(cen, pos) % 2;
//                	cen[pos+8] ^= cen[pos+8] % 2;
//                    zerror("invalid CEN header (encrypted entry)");
                }
			} else {
				if ((CEN***(cen, pos) & 1) == 0) {
                	byte[] name = Arrays.copyOfRange(cen, pos + CENHDR, pos + CENHDR + nlen);
                	System.out.println("Chang the entry : " + new String(name) + ", Encrypted...");
                	//b[n] & 0xff) | ((b[n + 1] & 0xff) << 8
                	cen[pos+8] |= 0x1;
//                    zerror("invalid CEN header (encrypted entry)");
                }
			}
            
            
            if (method != METHOD_STORED && method != METHOD_DEFLATED)
                zerror("invalid CEN header (unsupported compression method: " + method + ")");
            if (pos + CENHDR + nlen > limit)
                zerror("invalid CEN header (bad header size)");
            
            // skip ext and comment
            pos += (CENHDR + nlen + elen + clen);
        }
        
        writeFullyAt(cen, 0, cen.length, cenpos);
        
        if (pos + ENDHDR != cen.length) {
            zerror("invalid CEN header (bad header size)");
        }
    }
    
    // Reads len bytes of data from the specified offset into buf.
    // Returns the total number of bytes read.
    // Each/every byte read from here (except the cen, which is mapped).
    final long readFullyAt(byte[] buf, int off, long len, long pos)
        throws IOException
    {
        ByteBuffer bb = ByteBuffer.wrap(buf);
        bb.position(off);
        bb.limit((int)(off + len));
        return readFullyAt(bb, pos);
    }

    private final long readFullyAt(ByteBuffer bb, long pos)
        throws IOException
    {
        synchronized(ch) {
            return ch.position(pos).read(bb);
        }
    }
    
    final long writeFullyAt(byte[] buf, int off, long len, long pos)
            throws IOException
        {
            ByteBuffer bb = ByteBuffer.wrap(buf);
            bb.position(off);
            bb.limit((int)(off + len));
            return writeFullyAt(bb, pos);
        }
    
    private final long writeFullyAt(ByteBuffer bb, long pos)
            throws IOException
    {
        synchronized(fc) {
            return fc.position(pos).write(bb);
        }
    }
    
    // Searches for end of central directory (END) header. The contents of
    // the END header will be read and placed in endbuf. Returns the file
    // position of the END header, otherwise returns -1 if the END header
    // was not found or an error occurred.
    private END findEND() throws IOException
    {
        byte[] buf = new byte[READBLOCKSZ];
        long ziplen = ch.size();
        long minHDR = (ziplen - END_MAXLEN) > 0 ? ziplen - END_MAXLEN : 0;
        long minPos = minHDR - (buf.length - ENDHDR);

        for (long pos = ziplen - buf.length; pos >= minPos; pos -= (buf.length - ENDHDR))
        {
            int off = 0;
            if (pos < 0) {
                // Pretend there are some NUL bytes before start of file
                off = (int)-pos;
                Arrays.fill(buf, 0, off, (byte)0);
            }
            int len = buf.length - off;
            if (readFullyAt(buf, off, len, pos + off) != len)
                zerror("zip END header not found");

            // Now scan the block backwards for END header signature
            for (int i = buf.length - ENDHDR; i >= 0; i--) {
                if (buf[i+0] == (byte)'P'    &&
                    buf[i+1] == (byte)'K'    &&
                    buf[i+2] == (byte)'\005' &&
                    buf[i+3] == (byte)'\006' &&
                    (pos + i + ENDHDR + ENDCOM(buf, i) == ziplen)) {
                    // Found END header
                    buf = Arrays.copyOfRange(buf, i, i + ENDHDR);
                    END end = new END();
                    end.endsub = ENDSUB(buf);
                    end.centot = ENDTOT(buf);
                    end.cenlen = ENDSIZ(buf);
                    end.cenoff = ENDOFF(buf);
                    end.comlen = ENDCOM(buf);
                    end.endpos = pos + i;
                    if (end.cenlen == ZIP64_MINVAL ||
                        end.cenoff == ZIP64_MINVAL ||
                        end.centot == ZIP64_MINVAL32)
                    {
                        // need to find the zip64 end;
                        byte[] loc64 = new byte[ZIP64_LOCHDR];
                        if (readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR)
                            != loc64.length) {
                            return end;
                        }
                        long end64pos = ZIP64_LOCOFF(loc64);
                        byte[] end64buf = new byte[ZIP64_ENDHDR];
                        if (readFullyAt(end64buf, 0, end64buf.length, end64pos)
                            != end64buf.length) {
                            return end;
                        }
                        // end64 found, re-calcualte everything.
                        end.cenlen = ZIP64_ENDSIZ(end64buf);
                        end.cenoff = ZIP64_ENDOFF(end64buf);
                        end.centot = (int)ZIP64_ENDTOT(end64buf); // assume total < 2g
                        end.endpos = end64pos;
                    }
                    return end;
                }
            }
        }
        zerror("zip END header not found");
        return null; //make compiler happy
    }
    
    static void zerror(String msg) {
        throw new ZipError(msg);
    }
    
 // End of central directory record
    static class END {
        int  disknum;
        int  sdisknum;
        int  endsub;     // endsub
        int  centot;     // 4 bytes
        long cenlen;     // 4 bytes
        long cenoff;     // 4 bytes
        int  comlen;     // comment length
        byte[] comment;

        /* members of Zip64 end of central directory locator */
        int diskNum;
        long endpos;
        int disktot;
        
        @Override
        public String toString() {
        	return "disknum : " + disknum + "\n" +
        			"sdisknum : " + sdisknum + "\n" +
        			"endsub : " + endsub + "\n" +
        			"centot : " + centot + "\n" +
        			"cenlen : " + cenlen + "\n" +
        			"cenoff : " + cenoff + "\n" +
        			"comlen : " + comlen + "\n" +
        			"diskNum : " + diskNum + "\n" +
        			"endpos : " + endpos + "\n" +
        			"disktot : " + disktot;
        }
    }
}


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 5
支持
分享
最新回复 (14)
雪    币: 296
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
good job.谢谢分享!
2013-7-5 20:06
0
雪    币: 33
活跃值: (145)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
3
gooooooooooooooooood  job!
2013-7-5 21:25
0
雪    币: 80
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
大神啊啊
2013-7-6 13:14
0
雪    币: 738
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
回复mark一下,留着备用
2013-7-11 22:45
0
雪    币: 257
活跃值: (105)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
6
mark一下
2013-7-17 10:48
0
雪    币: 189
活跃值: (192)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
7
apktool wip2.0已经支持伪加密的apk解压.他用了第三方库org.apache.commons来处理
这个库有一个方法:
useEncryption(false);
可以设置是否检测加密信息的
2013-7-21 13:30
0
雪    币: 23
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
楼主你好,在Android4.3版本及之后的版本安装伪加密的apk失败。跟了了下发现java.util.zip.ZipEntry类中的构造方法已经被修改了,以前的这一句“it.seek(10);”修改为:
          it.seek(8);
        int gpbf = it.readShort() & 0xffff;

        if ((gpbf & ZipFile.GPBF_UNSUPPORTED_MASK) != 0) {
            throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf);
        }
有没有办法在Android4.3及之后的版本安装伪加密apk不报错呢。
2014-3-27 16:44
0
雪    币: 189
活跃值: (192)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
没有遇到这样的样本,你可以发样本给我看看.
2014-3-28 10:58
0
雪    币: 23
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
嗯,谢谢楼主!附件TestEncrypt.zip 中TestEncrypt.apk是未伪加密的,TestEncrypt_ent.apk是伪加密的apk,在Android4.3手机中安装抛的异常是“java.util.zip.ZipException: Invalid General Purpose Bit Flag: 2057”,我自己查看了下,在Android源码中java.util.zip包中的ZipEntry.java类导致的。附件中我附了ZipEntry.java类,(17)是Android4.2.2的源码,位置在362行;(18)是Android4.3的已经被修改过了,在360行。
上传的附件:
2014-3-30 18:43
0
雪    币: 9
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
留着备用,谢谢!!!
2014-4-1 16:27
0
雪    币: 189
活跃值: (192)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
12
看来是4.3校验了zip的伪加密,那这样4.3以后的系统就不能利用这个bug了
2014-4-1 16:42
0
雪    币: 23
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
谢谢楼主啦,看来得在学习,新的Android安全措施了。
2014-4-4 08:49
0
雪    币: 200
活跃值: (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
多谢啦。最近一个项目里读apk包名和版本,遇到几个这种伪加密的APK。先回复后收货,
2014-4-16 15:21
0
雪    币: 209
活跃值: (45)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
15
看不懂代码啊
能否用文字描述下apk的伪加密原理
2015-1-16 17:03
0
游客
登录 | 注册 方可回帖
返回
//