IDA-IDC脚本编写语法

搞得我想买一本IDA Pro 权威指南来看看了……【有机会就整来看一看】

这个IDC用的比较少吧。下面的有些例子也不能正确的使用…

可以通过下面这个网址找到更多的IDC脚本

https://www.cnblogs.com/LyShark/p/13100048.html

file -> scriptcommand

file -> script file

image-20230515134709557

由于这里汉化了,就比较容易了…

也许是这个用的少吧。笔记就不写了,反正也是copy也没有意义,就不浪费时间了。


IDA Python

IDC 能做到的 IDApython 也能做到,所以我们直接学IDApython 吧。

IDAPython 由三个独立的模块组成,一个是idc , 它是封装IDA的IDC函数的兼容性模块,第二个模块是idautils,这是IDA里的一个高级实用功能模块;第三个模块是idaapi,它允许访问更加底层的数据。

image-20230515140702108

首先 , 我们知道我这个IDA7.7是基于这个python 3.8.10的版本..

要用pyhton 3 的语法 而不是 pyhton 2的语法

1
2
3
print ("hello")

而不是 print 'hello'
image-20230515140906787 image-20230515140941109

又来咯,估计还得学python…..


写一个题吧。从题目中体会。

题目来自CSAW CTF2017年的总决赛的一道逆向题目“Rabbithole”

题目和内容来自:

https://blog.csdn.net/ChuMeng1999/article/details/121228002

首先用die 看一下

image-20230515233617314

发现是一个64位的,而且是在一个linux下的一个文件。用kali 运行一下。

image-20230515233754891

“可以看到与一般的crackme差不多,就是要求我们输入,然后程序内部会有算法比对,如果不满足则报错”

现在用ida 64 位打开一下看一看。

image-20230515234008986

可以看到这个一打开就是flag 。 用F5 看一下伪代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rcx
char *v4; // rdi
const char *v5; // rdi
void **v6; // r8
__int64 v7; // rdx
char v8; // al
__int64 v9; // rdx
char v10; // cl
char v11; // cl
char s[128]; // [rsp+8h] [rbp-90h] BYREF
unsigned __int64 v14; // [rsp+88h] [rbp-10h]

v3 = 32LL;
v14 = __readfsqword(0x28u);
v4 = s;
while ( v3 )
{
*(_DWORD *)v4 = 0;
v4 += 4;
--v3;
}
puts("Hey, what's the flag?");
v5 = "Oh no, something went quite wrong!";
if ( !fgets(s, 127, _bss_start) )
goto LABEL_10;
*strchr(s, 10) = 0;
v5 = "bad length much sad :(";
if ( ~(strlen(s) + 1) != -61LL )
goto LABEL_10;
v6 = &roots;
v7 = 0LL;
do
{
v8 = check_value((unsigned __int8)s[v7], v6[v7]);
v7 = v9 + 1;
v11 = v8 & v10;
}
while ( v7 != 59 );
v5 = "You didn't get it, much sadness :(";
if ( v11 )
{
puts("You got it! correct! awesome!");
return 0;
}
else
{
LABEL_10:
puts(v5);
return 1;
}
}

看到这个主要的函数就是 check_value

这个check_value 有2个参数..s[v7] 和 v6[v7]

image-20230515234812909

这个地方的v7 = 0LL 其实就是 0

1
2
3
4
5
6
7
8
9
C/C++中各種不同型別的常數0,以不同的後綴表示:

0 //int
0L //long
0LL //long long
0UL //unsigned long
0.0 //double
0.0f //float
0.0L //long double

由于下面的 v7!=59 说明了 这个while 循环循环 while 59次。

然后这里的这个v6 = &roots; 说明这个roots 是一个地址,把这个地址点过去看一看。

image-20230515235142612

因为,我们学过这个汇编语言中这个 offset 大概知道这个其实是一个偏移地址,也就是指针【差不多吧】

所以这个roots[] 数组 , 就是一个指针数组。。

然后去看看 check_value 这个函数的伪代码

1
2
3
4
5
6
7
8
9
10
11
char __fastcall check_value(unsigned __int8 a1, __int64 a2)
{
while ( !*(_BYTE *)a2 )
{
if ( *(_BYTE *)(a2 + 8) > a1 || *(_BYTE *)(a2 + 9) <= a1 )
a2 = *(_QWORD *)(a2 + 24);
else
a2 = *(_QWORD *)(a2 + 16);
}
return *(_BYTE *)(a2 + 8);
}

第一个参数就是s[v7] 第二个参数是 v6[v7]

也就是说 第一个参数:s[v11]是用户输入input,第二个参数: (_int64)v10[v11])是roots。

至于为什么这个s[v11]是用户输入input,是因为fgets(s, 127, _bss_start)【应该是的吧。我是这样理解的】

这个fget()函数和get函数差不多。更牛皮一点罢了。【参数更多】

1
2
3
4
if ( *(_BYTE *)(a2 + 8) > a1 || *(_BYTE *)(a2 + 9) <= a1 )
a2 = *(_QWORD *)(a2 + 24);
else
a2 = *(_QWORD *)(a2 + 16);

大概看一下这个代码。估计也就是算法,把不同情况下的东西进行不同的操作。

首先是roots指针数组的起始位置,因为node的地址都是在其上进行偏移计算的。

可以看到其值为28f900:

然后需要写两个函数,一个用于遍历,一个用于校验。
前面已经分析过遍历的次数是59次,10进制的59就是16进制的0x3b。
使用ida_bytes.get_qword来取得node的地址:

接下来就是在ascii字符中遍历了,我们知道ascii码中32-126是字符,即需要在0x20到0x7f的范围内遍历。针对每一个可能的字符都是用校验函数来校验,如果返回为真,则将其添加进flag,然后break循环,然后在roots数组中偏移8取下一个node,再针对下一个位置的字符再次遍历。

所以校验的函数应该这么写:

1
2
3
4
5
6
7
8
9
for i in range(0,0x3b) :
node = get_qword(roots) #read the node's address
c = 0x20
while c <= 0x7f :
if traverse_find(node,c) == 1:
flag+= chr(c)
break
c+= 1
roots += 8 #next root node

这里的traverse_find 函数就是我们等会儿要写的进行校验的函数。

Traverse_find的逻辑其实就是check_value伪码的逻辑,只要注意两点:
1.逻辑,如下图,C伪码中使用的是||逻辑表达式,比如A||B,意思是满足A,B中的任一个就为真。
简化的表示:
下图中的A就是*(_BYTE )(a2 + 8),B就是(_BYTE )(a2 + 9),C就是a2 =(_QWORD )(a2 + 24),D就是a2 =(_QWORD *)(a2 + 16)。
伪码的逻辑是满足A>a1或B<=a1(a1就是input),则执行C,否则执行D;
我在python脚本里使用的是and,所以判断的语句用的是:
A <= c and B > c(这里c就是input)。
如果满足条件,则执行D,否则执行C;
可以看到python和C伪码的逻辑是一样的,不过用的逻辑运算符不一样而已,这里一定要注意,不要混淆了。
2.进制转换:
比如下图中第6行,第8行的两个分支:

image-20230516000437375

他们在原基础上继续偏移24,16;转换成16进制分别是0x18,0x10。

最后写出来的IDA python 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from idaapi import *
from idc import * # 导入包

def traverse_find(node, c):
if get_byte(node) == 1:
r = get_byte(node + 8) & 0x1
if r == 1:
return 1
else:
return 0

b1 = get_byte(node + 8)
b2 = get_byte(node + 9)
if c >= b1 and c < b2: # 这里相当于一个满足了条件后的回调函数
return traverse_find(get_qword(node + 0x10), c) # +16
else:
return traverse_find(get_qword(node + 0x18), c) # +24

# ---------------定义check_value伪码的逻辑------------------------
flag = ''
roots = 0x00000028F900
# --------------常量的定义---------------------------------
for i in range(0, 0x3b): # 进行59次循环
node = get_qword(roots)
c = 0x20 # c=32
while c <= 0x7f: # 32~127个ascii码,可见字符,一个一个比对。
if traverse_find(node, c) == 1:
flag += chr(c)
break
c += 1
roots += 8

# --------------------进行遍历的函数------------------------------------
print(flag)

这里,我用的IDA用的是这个python3.8.10的版本的。所以需要用的是python3 的语法,也就是这个print 函数的写法不一样。

如果是python 2 就直接 print flag

image-20230515233442128

这个题,是一个关于树的题。我自己来写一下呢?

最后我知道了几个WP

https://anee.me/rabbithole-csaw-ctf-2017-finals-f7d70f3726f3