imgencryptWriteUp

Author Avatar
ciaoly 2021年08月06日
  • 在其它设备中阅读本文章

imgencryptWriteUp

题目链接: SOCTF - 综合实训平台

下载附件

题目描述:

小明为了安全,把图片用工具加密起来了,但没想到这个工具没有解密功能,你能帮他恢复吗?

flag格式: flag{xxxxxx}

下载题目附件, 打开发现有imgencryptoutput两个文件. 使用file命令查看imgencryptelf 64, outputdata. 猜测imgencrypt为加密程序, output为需要解密的图片. 运行加密程序, 发现如题目描述所示, 其解密程序"未实现".

ida动态调试(远程调试)

ida在加载该elf时, 提示"idt错误", 可能需要修补idt. 但是因为elf在运行时并不需要idt, 所以决定采用动态调试的方式对程序进行辅助分析.

在ida的函数列表中, 发现未混淆的encrypt函数和decrypt函数. 打开decrypt函数, 发现看不懂(

将ida64安装目录下的dbgsrv/里的Linux调试服务器上传到Linux主机, 设置好权限和防火墙并运行, 进行远程调试. 程序运行时的主菜单如下所示:

欢迎使用文件加密工具
=========================
[1] 加密文件
[2] 解密文件
[3] 退出
请输入指令:

尝试破解decrypt函数

在菜单栏的Jump命令里选择Jump to function, 跳转到decrypt函数, 设置断点. 在Linux主机中对程序进行交互, 按照程序提示, 输入2并回车, 此时程序断在decrypt函数中. 在ida中步进跟踪该函数, 发现其做了一系列看不懂的操作(

看起来decrypt函数应该是如同题目描述所言的"并未实现"了...

根据encrypt函数逆向猜解解密函数

encrypt函数运行过程如下:

请输入指令: 1
请输入要加密的文件名:
bmp
请输入加密后的文件名:
output2
文件加密成功!

同样是使用Jump to function, 跳转到encrypt函数并设置断点. 此时使用f5可以直接查看C语言代码. 可以看到其主要逻辑如下所示: (这段代码其实是使用ida静态分析之后得到的代码, 使用动态调试后得到的代码与这个略有不同, 但大体类似. 注释为猜测的可能的函数)

__int64 __usercall encrypt@<rax>(__int64 a1@<rdx>, __int64 a2@<rbp>, __int64 a3@<rsi>)
{
  // ...
  v12 = sub_55D4BA9FF120(&v14, &unk_55D4BAA000D8); // fpInput = fopen(filename, 'r');
  if ( v12 )
  {
    v13 = sub_55D4BA9FF120(&v15, &unk_55D4BAA000F3);// fpOutput = fopen(outputFilename, 'w');
    if ( v13 )
    {
      while ( 1 )
      {
        v11 = sub_55D4BA9FF0E0(&v16, 1LL, 10LL, v12); // fileReadCount = fread(buffer, 1, 10, fpInput)
        if ( v11 <= 0 )
          break;
        for ( i = 0; i < v11; ++i ) // 这里是主要的加密逻辑
          *((_BYTE *)&v18 + i - 208) ^= aN0b0dykn0w[i];// buffer[i] ^= key[i];
        sub_55D4BA9FF150(&v16, 1LL, v11, v13);// fwrite(buffer, 1, fileReadCount, fpOutput);
      }
      sub_55D4BA9FF0D0(&unk_55D4BAA00117, 1LL, v7);
      sub_55D4BA9FF0F0(v12); // fclose(fpInput);
      v4 = (void *)v13;
      sub_55D4BA9FF0F0(v13);// fclose(fpOutput);
      result = 0LL;
    }
    else
    {
      v4 = &unk_55D4BAA000F8;
      sub_55D4BA9FF110();
      result = 0LL;
    }
  }
  else
  {
    v4 = &unk_55D4BAA000DB;
    sub_55D4BA9FF110();
    result = 0LL;
  }
  v9 = __readfsqword(0x28u);
  v8 = v9 ^ v17;
  if ( v9 != v17 )
    result = sub_55D4BA9FF100(v4, v8, v5);
  return result;
}
  1. 利用第一个逻辑分支判断v12:

    输入一个不存在的文件名, v12的值为0, 程序打印"打开文件失败"并退出. 因此v12为读文件的指针.

  2. 利用第二个逻辑分支判断v13:

    同上, 输入一个不可写的文件名, v13的值为0, 程序打印"写入文件失败"并退出. 因此v13为写文件的指针.

  3. 判断v11:

    根据函数v11 = sub_55D4BA9FF0E0(&v16, 1LL, 10LL, v12);,立即数110的差值为10. 动态调试时发现, 该函数执行结束后返回值v11==0x0A, 因此v11可能为读取文件后返回的数据长度.

  4. 利用断点判断*((_BYTE *)&v18 + i - 208):

    主要加密功能的代码如下所示:

    loc_5606C2F46449:                       ; CODE XREF: encrypt+167↓j
                   mov     eax, [rbp-1CCh]
                   cdqe
                   movzx   ecx, byte ptr [rbp+rax-0D0h]
                   mov     eax, [rbp-1CCh]
                   cdqe
                   lea     rdx, aN0b0dykn0w ; "n0b0dykn0w"
                   movzx   eax, byte ptr [rax+rdx]
                   xor     ecx, eax
                   mov     edx, ecx
                   mov     eax, [rbp-1CCh]
                   cdqe
                   mov     [rbp+rax-0D0h], dl
                   add     dword ptr [rbp-1CCh], 1
    
    loc_5606C2F46486:                       ; CODE XREF: encrypt+11C↑j
                   mov     eax, [rbp-1CCh]
                   cmp     eax, [rbp-1C4h]
                   jl      short loc_5606C2F46449

    在异或运算(xor ecx,eax)处设置断点. ecx的值来自于内存[rbp+rax-0D0h](0xD0=208), 对应的c语言代码应该是*((_BYTE *)&v18 + i - 208); eax的值来自于内存[rax+aN0b0dykn0w], 对应的c语言代码应该是aN0b0dykn0w[i]. 连续点击运行按钮多次, 每次在断点处监视寄存器ecxeax的值, 发现ecx的值即为文件的字节. 因此可以判断*((_BYTE *)&v18 - 208)即为fread函数的buffer. (其实直接看后面fwrite时的buffer地址也能判断)

  5. 判断fwrite:

    定位如下代码

    mov     eax, [rbp-1C4h]
    movsxd  rdx, eax
    mov     rcx, [rbp-1B8h]
    lea     rax, [rbp-0D0h]
    mov     esi, 1
    mov     rdi, rax
    call    sub_5606C2F46150
    参数1(rdi) 参数2(rsi) 参数3(rdx) 参数4(rcx)
    [rbp-0D0H] 1 [rbp-1C4h] [rbp-1B8h]

    参数1即为上述4所分析出的buffer的地址, 参数2为固定值1, 参数3为上述3分析出的v11, 参数4为上述2分析出的"写文件指针".

综上, 可以看出该程序的加密逻辑为:

读取文件, 将文件中每十个字节与字符串"n0b0dykn0w"进行异或运算, 输出文件.

求解flag

按照上文分析, 既然是"异或加密", 且密钥是写死在程序中的. 则只需要用改程序对加密文件再执行一次"加密操作"即可解密. 运行程序, 对output文件进行加密操作, 打开输出的文件即为flag.

flag{y0u_f0und_the_0riginal_image}