这次我们来了解一下Tea系列算法,先从原始的Tea
开始了解
在密码学中,微型加密算法(Tiny Encryption Algorithm,TEA)是一种易于描述和执行的块密码,通常只需要很少的代码就可实现。其设计者是剑桥大学计算机实验室的大卫·惠勒与罗杰·尼达姆。这项技术最初于1994年提交给鲁汶的快速软件加密的研讨会上,并在该研讨会上演讲中首次发表。
TEA操作处理在两个32位无符号整型上(可能源于一个64位数据),并且使用一个128位的密钥。XTEA和XXTEA算法都是TEA算法的升级版,并且完善了其安全性。
有关其他的介绍可以参考百度百科
原理图如下:
算法的标准代码如下:
//加密函数
void encrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}
//解密函数
void decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}
话说QQ和微信的一些协议中就是使用的Tea加密
拿到题目,并没有乱七八糟的混淆,程序逻辑稍微看一下代码也很快能明白。
前面很大一部分是在验证flag的格式是否满足flag{*-*}
,然后将中间的字符串取出,以-
作为分隔符分别赋予v11和v12,经过sub_400A80
和sub_400836
函数转化后对结果进行校验。
其中`函数很明显就是
Tea`算法了。
而且我们通过Findcrypto
插件也很容易的识别出了Tea
标准算法的特征量0x61C88647
。
同时稍微对照一下源代码,我们便可以确定这就是标准的Tea
加密函数,那么接下来就是找key
和enc
的过程了。
Tea
的key
是四个32位无符号整数,enc
是两个32位无符号整数,我们需要牢记这一点。
接下来回到题目,strtok
函数是根据-
截取字符串,很明显程序中分别获取了以-
分隔的两个字符串。查看一下sub_400A80
函数。
不难看出是一个hex2int
函数,当然最好的办法就是动态观察一遍。注意到sub_400836
函数的两个参数v11
和v13
,其中v11
就是需要加密的flag,v13
就是key
,因此key即为{2,2,3,4}
,这里的int
是32位。
经过加密后,程序分两段对enc
进行判断。
第一段:
注意其中的强制类型转换,这一段代码所做的操作就是逐字节校验enc
的前四个字节,根据{3,1,0,2}
的顺序,所以第一段密文为0x67d7b805
第二段:
这段代码相对简单,直接进行的一个==
比较,其中sub_400bdd
进行了字符串的逆序,所以第二段密文为0x63c174c3
要注意小端序。
得到enc
和key
后我们便可以尝试带入标准Tea
的解密函数解密了。
这里呢,我想具体讲一下如何出题。其实我相信大家都已经做了或多或少许多的题目了,对于题目的一般套路也有了许多了解,我觉得也应该有必要试着自己出题来提升自己了,毕竟我一直觉得逆向工程的本质是代码的复用(复读机)
,Haha说的有点高大,其实我也是菜鸡,只是最近写了几个题感觉有点心得,所以分享出来。
明确了以上几点之后,就是写代码,往里面添东西了,最忌讳的就是一想到什么新奇的东西就往里加,这样不仅增加出题的成本,还非常容易导致题目出现逻辑漏洞,甚至无解的情况。
要求输入flag 长度不定 格式为 flag{*-*}
编写flag格式验证代码
编写取出 v0 和 v1 的代码 并进行类型转换 hex2int
对输入的flag进行加密 得到enc
验证程序是否实现其功能
验证解题思路是否符合逻辑。
获取flag , 不指定长度
验证flag格式 flag长度为6+1+8+8
按照格式要求 取出 v0 和 v1 如 E01a345b
key 内置
Tea加密
分段进行比较
第一段:
逐字节比较
第二段:
32位无符号数直接比较
解题思路:
动态调试,看懂flag格式验证的策略
根据数据特征识别算法
动调 dump 出 key 和最后的 enc
编写解密脚本 得到 plain
根据格式要求代入程序进行验证
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
//加密函数
void encrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}
//解密函数
void decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}
int getStr(char *buffer,int maxLen){
char c; // 读取到的一个字符
int len = 0; // 当前输入的字符串的长度
// 一次读取一个字符,保存到buffer
// 直到遇到换行符(\n),或者长度超过maxLen时,停止读取
while( (c=getchar()) != '\n' ){
buffer[len++]=c; // 将读取到的字符保存到buffer
if(len>=maxLen){
break;
}
}
buffer[len]='\0'; // 读取结束,在末尾手动添加字符串结束标志
fflush(stdin); // 刷新输入缓冲区
return len;
}
/*将大写字母转换成小写字母*/
int tolower(int c)
{
if (c >= 'A' && c <= 'Z')
{
return c + 'a' - 'A';
}
else
{
return c;
}
}
//将十六进制的字符串转换成整数
int htoi(char s[])
{
int i = 0;
int n = 0;
if (s[0] == '0' && (s[1]=='x' || s[1]=='X'))
{
i = 2;
}
else
{
i = 0;
}
for (; (s[i] >= '0' && s[i] <= '9') || (s[i] >= 'a' && s[i] <= 'z') || (s[i] >='A' && s[i] <= 'Z');++i)
{
if (tolower(s[i]) > '9')
{
n = 16 * n + (10 + tolower(s[i]) - 'a');
}
else
{
n = 16 * n + (tolower(s[i]) - '0');
}
}
return n;
}
void reverse(char *s, int start, int end)
{
char t;
while(end>start){
t=s[start];
s[start]=s[end];
s[end]=t;
start++;
end--;
}
}
int main()
{
uint32_t v[2]={1,2},k[4]={2,2,3,4};
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
int flagLen = 0;
bool success = false;
char flag[33];
memset(flag, 0, sizeof(flag));//清空字符串
printf("Please input you flag:");
flagLen = getStr(flag,32);
//check formant
uint8_t vv[5] = {0};
strncpy(vv,flag,4);
uint8_t five = 123;
uint8_t last = 125;
uint8_t *v1,*v2;
if(((uint8_t)flag[5] - five)>0){
printf("five error!");
return -1;
}
if(((uint8_t)flag[flagLen-1] + last) == 250){
;
}else{
printf("last error!");
return -1;
}
if(strcmp(vv,"flag")){
printf("header error!");
return -1;
}
int mallocSize = flagLen - 6;
char *tokstr = (char *)malloc(sizeof(char)*mallocSize+1);
memset(tokstr, 0, sizeof(tokstr));//清空字符串
strncpy(tokstr,flag+5,mallocSize);
v1 = strtok(tokstr,"-");
v2 = strtok(NULL,"-");
//exchange scale
uint32_t flagLong[2];
flagLong[0] = (uint32_t)htoi((char *)v1);
flagLong[1] = (uint32_t)htoi((char *)v2);
// printf("%d",sizeof(int)); 4 byte == 32 bit
// printf("加密前原始数据:%x %x\n",flagLong[0],flagLong[1]);
encrypt(flagLong, k);
// printf("加密后的数据:%x %x\n",flagLong[0],flagLong[1]);
// check flag
uint8_t check_enc[4];
uint8_t check_index[4] = {3,1,0,2};
uint8_t i=0;
check_enc[0] = 0x5;
check_enc[1] = 0xd7;
check_enc[2] = 0xb8;
check_enc[3] = 0x67;
for(i=0;i<4;i++){
uint8_t t = (uint8_t)(flagLong[0]>>(8*i));
// printf("%x\t",t);
if(check_enc[i]!=flagLong[check_index[i]]){
success = false;
}
}
char check_enc_last[9] = "3c471c36";
// snprintf(check_enc_last,9,"%x",flagLong[1]);//63c174c3
reverse(check_enc_last,0,7);
uint32_t enc_hex = htoi(check_enc_last);
// printf("%x",enc_hex);
if(flagLong[1] == enc_hex){
success = true;
}
if(!success){
printf("You Lost!\n");
}else{
printf("You Win!\n");
}
return 0;
}
makefile如下:
# modify CC to your own obfuscator-llvm location!
CC := /home/***/Desktop/llvm-4.0/build/bin/clang
CFLAGS := -s -mllvm -fla -mllvm -sub -mllvm -bcf
OUT := tea-level-1
SRC := main.c
# default: $(OUT)
.PHONY:build
build: $(SRC)
$(CC) $(CFLAGS) $^ -o $@
.PHONY:clean
clean: *.o
rm -rf *.o
编译命令:make build
希望各位大佬能一起交流进步,毕竟圈子这么小,说不定能做到各位师傅的题目。
以上是我的一点小心得,大佬勿喷。
解题和数据流分析见下一篇