Android逆向

动态调试

Java层动调

在Manifest给 AndroidManifest.xml 设置:

android:debuggable="true"

使用apktool工具解包修改manifest,重打包回去之后进行签名;

先对文件进行对齐:

zipalign -p -f -v 4 input.apk output_unsigned.apk

加签名,其中 abc.keystore 是通过 androidstudio 生成的签名文件,也可以自行用工具生成,参考:APK反编译、重打包、签名之apktool实现_apktool反编译还是乱码-CSDN博客

apksigner sign --ks abc.keystore output_unsigned.apk

安装到手机上,以调试模式启动:

adb shell am start -D -n com.chick.androdbgme/.MainActivity

用jeb attach上去,运行修改后代码。

Native层调试

参考:

IDA以Debug模式启动APK调试SO的JNI_OnLoad函数的两种办法

Frida

Frida是一款基于Python + JavaScript 的hook框架,本质是一种动态插桩技术。可以用于Android、Windows、iOS等各大平台,其执行脚本基于Python或者Node.js写成,而注入代码用JavaScript写成。

原理

frida使用的是动态二进制插桩技术DBI)。

插桩技术是指将额外的代码注入程序中以收集运行时的信息,可分为两种:

  • 源代码插桩 (Source Code Instrumentation, SCI):顾名思义,在程序源代码的基础上增加(注入)额外的代码,从而达到预期目的或者功能;

  • 二进制插桩 (Binary Instrumentation):额外代码注入到二进制可执行文件中,通过修改汇编地址,改变程序运行内容,运行后再返回到原来程序运行出处,从而实现程序的额外功能。

    • 静态二进制插桩 (Static Binary Instrumentation, SBI):在程序执行前插入额外的代码和数据,生成一个永久改变的可执行文件。

    • 动态二进制插桩 (Dynamic Binary Instrumentation, DBI):在程序运行时实时地插入额外代码和数据,对可执行文件没有任何永久改变。

DBI能做什么?

  1. 访问进程的内存
  2. 在应用程序运行时覆盖一些功能
  3. 从导入的类中调用函数
  4. 在堆上查找对象实例并使用这些对象实例
  5. Hook,跟踪和拦截函数等等

安装

安装frida包

1
2
pip install frida -i https://pypi.mirrors.ustc.edu.cn/simple/ 
pip install frida-tools -i https://pypi.mirrors.ustc.edu.cn/simple/

配置代码提示

安装node.js后,新建文件夹,在目录下执行命令:

1
npm i @types/frida-gum

配置成功,可以在当前目录下编写js代码。

Server环境配置

Releases · frida/frida (github.com)

下载Frida的Server端->frida-server-15.1.4-android-arm64.xz,这里版本要与hook程序架构适配

然后push到手机的data/local/tmp目录下,和IDA的动态调试有点类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看设备实际 CPU ABI,用于下载对应的frida-server-x.x.x-android-xxx.xz
adb shell getprop ro.product.cpu.abi

adb devices # 查看端口
adb.exe connect 127.0.0.1:62026

adb push .\frida-server-16.0.11-android-x86_64
/data/local/tmp/frida

adb shell
su
cd /data/local/tmp/frida

# 然后修改权限
chmod 777 frida-server-16.0.11-android-x86_64

# 直接运行frida服务
./frida-server-16.0.11-android-x86_64

# 开启端口转发,转发android TCP端口到本地
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

如果能显示进程列表说明环境搭建完成:

1
2
# PC端输入
frida-ps -R

Frida Hook

执行Hook

启动时hook:

1
2
# 将 xxxxxx 换成你手机里安装好的任意apk包名
frida -U -f com.test.apk -l test.js

启动apk后hook:

1
2
frida -U -f com.test.apk
%load test.js
Java层Hook

Frida的Java层重要函数

使用 java平台—>Java.perform(function () {} 获取 Java类 —>Java.use(className)

当获取到Java类之后,直接通过 <wrapper>.<method>.implementations = function() {} 的方式来hook wrapper类的method方法,不管是实例方法还是静态方法都可以。

Hook普通方法

被Hook的代码:

1
2
3
4
5
6
7
8
package com.example.xxx;

public class Student {
static public int Add(int a,int b)
{
return a+b;
}
}

Hook代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function main()
{
//使用java平台
Java.perform(
function() {
//获取java类
var student=Java.use("com.example.xxx.Student");
//hook Add方法(重写Add方法)
student.Add.implementation=function(a,b)
{
//修改参数
a=123;
b=456;
//调用原来的函数
var res = this.Add(a,b);
//输出结果
console.log(a,b,res);
return res;
}
}
);
}
setImmediate(main)
Hook执行so函数
Hook有导出函数

通过导出函数名称找到函数地址即可进行hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// # com.xfast.hgr:bg

function main()
{
Java.perform(function () {

var JavaString = Java.use("java.lang.String");
var adk_addr = Module.findExportByName("libnativelib.so","Java_com_faster_nativelib_NativeLib_adk");
console.log("[*] 目标hook函数的内存地址是: " + adk_addr);
//var aes_128 = new NativeFunction(aes_addr , 'pointer', ['pointer', 'pointer']); // 返回值,参数

Interceptor.attach(adk_addr,{
//在hook函数之前执行的语句
onEnter: function(args)
{
console.log("[*] Success Hook So!");
},
//在hook函数之后执行的语句
onLeave:function(retval)
{
console.log("[*] 原始的So层函数返回值是:"+ Java.vm.getEnv().getStringUtfChars(retval,null).readCString());
// var change=1;
// retval.replace(change);
// console.log("[*] 篡改的So层函数返回值是:"+retval);
}
});
});
}
// frida print String 类型返回值
// //方式一
// var element = Java.cast(obj,Java.use("java.lang.String"));
// //方式二
// var element = Java.vm.getEnv().getStringUtfChars(obj,null).readCString();

setImmediate(main)

执行后成功hook函数返回值:

1
2
3
4
5
6
// 配置好环境
frida -UF com.xfast.hgr -l test.js

或者
frida -UF com.xfast.hgr
%load test.js
Hook无导出函数

.so 文件被去符号,无法找到对应函数名称,通过偏移地址调用函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function hookTest9()
{
//so名称
var so_name="libnative-lib.so";
//要Hook的函数偏移
var fun_off=0x7078;

//加载到内存后,函数地址=so地址+函数偏移
var so_add=Module.findBaseAddress(so_name);
var add_func=parseInt(so_add,16)+fun_off;
var ptr_fun=new NativePointer(add_func);

Interceptor.attach(ptr_fun,{
//在hook函数之前执行
onEnter:function(args)
{
console.log("hook enter");
},
//在hook函数之后执行
onLeave:function(retval)
{
console.log("hook leaver");
}

});
}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function main()
{
Java.perform(
function() {
var b=Java.use("com.example.mobile01.b");
b.c.implementation=function()
{
return 'D2eFgHiJkLmNoPqR';
}

var des=Java.use("com.example.mobile01.DESHelper");
des.encrypt.implementation=function(str,str2,str3)
{
console.log(str);
console.log(str2);
console.log(str3);
var res=this.encrypt(str,str2,str3);
console.log(res);
return res;
}
}
);
}
setImmediate(main)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function main()
{
Java.perform(function()
{
Java.choose("com.dionysus.ez_android.MainActivity",
{
onMatch(instance)
{
let G = instance.G.value;
let maze_str = Java.use("java.lang.String").$new(G);
console.warn(maze_str);
},
onComplete()
{
console.log("Search complete.");
}

})
})
}

setImmediate(main);

脱壳

apk壳检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import zipfile
'''
first,get namelist from apk
second,matching the features
thrid,julging for the shellType
so easy~~
by zsdlove
2018/8/24 Morning
'''
shellfeatures={
"libchaosvmp.so":"娜迦",
"libddog.so":"娜迦",
"libfdog.so":"娜迦",
"libedog.so":"娜迦企业版",
"libexec.so":"爱加密",
"libexecmain.so":"爱加密",
"ijiami.dat":"爱加密",
"ijiami.ajm":"爱加密企业版",
"libsecexe.so":"梆梆免费版",
"libsecmain.so":"梆梆免费版",
"libSecShell.so":"梆梆免费版",
"libDexHelper.so":"梆梆企业版",
"libDexHelper-x86.so":"梆梆企业版",
"libprotectClass.so":"360",
"libjiagu.so":"360",
"libjiagu_art.so":"360",
"libjiagu_x86.so":"360",
"libegis.so":"通付盾",
"libNSaferOnly.so":"通付盾",
"libnqshield.so":"网秦",
"libbaiduprotect.so":"百度",
"aliprotect.dat":"阿里聚安全",
"libsgmain.so":"阿里聚安全",
"libsgsecuritybody.so":"阿里聚安全",
"libmobisec.so":"阿里聚安全",
"libtup.so":"腾讯",
"libexec.so":"腾讯",
"libshell.so":"腾讯",
"mix.dex":"腾讯",
"lib/armeabi/mix.dex":"腾讯",
"lib/armeabi/mixz.dex":"腾讯",
"libtosprotection.armeabi.so":"腾讯御安全",
"libtosprotection.armeabi-v7a.so":"腾讯御安全",
"libtosprotection.x86.so":"腾讯御安全",
"libnesec.so":"网易易盾",
"libAPKProtect.so":"APKProtect",
"libkwscmm.so":"几维安全",
"libkwscr.so":"几维安全",
"libkwslinker.so":"几维安全",
"libx3g.so":"顶像科技",
"libapssec.so":"盛大",
"librsprotect.so":"瑞星"
}
def shellDetector(apkpath):
shellType=""
shellsign=""
flag=True
zipfiles=zipfile.ZipFile(apkpath)
nameList=zipfiles.namelist()
for fileName in nameList:
for shell in shellfeatures.keys():
if shell in fileName:
flag=True
shellType=shellfeatures[shell]
shellsign=shell
break
else:
flag=False
if flag==True:
print("经检测,该apk使用了"+shellType+"进行加固")
if __name__ == '__main__':
shellDetector("test.apk")

脱壳方法

  • dumpDex

    dumpDex-Android脱壳:https://github.com/WrBug/dumpDex

    dumpDex是一个github上开源的xposed插件,可以用来脱掉当前市场上大部分的壳。

    一、准备工作

        首先需要root的手机一部,我使用的是华为荣耀6(android5.1),安装好xposed框架

        dumpDex项目地址:https://github.com/WrBug/dumpDex

        可以直接下载release的apk,也可以自行编译打包成apk安装到手机,我个人比较喜欢第二种。

        安装好apk后,对于32位手机,需要将lib/armeabi-v7a/libnativeDump.so复制到/data/local/tmp/libnativeDump.so,权限设置为777。

    二、开始脱壳

       1515214-20191209202615320-855088235

        反编译apk后,根据特征发现是使用了梆梆加固

        安装插件,重启手机,打开加固的apk,脱壳的后的dex会在/data/data/对应包路径/dump文件夹下

       1515214-20191209204222043-373634842

        脱壳后的dex如图所示,其中有一部分是壳的dex,需要自己自行选择一下

     三、补充说明

        设置权限问题,GitHub上有详细说明

        编译过程有可能会有让你设置签名的一个问题,直接设置成你的debug签名就可

       如果加固后的包名在PackageInfo.java中没有对应的,自行加到PackageInfo.java中即可

        应用有可能有卡死状况或者手机重启状况,但应该能脱出壳来,建议多脱几遍

        1515214-20191209205153835-993146226

       源码部分如果是百度系的壳,直接返回并有没dump,因为还没遇到过百度的壳,暂不知道具体原因

  • FART

    1、安装镜像

    https://github.com/hanbinglengyue/FART上下载镜像,然后按照https://www.bodkin.ren/index.php/archives/513/刷机,中间没有遇到什么问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    adb reboot bootloader
    fastboot oem unlock

    fastboot flash system system.img
    fastboot flash userdata userdata.img
    fastboot flash boot boot.img
    fastboot flash cache cache.img
    fastboot flash ramdisk ramdisk.img
    fastboot reboot

    2、修改fart文件并上传

    将fart配置文件fart复制到/data/fart(注意文件权限问题,和换行的问题),其中,fart配置文件中为要脱壳的app包名

    1
    adb push ~/app_crack/fart/test/fart  /data

    20179028-4f502705af9aa74f

    3、查看日志确认

    1
    2
    # 在命令行输入下面的命令,然后打开app
    adb logcat -s ActivityThread

    20179028-32b9c75f97ed7a86

    4、查看脱出来的dex

    1
    adb pull /sdcard/fart/包名/6600696_dexfile.dex .

    20179028-0e0e17108be7cabe

  • Frida

    https://github.com/hluwa/FRIDA-DEXDump

    https://github.com/GuoQiang1993/Frida-Apk-Unpack

    使用:

    1. 启动 APP。
    2. 启动 frida-server。
    3. python main.py。

    或者可以将脚本封装成命令,就像这样:

    715510_KETE3VDRG42AS8P

混淆

BlackObfuscator

jeb 5.1+版本反编译,运行调试java代码。

ZUC

解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
from math import ceil

S0 = [
0x3E, 0x72, 0x5B, 0x47, 0xCA, 0xE0, 0x00, 0x33, 0x04, 0xD1, 0x54, 0x98, 0x09, 0xB9, 0x6D, 0xCB,
0x7B, 0x1B, 0xF9, 0x32, 0xAF, 0x9D, 0x6A, 0xA5, 0xB8, 0x2D, 0xFC, 0x1D, 0x08, 0x53, 0x03, 0x90,
0x4D, 0x4E, 0x84, 0x99, 0xE4, 0xCE, 0xD9, 0x91, 0xDD, 0xB6, 0x85, 0x48, 0x8B, 0x29, 0x6E, 0xAC,
0xCD, 0xC1, 0xF8, 0x1E, 0x73, 0x43, 0x69, 0xC6, 0xB5, 0xBD, 0xFD, 0x39, 0x63, 0x20, 0xD4, 0x38,
0x76, 0x7D, 0xB2, 0xA7, 0xCF, 0xED, 0x57, 0xC5, 0xF3, 0x2C, 0xBB, 0x14, 0x21, 0x06, 0x55, 0x9B,
0xE3, 0xEF, 0x5E, 0x31, 0x4F, 0x7F, 0x5A, 0xA4, 0x0D, 0x82, 0x51, 0x49, 0x5F, 0xBA, 0x58, 0x1C,
0x4A, 0x16, 0xD5, 0x17, 0xA8, 0x92, 0x24, 0x1F, 0x8C, 0xFF, 0xD8, 0xAE, 0x2E, 0x01, 0xD3, 0xAD,
0x3B, 0x4B, 0xDA, 0x46, 0xEB, 0xC9, 0xDE, 0x9A, 0x8F, 0x87, 0xD7, 0x3A, 0x80, 0x6F, 0x2F, 0xC8,
0xB1, 0xB4, 0x37, 0xF7, 0x0A, 0x22, 0x13, 0x28, 0x7C, 0xCC, 0x3C, 0x89, 0xC7, 0xC3, 0x96, 0x56,
0x07, 0xBF, 0x7E, 0xF0, 0x0B, 0x2B, 0x97, 0x52, 0x35, 0x41, 0x79, 0x61, 0xA6, 0x4C, 0x10, 0xFE,
0xBC, 0x26, 0x95, 0x88, 0x8A, 0xB0, 0xA3, 0xFB, 0xC0, 0x18, 0x94, 0xF2, 0xE1, 0xE5, 0xE9, 0x5D,
0xD0, 0xDC, 0x11, 0x66, 0x64, 0x5C, 0xEC, 0x59, 0x42, 0x75, 0x12, 0xF5, 0x74, 0x9C, 0xAA, 0x23,
0x0E, 0x86, 0xAB, 0xBE, 0x2A, 0x02, 0xE7, 0x67, 0xE6, 0x44, 0xA2, 0x6C, 0xC2, 0x93, 0x9F, 0xF1,
0xF6, 0xFA, 0x36, 0xD2, 0x50, 0x68, 0x9E, 0x62, 0x71, 0x15, 0x3D, 0xD6, 0x40, 0xC4, 0xE2, 0x0F,
0x8E, 0x83, 0x77, 0x6B, 0x25, 0x05, 0x3F, 0x0C, 0x30, 0xEA, 0x70, 0xB7, 0xA1, 0xE8, 0xA9, 0x65,
0x8D, 0x27, 0x1A, 0xDB, 0x81, 0xB3, 0xA0, 0xF4, 0x45, 0x7A, 0x19, 0xDF, 0xEE, 0x78, 0x34, 0x60
]

S1 = [
0x55, 0xC2, 0x63, 0x71, 0x3B, 0xC8, 0x47, 0x86, 0x9F, 0x3C, 0xDA, 0x5B, 0x29, 0xAA, 0xFD, 0x77,
0x8C, 0xC5, 0x94, 0x0C, 0xA6, 0x1A, 0x13, 0x00, 0xE3, 0xA8, 0x16, 0x72, 0x40, 0xF9, 0xF8, 0x42,
0x44, 0x26, 0x68, 0x96, 0x81, 0xD9, 0x45, 0x3E, 0x10, 0x76, 0xC6, 0xA7, 0x8B, 0x39, 0x43, 0xE1,
0x3A, 0xB5, 0x56, 0x2A, 0xC0, 0x6D, 0xB3, 0x05, 0x22, 0x66, 0xBF, 0xDC, 0x0B, 0xFA, 0x62, 0x48,
0xDD, 0x20, 0x11, 0x06, 0x36, 0xC9, 0xC1, 0xCF, 0xF6, 0x27, 0x52, 0xBB, 0x69, 0xF5, 0xD4, 0x87,
0x7F, 0x84, 0x4C, 0xD2, 0x9C, 0x57, 0xA4, 0xBC, 0x4F, 0x9A, 0xDF, 0xFE, 0xD6, 0x8D, 0x7A, 0xEB,
0x2B, 0x53, 0xD8, 0x5C, 0xA1, 0x14, 0x17, 0xFB, 0x23, 0xD5, 0x7D, 0x30, 0x67, 0x73, 0x08, 0x09,
0xEE, 0xB7, 0x70, 0x3F, 0x61, 0xB2, 0x19, 0x8E, 0x4E, 0xE5, 0x4B, 0x93, 0x8F, 0x5D, 0xDB, 0xA9,
0xAD, 0xF1, 0xAE, 0x2E, 0xCB, 0x0D, 0xFC, 0xF4, 0x2D, 0x46, 0x6E, 0x1D, 0x97, 0xE8, 0xD1, 0xE9,
0x4D, 0x37, 0xA5, 0x75, 0x5E, 0x83, 0x9E, 0xAB, 0x82, 0x9D, 0xB9, 0x1C, 0xE0, 0xCD, 0x49, 0x89,
0x01, 0xB6, 0xBD, 0x58, 0x24, 0xA2, 0x5F, 0x38, 0x78, 0x99, 0x15, 0x90, 0x50, 0xB8, 0x95, 0xE4,
0xD0, 0x91, 0xC7, 0xCE, 0xED, 0x0F, 0xB4, 0x6F, 0xA0, 0xCC, 0xF0, 0x02, 0x4A, 0x79, 0xC3, 0xDE,
0xA3, 0xEF, 0xEA, 0x51, 0xE6, 0x6B, 0x18, 0xEC, 0x1B, 0x2C, 0x80, 0xF7, 0x74, 0xE7, 0xFF, 0x21,
0x5A, 0x6A, 0x54, 0x1E, 0x41, 0x31, 0x92, 0x35, 0xC4, 0x33, 0x07, 0x0A, 0xBA, 0x7E, 0x0E, 0x34,
0x88, 0xB1, 0x98, 0x7C, 0xF3, 0x3D, 0x60, 0x6C, 0x7B, 0xCA, 0xD3, 0x1F, 0x32, 0x65, 0x04, 0x28,
0x64, 0xBE, 0x85, 0x9B, 0x2F, 0x59, 0x8A, 0xD7, 0xB0, 0x25, 0xAC, 0xAF, 0x12, 0x03, 0xE2, 0xF2
]

D = [
0x44D7, 0x26BC, 0x626B, 0x135E, 0x5789, 0x35E2, 0x7135, 0x09AF,
0x4D78, 0x2F13, 0x6BC4, 0x1AF1, 0x5E26, 0x3C4D, 0x789A, 0x47AC
]


def addition_uint31(a, b):
c = a + b
return (c & 0x7FFFFFFF) + (c >> 31)


def rotl_uint31(a, shift):
return ((a << shift) | (a >> (31 - shift))) & 0x7FFFFFFF


def rotl_uint32(a, shift):
return ((a << shift) | (a >> (32 - shift))) & 0xFFFFFFFF


def l1(x):
return (x ^ rotl_uint32(x, 2) ^ rotl_uint32(x, 10) ^ rotl_uint32(x, 18) ^ rotl_uint32(x, 24))


def l2(x):
return (x ^ rotl_uint32(x, 8) ^ rotl_uint32(x, 14) ^ rotl_uint32(x, 22) ^ rotl_uint32(x, 30))


def make_uint32(a, b, c, d):
return ((a << 24) & 0xffffffff) | ((b << 16) & 0xffffffff) | ((c << 8) & 0xffffffff) | d


def make_uint31(a, b, c):
return ((a << 23) & 0x7fffffff) | ((b << 8) & 0x7fffffff) | c


class ZUC(object):
def __init__(self, key, iv):
self.r = [0, 0]
self.lfsr = [0 for _ in range(16)]
self.x = [0, 0, 0, 0]
self.zuc_init(key, iv)

def bit_reorganization(self):
self.x[0] = ((self.lfsr[15] & 0x7FFF8000) << 1) | (self.lfsr[14] & 0xFFFF)
self.x[1] = ((self.lfsr[11] & 0xFFFF) << 16) | (self.lfsr[9] >> 15)
self.x[2] = ((self.lfsr[7] & 0xFFFF) << 16) | (self.lfsr[5] >> 15)
self.x[3] = ((self.lfsr[2] & 0xFFFF) << 16) | (self.lfsr[0] >> 15)

def lfsr_next(self):
f = self.lfsr[0]
v = rotl_uint31(self.lfsr[0], 8)
f = addition_uint31(f, v)
v = rotl_uint31(self.lfsr[4], 20)
f = addition_uint31(f, v)
v = rotl_uint31(self.lfsr[10], 21)
f = addition_uint31(f, v)
v = rotl_uint31(self.lfsr[13], 17)
f = addition_uint31(f, v)
v = rotl_uint31(self.lfsr[15], 15)
f = addition_uint31(f, v)
return f

def lfsr_append(self, f):
self.lfsr.append(f)
if len(self.lfsr) > 16:
self.lfsr.pop(0)

def lfsr_init(self, u):
self.lfsr_append(addition_uint31(self.lfsr_next(), u))

def lfsr_shift(self):
self.lfsr_append(self.lfsr_next())

def f(self):
W = ((self.x[0] ^ self.r[0]) + self.r[1]) & 0xffffffff
W1 = (self.r[0] + self.x[1]) & 0xffffffff
W2 = self.r[1] ^ self.x[2]
u = l1(((W1 & 0x0000ffff) << 16) | (W2 >> 16))
v = l2(((W2 & 0x0000ffff) << 16) | (W1 >> 16))
self.r = [make_uint32(S0[u >> 24], S1[(u >> 16) & 0xFF],
S0[(u >> 8) & 0xFF], S1[u & 0xFF]),
make_uint32(S0[v >> 24], S1[(v >> 16) & 0xFF],
S0[(v >> 8) & 0xFF], S1[v & 0xFF])]
return W

def zuc_init(self, key, iv):
# Expand key.
self.lfsr = [make_uint31(key[i], D[i], iv[i]) for i in range(16)]
self.r = [0, 0]
for i in range(32):
self.bit_reorganization()
w = self.f()
self.lfsr_init(w >> 1)

def zuc_generate_keystream(self, length):
keystream_buffer = []
self.bit_reorganization()
self.f() # Discard the output of F.

def itor():
self.lfsr_shift()
self.bit_reorganization()
return self.f() ^ self.x[-1]

keystream_buffer = [itor() for _ in range(length)]
self.lfsr_shift()
return keystream_buffer

def zuc_encrypt(self, input):
length = len(input)
key_stream = self.zuc_generate_keystream(length)
return [inp ^ key_stream[i] for i, inp in enumerate(input)]


if '__main__' == __name__:
key = []
iv = []
# Decrypt
zuc2 = ZUC(key, iv)
cipher = []
out2 = zuc2.zuc_encrypt(cipher)
print("Decrypted: ", ["%08x" % e for e in out2])
print(''.join(map(chr,out2)))