前言

这次我们来了解一下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_400A80sub_400836函数转化后对结果进行校验。

其中`函数很明显就是Tea`算法了。

而且我们通过Findcrypto插件也很容易的识别出了Tea标准算法的特征量0x61C88647

同时稍微对照一下源代码,我们便可以确定这就是标准的Tea加密函数,那么接下来就是找keyenc的过程了。

Teakey是四个32位无符号整数,enc是两个32位无符号整数,我们需要牢记这一点。

接下来回到题目,strtok函数是根据-截取字符串,很明显程序中分别获取了以-分隔的两个字符串。查看一下sub_400A80函数。

不难看出是一个hex2int函数,当然最好的办法就是动态观察一遍。注意到sub_400836函数的两个参数v11v13,其中v11就是需要加密的flag,v13就是key,因此key即为{2,2,3,4},这里的int是32位。

经过加密后,程序分两段对enc进行判断。
第一段:

注意其中的强制类型转换,这一段代码所做的操作就是逐字节校验enc的前四个字节,根据{3,1,0,2}的顺序,所以第一段密文为0x67d7b805

第二段:

这段代码相对简单,直接进行的一个==比较,其中sub_400bdd进行了字符串的逆序,所以第二段密文为0x63c174c3

要注意小端序。

得到enckey后我们便可以尝试带入标准Tea的解密函数解密了。

二、出题

这里呢,我想具体讲一下如何出题。其实我相信大家都已经做了或多或少许多的题目了,对于题目的一般套路也有了许多了解,我觉得也应该有必要试着自己出题来提升自己了,毕竟我一直觉得逆向工程的本质是代码的复用(复读机),Haha说的有点高大,其实我也是菜鸡,只是最近写了几个题感觉有点心得,所以分享出来。

  1. 明确想要设置的考察点
  2. 编写出题思路
  3. 确定程序要实现的功能
  4. 编写解题思路
  5. 验证解题思路

明确了以上几点之后,就是写代码,往里面添东西了,最忌讳的就是一想到什么新奇的东西就往里加,这样不仅增加出题的成本,还非常容易导致题目出现逻辑漏洞,甚至无解的情况。

出题思路:

要求输入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

总结

希望各位大佬能一起交流进步,毕竟圈子这么小,说不定能做到各位师傅的题目。

以上是我的一点小心得,大佬勿喷。
解题和数据流分析见下一篇

源链接

Hacking more

...