1、申请ID:b1gcat
2、个人邮箱:admistrator@foxmail.com
3、原创技术文章:某线下比赛一道ctf逆向分析
昨天参加了一场ctf拿到了一道re题目,解题过程分享给大家,本次解题使用了ida、angr
下载题目(bits为64elf文件,core为14字节乱码)
b1gcat@b1gcat bits ls -l
total 32
-rw-rw-r--@ 1 b1gcat staff 10296 2 15 2021 bits
-rw-rw-r--@ 1 b1gcat staff 14 2 15 2021 code
运行bits(实际,bits是运行 在服务器上的,需要交互式输入answer)
└─# ./bits
Your number is 4037157444
Your answer: 111
Thanks for coming!
将bits拖入die,无壳;(图略)
查看是否存在preinit、init等处理
└─# r2 -e bin.cache=true bits
-- Find hexpairs with '/x a0 cc 33'
[0x00000af0]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze all functions arguments/locals
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Finding and parsing C++ vtables (avrr)
[[ESIL] Trap, trying to execute on non-executable memory
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information (aanr)
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x00000af0]> iee
[Constructors]
vaddr=0x00000bf0 paddr=0x00000bf0 hvaddr=0x00201d20 hpaddr=0x00001d20 type=init
vaddr=0x00000bb0 paddr=0x00000bb0 hvaddr=0x00201d28 hpaddr=0x00001d28 type=fini
2 entrypoints
[0x00000af0]> pdg @ 0x00000bf0
// WARNING: Removing unreachable block (ram,0x00000b88)
// WARNING: Removing unreachable block (ram,0x00000b94)
void entry.init0(void)
{
return;
}
看起来基本上是一个中规中矩的逆向 happy。
拖入IDA,F5后很输入逻辑很清晰(不上图了,图显示不了):
int v5; // eax
unsigned int v6; // [rsp+0h] [rbp-40h] BYREF
unsigned int randkey; // [rsp+4h] [rbp-3Ch]
unsigned int codelen; // [rsp+8h] [rbp-38h]
unsigned int size_4; // [rsp+Ch] [rbp-34h]
void *code; // [rsp+10h] [rbp-30h]
void *s; // [rsp+18h] [rbp-28h]
FILE *stream; // [rsp+20h] [rbp-20h]
unsigned __int64 v13; // [rsp+28h] [rbp-18h]
v13 = __readfsqword(0x28u);
if ( ptrace(PTRACE_TRACEME, 0LL, 1LL, 0LL) < 0 )// 反调试,需要的话可以直接nop掉
{
puts("Do not trace me!");
exit(0);
}
v3 = getpid();
v4 = getsid(v3);
if ( v4 != getppid() ) // //反调试,nop掉即可
{
puts("Do not trace me!");
exit(1);
}
code = 0LL;
s = 0LL;
randkey = sub_BFA(); // 随机数生成
stream = fopen("code", "rb"); // 打开14字节的乱码文件
if ( !stream )
{
fwrite("Could not open file code!\n", 1uLL, 0x1AuLL, stderr);
exit(1);
}
fseek(stream, 0LL, 2);
codelen = ftell(stream);
fseek(stream, 0LL, 0);
code = malloc(codelen);
if ( fread(code, codelen, 1uLL, stream) != 1 )
{
fwrite("Error reading file code\n", 1uLL, 0x18uLL, stderr);
exit(1);
}
size_4 = sub_C31(randkey, codelen, (__int64)code);
printf("Your number is %u\n", size_4);
printf("Your answer: ");
__isoc99_scanf("%d", &v6);
v5 = sub_C31(v6, codelen, (__int64)code);
if ( size_4 == v5 )
{
puts("Congrats!");
s = malloc(0x40uLL);
memset(s, 0, 0x40uLL);
stream = fopen("flag.txt", "rb");
fgets((char *)s, 64, stream);
分析下代码逻辑:
1、生成随机数,随机数randkey和code(固定)作为输入传入加密函数sub_C31中并返回一个加密后的结果y1,并输出"you number is: y";
2、我们输入一个inputkey,inputkey和 code(固定)作为输入传入加密函数sub_C31中并返回一个加密后的结果y2
3、看来只要y1==y2就行了
结论:通过分析如果y1=y2,那么inputkey和randkey必然相等,但是randkey是无法预测的,只能从加密函数sub_C31分析依据解密解果推导输入的随机数即可。
我们看下sub_C31
__int64 __fastcall sub_C31(unsigned int input, int codelen, char *a3)
{
int v3; // eax
int v4; // eax
unsigned __int8 v7; // [rsp+13h] [rbp-1Dh]
unsigned __int8 v8; // [rsp+13h] [rbp-1Dh]
unsigned __int8 v9; // [rsp+13h] [rbp-1Dh]
unsigned __int8 v10; // [rsp+13h] [rbp-1Dh]
unsigned __int8 v11; // [rsp+13h] [rbp-1Dh]
char v12; // [rsp+13h] [rbp-1Dh]
int v13; // [rsp+14h] [rbp-1Ch]
unsigned int v14; // [rsp+18h] [rbp-18h]
unsigned int v15; // [rsp+1Ch] [rbp-14h]
int v16; // [rsp+20h] [rbp-10h]
int v17; // [rsp+24h] [rbp-Ch]
int i; // [rsp+28h] [rbp-8h]
v17 = 0;
v16 = 0;
v15 = 0;
v14 = 0;
v13 = 0;
for ( i = 0; i < codelen; ++i )
{
v7 = a3[i];
if ( (v7 & 1) != 0 )
{
input ^= dword_202020[v13];
v13 = ((_BYTE)v13 + 1) & 0xF;
}
v8 = v7 >> 1;
v3 = v8 & 3;
if ( v3 == 2 )
{
v15 = dword_202020[v13] & 0xAABBCCDD;
v13 = ((_BYTE)v13 + 1) & 0xF;
v9 = v8 >> 2;
}
else if ( v3 == 3 )
{
input += v14 + v15;
v15 = 0;
v14 = 0;
v9 = v8 >> 2;
}
else
{
if ( v3 == 1 )
{
v14 = dword_202020[v13] | 0xABCDABCD;
v13 = ((_BYTE)v13 + 1) & 0xF;
}
v9 = v8 >> 2;
}
if ( (v9 & 1) != 0 )
input = ~input;
v10 = v9 >> 1;
if ( (v10 & 1) != 0 )
input ^= (((input << 16) ^ input) >> 16) ^ (input << 16);
v11 = v10 >> 1;
v4 = v11 & 3;
if ( v4 == 2 )
{
v17 = dword_202020[v13] - 539034144;
v13 = ((_BYTE)v13 + 1) & 0xF;
v12 = v11 >> 2;
}
else if ( v4 == 3 )
{
input += v16 + v17;
v17 = 0;
v16 = 0;
v12 = v11 >> 2;
}
else
{
if ( v4 == 1 )
{
v16 = 539034132 * dword_202020[v13];
v13 = ((_BYTE)v13 + 1) & 0xF;
}
v12 = v11 >> 2;
}
if ( (v12 & 1) != 0 )
input = input - (input & 7) - (input & 7) + 7;
}
return input;
}
好家伙有点长,大循环 套小循环,先大橘关分析下:
1)dword_202020是固定数组,可以认为是常量
2)整个循环计算是依据code来驱动的,也就是说,code不变循环流程不变 (很关键)
3)最后返回变形(加密)后的input
分析下,这么多循环逆向出来太阳估计下山了,想用angr直接 躺平但是angr遇到循环怂得一B基本跑不出结果,怎么办?
一个声音响起上述分析中如果加密的序列就不会变,那么把序列输出出来是不是就更加简化流程,而且还去掉了循环了吗,先试试?
于是我把加密函数copy出来,然后在每个处理input地方家了打印,以便输出固定的加密序列,修改后的加密函数:
__int64 sub_C31(unsigned int key, int bufsize, unsigned char *buf)
{
int v3; // eax
int v4; // eax
unsigned __int8 v7; // [rsp+13h] [rbp-1Dh]
unsigned __int8 v8; // [rsp+13h] [rbp-1Dh]
unsigned __int8 v9; // [rsp+13h] [rbp-1Dh]
unsigned __int8 v10; // [rsp+13h] [rbp-1Dh]
unsigned __int8 v11; // [rsp+13h] [rbp-1Dh]
char v12; // [rsp+13h] [rbp-1Dh]
int v13; // [rsp+14h] [rbp-1Ch]
unsigned int v14; // [rsp+18h] [rbp-18h]
unsigned int v15; // [rsp+1Ch] [rbp-14h]
int v16; // [rsp+20h] [rbp-10h]
int v17; // [rsp+24h] [rbp-Ch]
int i; // [rsp+28h] [rbp-8h]
v17 = 0;
v16 = 0;
v15 = 0;
v14 = 0;
v13 = 0;
for ( i = 0; i < bufsize; ++i )
{
v7 = buf[i];
if ( (v7 & 1) != 0 )
{
key ^= dword_202020[v13];
printf("key ^= dword_202020[%d];\n", v13);
v13 = ((_BYTE)v13 + 1) & 0xF;
}
v8 = v7 >> 1;
v3 = v8 & 3;
if ( v3 == 2 )
{
v15 = dword_202020[v13] & 0xAABBCCDD;
v13 = ((_BYTE)v13 + 1) & 0xF;
v9 = v8 >> 2;
}
else if ( v3 == 3 )
{
key += v14 + v15;
printf("key += %u;\n", v14 + v15);
v15 = 0;
v14 = 0;
v9 = v8 >> 2;
}
else
{
if ( v3 == 1 )
{
v14 = dword_202020[v13] | 0xABCDABCD;
v13 = ((_BYTE)v13 + 1) & 0xF;
}
v9 = v8 >> 2;
}
if ( (v9 & 1) != 0 ) {
key = ~key;
printf("key=~key;\n");
}
v10 = v9 >> 1;
if ( (v10 & 1) != 0 ) {
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
printf("key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);\n");
}
v11 = v10 >> 1;
v4 = v11 & 3;
if ( v4 == 2 )
{
v17 = dword_202020[v13] - 539034144;
v13 = ((_BYTE)v13 + 1) & 0xF;
v12 = v11 >> 2;
}
else if ( v4 == 3 )
{
key += v16 + v17;
printf("key += %u;\n", v16 + v17);
v17 = 0;
v16 = 0;
v12 = v11 >> 2;
}
else
{
if ( v4 == 1 )
{
v16 = 539034132 * dword_202020[v13];
v13 = ((_BYTE)v13 + 1) & 0xF;
}
v12 = v11 >> 2;
}
if ( (v12 & 1) != 0 ) {
key = key - (key & 7) - (key & 7) + 7;
printf("key = key - (key & 7) - (key & 7) + 7;\n");
}
}
return key;
}
输出序列
key ^= dword_202020[0];
key = ~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key ^= dword_202020[3];
key = key - (key & 7) - (key & 7) + 7;
key = ~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 1256252113;
key ^= dword_202020[6];
key += 636006435;
key += 0;
key ^= dword_202020[7];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[10];
key = ~key;
key += 1908961232;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[12];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 0;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[14];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[1];
key += 3791846473;
key = ~key;
key = key - (key & 7) - (key & 7) + 7;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key = ~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 3767795326;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[7];
key += 371756133;
key = ~key;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[8];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
很起来很nice,虽然也是很长不过流程更清晰了,现在呢手工逆感觉还是很难,比如最后一个方程想半天也没办法手工逆向(数学知识不扎实), 还是用angr躺吧, 因此我把序列写了个小程序,再用angr跑。
```
#include <stdio.h>
unsigned int dword_202020[16] = {
0x24DD20CF, 0x3E4F0354, 0x18B2E85F, 0x2F2CAFB8, 0x5810ADCB, 0x42F7FF85, 0x36E0D6C2, 0x5F3EF93F,
0x7F46E74A, 0x44DDC864, 0x64959795, 0x39413451, 0x5DC36C45, 0x62037E7E, 0x5AEA541F, 0x153F8FAC};
int calc(unsigned int key)
{
key ^= dword_202020[0];
key = ~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key ^= dword_202020[3];
key = key - (key & 7) - (key & 7) + 7;
key = ~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 1256252113;
key ^= dword_202020[6];
key += 636006435;
key += 0;
key ^= dword_202020[7];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[10];
key = ~key;
key += 1908961232;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[12];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 0;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[14];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[1];
key += 3791846473;
key = ~key;
key = key - (key & 7) - (key & 7) + 7;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key = ~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 3767795326;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[7];
key += 371756133;
key = ~key;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[8];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
return key;
}
int main(void) {
unsigned int x = 0;
unsigned int y = 0;
scanf("%u", &x);
y = calc(x);
if (y == 514763580) { //**比赛实际服务器返回的number**)
printf("right\n");
} else {
printf("wrong\n");
}
}
编译好gcc -o f fk.c,生成f, 拿出angr模版跑反向推导rnd
import angr
import angr
import claripy
import sys
def main():
print(" solving :", sys.argv[1])
p = angr.Project(sys.argv[1], load_options={"auto_load_libs": True})
'''
带参数:
flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(32)]
flag = claripy.Concat(*flag_chars)
#对于 c++ 的程序,如果调用了 c++ 的函数,使用 full_init_state
state = p.factory.full_init_state(add_options=angr.options.unicorn, args=[sys.argv[1], flag])
'''
state = p.factory.entry_state()
'''
添加约束
for c in flag_chars:
state.solver.add(c > 32)
state.solver.add(c < 127)
'''
# 在入口准备开始符号执行
sm = p.factory.simgr(state)
def good(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'right'.encode() in stdout_output # :boolean
def bad(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'wrong'.encode() in stdout_output # :boolean
'''
遍历可能的路径并找到成功的那条路径
最终想找到的路径是0x804868B,要避开的路径是0x804869E,这里可以写多个avoid,用[]
'''
sm.explore(find=good, avoid=bad)
if sm.found:
# print(sm.found[0].solver.eval(flag, cast_to=bytes)
# print(sm.found[0].posix.dumps(sys.stdout.fileno())
print(sm.found[0].posix.dumps(sys.stdin.fileno()))
else:
print("Not found")
print("Done")
if name == 'main':
main()
几秒就 出结果了 。
solving : f
WARNING | 2022-11-22 18:41:02,418 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
WARNING | 2022-11-22 18:41:02,437 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory with an unspecified value. This could indicate unwanted behavior.
WARNING | 2022-11-22 18:41:02,437 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2022-11-22 18:41:02,437 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2022-11-22 18:41:02,437 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILLUNCONSTRAINED{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2022-11-22 18:41:02,437 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILLUNCONSTRAINED{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2022-11-22 18:41:02,437 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff9c with 4 unconstrained bytes referenced from 0x401065 (_start+0x5 in f (0x1065))
b'1431956068'
Done
bingo:
└─# ./bits
Your number is 514763580
Your answer: 1431956068
Congrats!