-
-
[原创]使用ollvm自定义简单的字符串加密
-
发表于: 2020-12-22 17:20 8103
-
题目出自3W班9月的题目用ollvm9.0实现字符串简单加密
题目主要是为了能熟练ollvm中如何进行一个简单的加密,以及c++部分怎么生成对应的IR指令来达到像c++函数效果。所以主要我们的思路可以切换成。
1、首先确定加密的核心逻辑并用C++实现
2、根据C++的算法。生成一份IR指令来为我们提供参考
3、ollvm里面如何用C++生成相应的IR指令
4、根据参考的IR指令来生成我们需要的加密和解密函数
做完这个题目,基本就可以对ollvm的工作原理有一定的了解,并且改造属于自己的加密或者混淆了。
先贴上测试好的结果: https://github.com/dqzg12300/kOLLVM.git
想要写一个字符串加密的pass,第一步就是先实现一遍c++的算法流程,然后再看一看生成的IR文件,然后再写对应的加密pass,下面看一个自己实现的简单c++字符串加密。
这个简单加密的意思,就是根据复杂度参数。来进行一定次数的迭代,将当前字符每次都异或一下,最后一次是先去反,再异或,解密就是反之。测试结果能正常加密和解密后,我们就先输出一份ir文件。看看在ir中间语言中是如何进行加密和解密的。
clang -emit-llvm -S main.cpp -o main.ll
生成好对应的ir文件后,我们开始写这个加密pass,然后再写的过程中,根据逻辑需要,去ir中找对应的指令处理方式
在ir文件中的层级划分:Module(模块)的下一层是若干Function(函数),然后在Function的下一层是若干BasicBlock(基本快),再BasicBlock的下一层是若干Instruction(指令块)
现在准备就绪,下面开始先准备一个加密的pass,基本代码如下
这里准备好了pass的基本代码后,最后就剩下最重要的核心逻辑,如何把c++的加密方式。在pass中实现,我们的功能是实现字符串加密,那么第一步应该是取得这个函数中的全部字符串,那么我们先看看ir中字符串的特征
可以看到,这个str是一个操作数,想要获取全部字符串,就得先遍历所有指令块中的操作数。然后再根据字符串的特征来进行过滤。下面先看如何遍历所有指令块。
上面遍历了函数中的所有基本快,然后遍历所有指令块,然后遍历所有操作数,然后获取操作数的值,判断该操作数是否是一个字符串,并且打印这个指令块,操作数,以及取到的操作数的值,下面看看打印的结果
那么看到了,我们想获取的字符串是在stripOp中。那么接下来就把所有字符串全部获取出来并转换成string
之前看到的字符串的ir代码看到所有字符串都是全局的,所以要先转换成全局的对象,然后再转换成数值。然后看这里的打印结果
获取到所有的字符串了之后。接下来。我们要先把这个字符串加密,然后再用插入指令块来进行解密。下面继续完善,先把之前搞好的加密算法迁移进来。
这里大致流程和之前一样。只是key我们装起来了。然后每个字节处理都随机一次key。接下来的处理就是插入指令块来对这个加密数据encres进行解密还原处理。
我们想要处理这个加密的数据,首先要先创建一个内存指令,来存放这个加密后的数据。然后再对加密后的数据遍历。进行还原。所以,我们的下一步先创建一个BitCastInst。并且我们需要用一个int8的array来给这个内存指令进行赋值。下面的代码是先创建array指令,然后用array指令创建一个内存指令
上面的就是先创建一个int8的array类型,然后用这个类型创建一个array,然后再用这个array创建内存指令,这些指令都插入在遍历到字符串指令的当前行的前方。这个bitcast将用来存放加密后的字符串数据
接下来就是加密的逻辑处理。和我们之前c++的流程一样,只不过这里需要换成插入指令块的形式来进行加密数据的还原,我直接贴上解密的代码部分,然后里面有详细的注释。
上面就是把c++的解密流程用插入指令块的方式实现的方式。流程比较繁琐,但是大概意思是差不多的。
最后这里完成后,我们就可以删除指令块中的字符串明文部分。然后只保留密文
到这里整个流程就完成了。这里还有一个点需要注意的是,由于字符串的特性,当使用了多个相同的字符串,实际在汇编层的代码中,会优化为一个字符串,所以在字符串加密的时候,我们要留意解密字符串的作用域。下面举一个例子
这个例子中使用了两个hello。如果我们在使用这个字符串时,调用的解密。那么下面else中的代码则会无法访问到bitcat。因为不在同一个作用域,所以为了防止出现这种情况,我在解密时再做一个特殊的处理,我们先获取第一个指令块的位置,然后所有的字符串解密指令块,都插入在最开始的位置,这样就不会出现作用域的问题了。最后贴上完整代码
#include <stdio.h>
#include <cstring>
#include <string>
int
main(
int
argc, char
*
*
argv) {
/
/
加密
std::string str1
=
"hello world!!!"
;
/
/
这里是随机的key,先写固定,真实实现的时候再每个字节使用一个随机key
int
randkey
=
11
;
/
/
加密复杂度
int
kstr_size
=
10
;
int
enclen
=
randkey
+
kstr_size;
char encres[str1.size()];
int
idx
=
0
;
memset(encres,
0
,enclen);
/
/
这里大概就是遍历字符串,每个字符根据加密复杂度进行一定数量迭代异或,最后一次的迭代使用取反再异或
for
(
int
i
=
0
;i<str1.size();i
+
+
){
printf(
"cur: %x \r\n"
,str1[i]);
for
(
int
y
=
randkey;y<enclen;y
+
+
){
if
(y
=
=
randkey){
encres[i]
=
str1[i]^y;
}
else
if
(y
=
=
enclen
-
1
){
encres[i]
=
encres[i]^(~y);
}
else
{
encres[i]
=
encres[i]^y;
}
printf(
"%x "
,encres[i]);
idx
+
+
;
}
printf(
"\r\n"
);
}
printf(
"encdata: %s\r\n"
,encres);
/
/
下面是解密函数
char decres[str1.size()];
for
(
int
i
=
0
;i<str1.size();i
+
+
){
printf(
"cur enc: %x \r\n"
,encres[i]);
for
(
int
y
=
enclen
-
1
;y>
=
randkey;y
-
-
){
if
(y
=
=
enclen
-
1
){
decres[i]
=
encres[i]^(~y);
}
else
{
decres[i]
=
decres[i]^y;
}
printf(
"%x "
,decres[i]);
}
printf(
"\r\n"
);
}
printf(
"res: %s\r\n"
,decres);
return
0
;
}
#include <stdio.h>
#include <cstring>
#include <string>
int
main(
int
argc, char
*
*
argv) {
/
/
加密
std::string str1
=
"hello world!!!"
;
/
/
这里是随机的key,先写固定,真实实现的时候再每个字节使用一个随机key
int
randkey
=
11
;
/
/
加密复杂度
int
kstr_size
=
10
;
int
enclen
=
randkey
+
kstr_size;
char encres[str1.size()];
int
idx
=
0
;
memset(encres,
0
,enclen);
/
/
这里大概就是遍历字符串,每个字符根据加密复杂度进行一定数量迭代异或,最后一次的迭代使用取反再异或
for
(
int
i
=
0
;i<str1.size();i
+
+
){
printf(
"cur: %x \r\n"
,str1[i]);
for
(
int
y
=
randkey;y<enclen;y
+
+
){
if
(y
=
=
randkey){
encres[i]
=
str1[i]^y;
}
else
if
(y
=
=
enclen
-
1
){
encres[i]
=
encres[i]^(~y);
}
else
{
encres[i]
=
encres[i]^y;
}
printf(
"%x "
,encres[i]);
idx
+
+
;
}
printf(
"\r\n"
);
}
printf(
"encdata: %s\r\n"
,encres);
/
/
下面是解密函数
char decres[str1.size()];
for
(
int
i
=
0
;i<str1.size();i
+
+
){
printf(
"cur enc: %x \r\n"
,encres[i]);
for
(
int
y
=
enclen
-
1
;y>
=
randkey;y
-
-
){
if
(y
=
=
enclen
-
1
){
decres[i]
=
encres[i]^(~y);
}
else
{
decres[i]
=
decres[i]^y;
}
printf(
"%x "
,decres[i]);
}
printf(
"\r\n"
);
}
printf(
"res: %s\r\n"
,decres);
return
0
;
}
#include <kllvm/Transforms/Obfuscation/Utils.h>
#include "kllvm/Transforms/Obfuscation/KStringEncode.h"
#include <string>
using namespace llvm;
namespace {
/
/
加密复杂度
const
int
defaultKStringSize
=
0x10
;
static cl::opt<
int
>
KStringSize(
"kstr_size"
, cl::desc(
"Choose the probability [%] each basic blocks will be obfuscated by the -kstr pass"
), cl::value_desc(
"king string encode Encryption length"
), cl::init(defaultKStringSize), cl::Optional);
struct KStringEncode: public FunctionPass{
static char
ID
;
/
/
Pass identification
bool
flag;
KStringEncode() : FunctionPass(
ID
) {}
KStringEncode(
bool
flag) : FunctionPass(
ID
) {this
-
>flag
=
flag; KStringEncode();}
virtual
bool
runOnFunction(Function &F){
/
/
先检查加密复杂度是否在合法范围
if
( !((KStringSize >
0
) && (KStringSize <
=
100
)) ) {
errs()<<
"KStringEncode application basic blocks percentage -kstr_size=x must be 0 < x <= 100"
;
return
false;
}
if
(toObfuscate(flag,&F,
"kstr"
)) {
kstr(F);
}
return
false;
}
void kstr(Function& func){
/
/
todo 这里再写具体的
pass
逻辑
}
};
}
char KStringEncode::
ID
=
0
;
static RegisterPass<KStringEncode> X(
"kstr"
,
"inserting bogus control flow"
);
Pass
*
llvm::createKStringEncode() {
return
new KStringEncode();
}
Pass
*
llvm::createKStringEncode(
bool
flag) {
return
new KStringEncode(flag);
}
#include <kllvm/Transforms/Obfuscation/Utils.h>
#include "kllvm/Transforms/Obfuscation/KStringEncode.h"
#include <string>
using namespace llvm;
namespace {
/
/
加密复杂度
const
int
defaultKStringSize
=
0x10
;
static cl::opt<
int
>
KStringSize(
"kstr_size"
, cl::desc(
"Choose the probability [%] each basic blocks will be obfuscated by the -kstr pass"
), cl::value_desc(
"king string encode Encryption length"
), cl::init(defaultKStringSize), cl::Optional);
struct KStringEncode: public FunctionPass{
static char
ID
;
/
/
Pass identification
bool
flag;
KStringEncode() : FunctionPass(
ID
) {}
KStringEncode(
bool
flag) : FunctionPass(
ID
) {this
-
>flag
=
flag; KStringEncode();}
virtual
bool
runOnFunction(Function &F){
/
/
先检查加密复杂度是否在合法范围
if
( !((KStringSize >
0
) && (KStringSize <
=
100
)) ) {
errs()<<
"KStringEncode application basic blocks percentage -kstr_size=x must be 0 < x <= 100"
;
return
false;
}
if
(toObfuscate(flag,&F,
"kstr"
)) {
kstr(F);
}
return
false;
}
void kstr(Function& func){
/
/
todo 这里再写具体的
pass
逻辑
}
};
}
char KStringEncode::
ID
=
0
;
static RegisterPass<KStringEncode> X(
"kstr"
,
"inserting bogus control flow"
);
Pass
*
llvm::createKStringEncode() {
return
new KStringEncode();
}
Pass
*
llvm::createKStringEncode(
bool
flag) {
return
new KStringEncode(flag);
}
@.
str
=
private unnamed_addr constant [
15
x i8] c
"hello world!!!\00"
, align
1
@.
str
=
private unnamed_addr constant [
15
x i8] c
"hello world!!!\00"
, align
1
void kstr(Function& func){
for
(BasicBlock& bb:func){
for
(Instruction& ins :bb){
for
(Value
*
val:ins.operands()){
Value
*
stripOp
=
val
-
>stripPointerCasts();
if
(stripOp
-
>getName().contains(
".str"
)){
errs()<<ins<<
"\n"
;
errs()<<
*
val<<
"\n"
;
errs()<<
*
stripOp<<
"\n"
;
}
}
}
}
}
void kstr(Function& func){
for
(BasicBlock& bb:func){
for
(Instruction& ins :bb){
for
(Value
*
val:ins.operands()){
Value
*
stripOp
=
val
-
>stripPointerCasts();
if
(stripOp
-
>getName().contains(
".str"
)){
errs()<<ins<<
"\n"
;
errs()<<
*
val<<
"\n"
;
errs()<<
*
stripOp<<
"\n"
;
}
}
}
}
}
store i8
*
getelementptr inbounds ([
7
x i8], [
7
x i8]
*
@.
str
, i64
0
, i64
0
), i8
*
*
%
str
, align
8
i8
*
getelementptr inbounds ([
7
x i8], [
7
x i8]
*
@.
str
, i64
0
, i64
0
)
@.
str
=
private unnamed_addr constant [
7
x i8] c
"kanxue\00"
, align
1
store i8
*
getelementptr inbounds ([
7
x i8], [
7
x i8]
*
@.
str
, i64
0
, i64
0
), i8
*
*
%
str
, align
8
i8
*
getelementptr inbounds ([
7
x i8], [
7
x i8]
*
@.
str
, i64
0
, i64
0
)
@.
str
=
private unnamed_addr constant [
7
x i8] c
"kanxue\00"
, align
1
/
/
封装一个转换操作数值为字符串的函数
std::string ConvertOpToString(Value
*
op){
GlobalVariable
*
globalVar
=
dyn_cast<GlobalVariable>(op);
if
(!globalVar){
errs()<<
"dyn cast gloabl err"
;
return
"";
}
ConstantDataSequential
*
cds
=
dyn_cast<ConstantDataSequential>(globalVar
-
>getInitializer());
if
(!cds){
errs()<<
"dyn cast constant data err"
;
return
"";
}
return
cds
-
>getRawDataValues();;
}
void kstr(Function& func){
for
(BasicBlock& bb:func){
for
(Instruction& ins :bb){
for
(Value
*
val:ins.operands()){
Value
*
stripOp
=
val
-
>stripPointerCasts();
if
(stripOp
-
>getName().contains(
".str"
)){
std::string strdata
=
ConvertOpToString(stripOp);
errs()<<strdata<<
"\n"
;
}
}
}
}
}
/
/
封装一个转换操作数值为字符串的函数
std::string ConvertOpToString(Value
*
op){
GlobalVariable
*
globalVar
=
dyn_cast<GlobalVariable>(op);
if
(!globalVar){
errs()<<
"dyn cast gloabl err"
;
return
"";
}
ConstantDataSequential
*
cds
=
dyn_cast<ConstantDataSequential>(globalVar
-
>getInitializer());
if
(!cds){
errs()<<
"dyn cast constant data err"
;
return
"";
}
return
cds
-
>getRawDataValues();;
}
void kstr(Function& func){
for
(BasicBlock& bb:func){
for
(Instruction& ins :bb){
for
(Value
*
val:ins.operands()){
Value
*
stripOp
=
val
-
>stripPointerCasts();
if
(stripOp
-
>getName().contains(
".str"
)){
std::string strdata
=
ConvertOpToString(stripOp);
errs()<<strdata<<
"\n"
;
}
}
}
}
}
kanxue
hello ollvm:
%
d
kanxue
hello ollvm:
%
d
/
/
转换字符串
std::string ConvertOpToString(Value
*
op){
GlobalVariable
*
globalVar
=
dyn_cast<GlobalVariable>(op);
if
(!globalVar){
errs()<<
"dyn cast gloabl err"
;
return
"";
}
ConstantDataSequential
*
cds
=
dyn_cast<ConstantDataSequential>(globalVar
-
>getInitializer());
if
(!cds){
errs()<<
"dyn cast constant data err"
;
return
"";
}
return
cds
-
>getRawDataValues();;
}
void kstr(Function& func){
for
(BasicBlock& bb:func){
for
(Instruction& ins :bb){
for
(Value
*
val:ins.operands()){
Value
*
stripOp
=
val
-
>stripPointerCasts();
if
(stripOp
-
>getName().contains(
".str"
)){
std::string strdata
=
ConvertOpToString(stripOp);
errs()<<strdata<<
"\n"
;
/
/
加密流程
uint8_t keys[strdata.size()];
char encres[strdata.size()];
int
idx
=
0
;
memset(encres,
0
,strdata.size());
for
(
int
i
=
0
;i<strdata.size();i
+
+
){
uint8_t randkey
=
llvm::cryptoutils
-
>get_uint8_t();
keys[i]
=
randkey;
int
enclen
=
randkey
+
defaultKStringSize;
for
(
int
y
=
randkey;y<enclen;y
+
+
){
if
(y
=
=
randkey){
encres[i]
=
strdata[i]^y;
}
else
if
(y
=
=
enclen
-
1
){
encres[i]
=
encres[i]^(~y);
}
else
{
encres[i]
=
encres[i]^y;
}
idx
+
+
;
}
}
}
}
}
}
}
/
/
转换字符串
std::string ConvertOpToString(Value
*
op){
GlobalVariable
*
globalVar
=
dyn_cast<GlobalVariable>(op);
if
(!globalVar){
errs()<<
"dyn cast gloabl err"
;
return
"";
}
ConstantDataSequential
*
cds
=
dyn_cast<ConstantDataSequential>(globalVar
-
>getInitializer());
if
(!cds){
errs()<<
"dyn cast constant data err"
;
return
"";
}
return
cds
-
>getRawDataValues();;
}
void kstr(Function& func){
for
(BasicBlock& bb:func){
for
(Instruction& ins :bb){
for
(Value
*
val:ins.operands()){
Value
*
stripOp
=
val
-
>stripPointerCasts();
if
(stripOp
-
>getName().contains(
".str"
)){
std::string strdata
=
ConvertOpToString(stripOp);
errs()<<strdata<<
"\n"
;
/
/
加密流程
uint8_t keys[strdata.size()];
char encres[strdata.size()];
int
idx
=
0
;
memset(encres,
0
,strdata.size());
for
(
int
i
=
0
;i<strdata.size();i
+
+
){
uint8_t randkey
=
llvm::cryptoutils
-
>get_uint8_t();
keys[i]
=
randkey;
int
enclen
=
randkey
+
defaultKStringSize;
for
(
int
y
=
randkey;y<enclen;y
+
+
){
if
(y
=
=
randkey){
encres[i]
=
strdata[i]^y;
}
else
if
(y
=
=
enclen
-
1
){
encres[i]
=
encres[i]^(~y);
}
else
{
encres[i]
=
encres[i]^y;
}
idx
+
+
;
}
}
}
}
}
}
}
ArrayType
*
arrType
=
ArrayType::get(
Type
::getInt8Ty(func.getContext()),strdata.size());
AllocaInst
*
arrayInst
=
new AllocaInst(arrType,
0
,nullptr,
1
,Twine(stripOp
-
>getName()
+
".arr"
),&ins);
BitCastInst
*
bitInst
=
new BitCastInst(arrayInst,
Type
::getInt8PtrTy(func.getParent()
-
>getContext()),Twine(stripOp
-
>getName()
+
"bitcast"
),&ins);
ArrayType
*
arrType
=
ArrayType::get(
Type
::getInt8Ty(func.getContext()),strdata.size());
AllocaInst
*
arrayInst
=
new AllocaInst(arrType,
0
,nullptr,
1
,Twine(stripOp
-
>getName()
+
".arr"
),&ins);
BitCastInst
*
bitInst
=
new BitCastInst(arrayInst,
Type
::getInt8PtrTy(func.getParent()
-
>getContext()),Twine(stripOp
-
>getName()
+
"bitcast"
),&ins);
ArrayType
*
arrType
=
ArrayType::get(
Type
::getInt8Ty(func.getContext()),strdata.size());
AllocaInst
*
arrayInst
=
new AllocaInst(arrType,
0
,nullptr,
1
,Twine(stripOp
-
>getName()
+
".arr"
),&ins);
BitCastInst
*
bitInst
=
new BitCastInst(arrayInst,
Type
::getInt8PtrTy(func.getParent()
-
>getContext()),Twine(stripOp
-
>getName()
+
".bitcast"
),&ins);
/
/
创建一个对象用来存放当前加密字节解密时每次异或的结果
AllocaInst
*
eor_res
=
new AllocaInst(
Type
::getInt8Ty(func.getContext()),
0
,nullptr,
1
,Twine(stripOp
-
>getName()
+
".alloc.res"
),&ins);
for
(
int
i
=
0
;i<strdata.size();i
+
+
){
uint8_t randkey
=
keys[i];
int
enclen
=
randkey
+
defaultKStringSize;
ConstantInt
*
enc_const
=
ConstantInt::get(
Type
::getInt8Ty(func.getContext()),encres[i]);
/
/
用来存放解密结果的bitcat
ConstantInt
*
i_const
=
ConstantInt::get(
Type
::getInt8Ty(func.getContext()),i);
GetElementPtrInst
*
element
=
GetElementPtrInst::CreateInBounds(bitInst,i_const);
element
-
>insertBefore(&ins);
StoreInst
*
last_store
=
nullptr;
for
(
int
y
=
enclen
-
1
;y>
=
randkey;y
-
-
){
/
*
下面是获取y的指令块
*
/
/
/
先是创建一个数值y
ConstantInt
*
eor_data
=
ConstantInt::get(
Type
::getInt8Ty(func.getContext()),y);
/
/
申请一个int8的内存来存放数值y
AllocaInst
*
eor_alloc
=
new AllocaInst(
Type
::getInt8Ty(func.getContext()),
0
,nullptr,
1
,Twine(stripOp
-
>getName()
+
".alloc.y"
),&ins);
/
/
将数值y赋值给申请的内存空间
StoreInst
*
store_eor
=
new StoreInst(eor_data,eor_alloc);
store_eor
-
>insertAfter(eor_alloc);
/
/
从内存空间中加载里面的数值y
LoadInst
*
eor_load
=
new LoadInst(eor_alloc,"");
eor_load
-
>insertAfter(store_eor);
/
/
如果是第一次异或
if
(y
=
=
enclen
-
1
){
/
/
然后进行取反计算
BinaryOperator
*
binNotOp
=
BinaryOperator::CreateNot(eor_load);
binNotOp
-
>insertAfter(eor_load);
/
/
然后异或
BinaryOperator
*
binXorOp
=
BinaryOperator::CreateXor(enc_const,binNotOp);
binXorOp
-
>insertAfter(binNotOp);
/
/
将加密字节设置为上次异或的结果
StoreInst
*
store_eor_res
=
new StoreInst(binXorOp,eor_res);
store_eor_res
-
>insertAfter(store_data);
}
else
{
/
/
加载获取上次异或的结果
LoadInst
*
eor_load_res
=
new LoadInst(eor_res,stripOp
-
>getName()
+
".load"
);
eor_load_res
-
>insertAfter(store_eor);
/
/
然后再进行异或计算
BinaryOperator
*
binXorOp
=
BinaryOperator::CreateXor(eor_load_res,eor_load);
binXorOp
-
>insertAfter(eor_load);
/
/
将计算后的结果存放回数组中
StoreInst
*
store_data
=
new StoreInst(binXorOp,eor_res);
store_data
-
>insertAfter(binXorOp);
/
/
当循环到最后一次时,获取一下最后一次赋值的指令块地址。方便后面接着往后插指令块
if
(y
=
=
randkey){
last_store
=
store_data;
}
}
}
/
/
读取这个字节经过多次异或后的最终结果
LoadInst
*
dec_res
=
new LoadInst(eor_res,stripOp
-
>getName()
+
".dec.res"
);
dec_res
-
>insertAfter(last_store);
/
/
将这个结果写入到前面用来存放的解密结果bitcat处
StoreInst
*
store_data
=
new StoreInst(dec_res,element);
store_data
-
>insertAfter(dec_res);
}
ArrayType
*
arrType
=
ArrayType::get(
Type
::getInt8Ty(func.getContext()),strdata.size());
AllocaInst
*
arrayInst
=
new AllocaInst(arrType,
0
,nullptr,
1
,Twine(stripOp
-
>getName()
+
".arr"
),&ins);
BitCastInst
*
bitInst
=
new BitCastInst(arrayInst,
Type
::getInt8PtrTy(func.getParent()
-
>getContext()),Twine(stripOp
-
>getName()
+
".bitcast"
),&ins);
/
/
创建一个对象用来存放当前加密字节解密时每次异或的结果
AllocaInst
*
eor_res
=
new AllocaInst(
Type
::getInt8Ty(func.getContext()),
0
,nullptr,
1
,Twine(stripOp
-
>getName()
+
".alloc.res"
),&ins);
for
(
int
i
=
0
;i<strdata.size();i
+
+
){
uint8_t randkey
=
keys[i];
int
enclen
=
randkey
+
defaultKStringSize;
ConstantInt
*
enc_const
=
ConstantInt::get(
Type
::getInt8Ty(func.getContext()),encres[i]);
/
/
用来存放解密结果的bitcat
ConstantInt
*
i_const
=
ConstantInt::get(
Type
::getInt8Ty(func.getContext()),i);
GetElementPtrInst
*
element
=
GetElementPtrInst::CreateInBounds(bitInst,i_const);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)