网安导实验三:“逆向工程、漏洞挖掘与利用”教程
逆向工程
逆向工程是指对软件、硬件、芯片等产品进行逆向分析,以获取其设计原理、技术规格、实现方法等技术信息的过程。逆向工程是一种合法的技术手段,但在实际应用中,逆向工程也被用于非法目的,如破解软件、盗版等。逆向工程的主要内容包括逆向分析、逆向设计、逆向仿制等。
网安导实验只涉及软件分析。汇编与 GDB
已在实验二教程中介绍,这里将介绍使用 GDB
进行逆向工程的方法。例如:我们现在有一个程序,它的源代码如下:
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 #include <stdio.h> #include <stdlib.h> #include <time.h> #define MAX_SIZE 100000 void obfuscate (char *input) { int random_factor[100 ][3 ]; srand(0LL ); for (int j = 0 ; j < 100 ; j++) { for (int k = 0 ; k < 3 ; k++) { random_factor[j][k] = rand(); } } for (int i = 0 ; input[i] != '\0' ; i++) { input[i] = abs ((input[(input[i + 1 ] == '\0' ) ? i + 1 : 0 ] ^ random_factor[i][0 ] ^ (input[i] + random_factor[i][1 ]) % (256 ^ random_factor[i][2 ])) + random_factor[i][3 ]) % 95 + 32 ; } } int main () { char input[MAX_SIZE]; scanf ("%99s" , input); printf ("You input: %s\n" , input); obfuscate(input); printf ("Processed input: %s\n" , input); return 0 ; }
现在,我们获得了某个输入的输出,想要找到原始输入。
例子 1 2 3 You input: AAAAB3NzaC1yc2EAAAADAQABAAABgQCnZfmfODcOKr9lrOtKO7rl+KdgPGenju+xfPiiKLT2MbnKUAzn7oXJc3cv50b8OT4vOI4 Processed input: cUw)TJLdO8Ezqi;P?w{5Jaj2^i91UwLRS403dqm0`A* 7iX;5dF DrAM=vzj/gZ^yI;"\+{'Gt-9CrhCej.Ar|#5n!XF'oLg[q? (假设无输入,推演原始输入)
在此代码中,由于使用了
srand(0LL),所以随机数将是一个固定的序列,而且混淆后的字符串的第
i 位与 i 和 i+1
位有关。即除了最后一位,混淆后的序列的每一位都与原序列的两位有关。然而,其逆向操作代码写起来十分的烦人,需要考虑许多东西;在更多情况下只有二进制,并且文件经过混淆和膨胀难以分析逻辑。
这时,我们可以使用 GDB Python
脚本来自动化调试。其具体思路为,在输入字符串后设置还原点,并在执行
obfuscate
函数后设置断点,每次逐位检测混淆后字符串是否满足条件,逐位增加还原字符串。
script.py 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 import gdbtarget = "cUw)TJLdO8Ezqi;P?w{5Jaj2^i91UwLRS403dqm0`A* 7iX;5dF DrAM=vzj/gZ^yI;\"\+{'Gt-9CrhCej.Ar|#5n!XF'oLg[q?" alphabet = ascii .printable class ObfuscateBreakpoint (gdb.Breakpoint): def __init__ (self, spec, target ): super ().__init__(spec) self .target = target self .current_index = 0 self .current_guess = [' ' ] * len (target) def stop (self ): frame = gdb.newest_frame() input_addr = frame.read_var('input' ) input_str = input_addr.string() if input_str == self .target: print ("Found input:" , '' .join(self .current_guess)) gdb.execute("quit" ) return True for char in alphabet: self .current_guess[self .current_index] = char gdb.execute(f"set input[{self.current_index} ] = '{char} '" ) gdb.execute("continue" ) input_str = input_addr.string() if input_str[self .current_index] == self .target[self .current_index]: self .current_index += 1 break return False def main (): gdb.execute("file a.out" ) gdb.execute("b obfuscate" ) gdb.execute("r" ) ObfuscateBreakpoint("*obfuscate" , target) gdb.execute("continue" ) if __name__ == "__main__" : main()
漏洞挖掘与利用
漏洞挖掘
我们这里介绍最简单的几种漏洞。
栈溢出
栈溢出是一种常见的漏洞,其原理是当程序向栈中写入数据时,如果写入的数据超出了栈的大小,就会覆盖栈中的其他数据,从而导致程序崩溃或执行恶意代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <stdio.h> void target () { printf ("You have reached the target function!\n" ); } void vuln () { char buf[10 ]; gets(buf); printf ("You entered: %s\n" , buf); } int main () { vuln(); return 0 ; }
我们只要在执行 vuln 函数时覆盖其返回地址为
target 函数的地址,就可以执行 target
函数。
字符串格式化漏洞
字符串格式化漏洞是一种常见的漏洞,其原理是当程序使用
printf
等函数输出字符串时,如果字符串中包含格式化字符串,就会导致程序崩溃或执行恶意代码。
例如,对这样的代码:
1 printf ("%d %d %d %d %d %d %d %d %d %d\n" );
若此代码中的格式化字符串与其后的参数一一对应,那么程序会先将全部参数压栈,然后按照格式化字符串的要求输出参数。而在这里,这个代码会输出栈中的内容,从而泄露栈中的数据。
在实际情况中,由疏忽导致的格式化字符串与参数不匹配的情况并不常见,因为这样的代码会在编译时提示错误。而这是一个更为常见的情况:
1 2 3 4 char buf[100 ];gets(buf); trackle(buf); printf (buf);
若不对 buf
进行检查,那么用户输入的字符串中就可能包含格式化字符串,从而导致漏洞。