be-quick-or-be-dead-1
程序逻辑比较简单:
- 设定一个时间计数器,一旦某个函数运行超时就退出
- get_key :生成一个固定的key值
- print_flag : 将 flag 打印出来。因为get_key 超时,导致无法执行到该函数
- 这题如果爆破,估计直接能出结果(将set_timer直接nop掉。)
看别人的wp,patch可以将超时设置到8s,或者将set_timer 直接retn
从IDA 获取数据
flag = 0x0000000000601080
ret = []
for b in get_bytes(flag, 59): // 返回的是str
ret.append(hex(ord(b)))
print ret
得到16进制的数组
['0x52', '0x4b', '0x9e', '0x8a', '0x60', '0x76', '0xbb', '0x9e', '0x53', '0x4a', '0x84', '0xba', '0x47', '0x4d', '0x89', '0x8d', '0x43', '0x50', '0xa2', '0x81', '0x48', '0x4b', '0x93', '0x82', '0x77', '0x57', '0x93', '0x8b', '0x4c', '0x41', '0x98', '0x96', '0x59', '0x43', '0x8f', '0x9c', '0x74', '0x41', '0x92', '0x88', '0x5c', '0x57', '0x89', '0x84', '0x59', '0x4b', '0x92', '0x8b', '0x71', '0x46', '0xcd', '0x86', '0x19', '0x43', '0x9c', '0x86', '0x55', '0x5f', '0x0']
核心加密算法
// key = 0xE5FD2222LL
__int64 __fastcall decrypt_flag(int key)
{
__int64 result; // rax
int v2; // [rsp+0h] [rbp-14h]
unsigned int i; // [rsp+10h] [rbp-4h]
v2 = key;
for ( i = 0; ; ++i )
{
result = i;
if ( i > 0x39 )
break;
flag[i] ^= *((_BYTE *)&v2 + (signed int)i % 4);
if ( (signed int)i % 4 == 3 )
++v2;
}
return result;
}
核心就在这句话:
flag[i] ^= *((_BYTE *)&v2 + (signed int)i % 4);
v2 是一个int类型:
- &v2: 取int的地址,也就是一个指向v2的整型指针
- (byte *)&v2 :将 int 类型指针转换为 byte 类型
- (byte *)&v2 + i % 4:每次取一个 byte
- 所以整句的意思是,每次取 key 的一个byte出来,进行xor
- 后面 i%4 == 3,也就是四次就把 key + 1
写出解密代码。
flag = ['0x52', '0x4b', '0x9e', '0x8a', '0x60', '0x76', '0xbb', '0x9e', '0x53', '0x4a', '0x84', '0xba', '0x47', '0x4d', '0x89', '0x8d', '0x43', '0x50', '0xa2', '0x81', '0x48', '0x4b', '0x93', '0x82', '0x77', '0x57', '0x93', '0x8b', '0x4c', '0x41', '0x98', '0x96', '0x59', '0x43', '0x8f', '0x9c', '0x74', '0x41', '0x92', '0x88', '0x5c', '0x57', '0x89', '0x84', '0x59', '0x4b', '0x92', '0x8b', '0x71', '0x46', '0xcd', '0x86', '0x19', '0x43', '0x9c', '0x86', '0x55', '0x5f', '0x0']
key = ['0x22', '0x22', '0xFD', '0xE5'] #转换为数组
ret = []
for i in range(0x3a):
c = int(flag[i], 16)
c ^= int(key[i % 4], 16)
ret.append(chr(c))
print key
if i % 4 == 3:
key[0] = hex(int(key[0], 16) + 1)
print ''.join(ret)
得到结果:
picoCTF{why_bother_doing_unnecessary_computation_d0c6aace}
be-quick-or-be-dead-2
相比于第一题,有两点变化:
- set_timer 超时默认从1s变为8s
- get_key 中使用了 fib 函数来生成key,而不是直接返回
尝试着用python递归实现fib,发现直接超出了递归的最大限制次数:
# fib递归的算法会严重超时
def fib(a1):
v1 = 0
v3 = 0
if(a1 > 1):
v1 = fib(a1 - 1)
v3 = v1 + fib(a1 - 2)
else:
v3 = a1
return v3
提示 max recursion depth exceeded
典型的斐波拉契数列,wiki 一下就知道,有多种算法实现:
- 递归实现
- 非递归(利用数组,循环即可)
- 快速矩阵幂 (涉及到矩阵乘法)
非递归的方法最简单,自己实现一个:
from ctypes import *
def fib2(n):
input= {}
input[0] = 0
input[1] = 1
for i in range(2, n+1):
input[i] = input[i-1] + input[i-2]
input[i] = c_uint(input[i]).value
return hex(input[n])
个人遇到的最大问题是python中的变量,如何转换为 c 语言类型。
使用 ctype 类,文档中给出了各种数据类型的关键字:
ctypes type | C type | Python type |
---|---|---|
c_bool | _Bool | bool (1) |
c_char | char | 1-character string |
c_wchar | wchar_t | 1-character unicode string |
c_byte | char | int/long |
c_ubyte | unsigned char | int/long |
c_short | short | int/long |
c_ushort | unsigned short | int/long |
c_int | int | int/long |
c_uint | unsigned int | int/long |
c_long | long | int/long |
c_ulong | unsigned long | int/long |
c_longlong | __int64 or long long | int/long |
c_ulonglong | unsigned __int64 or unsigned long long | int/long |
c_float | float | float |
c_double | double | float |
c_longdouble | long double | float |
c_char_p | char * (NUL terminated) | string or None |
c_wchar_p | wchar_t * (NUL terminated) | unicode or None |
c_void_p | void * | int/long or None |
本题需要将结果转换为 unsigned long,所以选择 c_uint 即可。注意一点,这里只做了类型转换,还需要提取value,否则系统会提示操作符类型不一致。
接下来将 calculate_key 中的 call fib => mov eax, 5D023A41
运行patch之后的程序,得到flag
be-quick-or-be-dead-3
相比第二题,第三题仅仅是生成key的算法有区别:
很明显,还是个递归算法导致运行超时。根据前面的经验,直接写出T(N)的算法:
from ctypes import *
def quick3(n):
input = {}
for i in range(5):
input[i] = i*i + 9029
for i in range(5, n + 1):
v2 = input[i-1] - input[i-2]
v4 = input[i-3] - input[i-4] + v2
input[i] = v4 + 4660 * input[i-5]
input[i] = c_uint(input[i]).value
return hex(input[n])
print quick3(0x186B5) ##0x221d8eea
在同样的位置进行patch,运行patch之后的程序,得到flag:
Be Quick Or Be Dead 3
=====================
Calculating key...
Done calculating key
Printing flag:
picoCTF{dynamic_pr0gramming_ftw_a0b0b7f8}
至此,三道题目完成。
小结
通过三个习题新学习到以下内容
- 如何利用 ida 的 keypatch 插件对程序进行patch
- 如何利用ctypes进行数据类型转换
- 加入 #encoding=utf-8 后,才能写中文注释
- 程序的超时机制。例如这里是 3s 之后 alarm,并调用 alarm_handler 函数
unsigned int set_timer()
{
if ( __sysv_signal(14, alarm_handler) == (__sighandler_t)-1LL )
{
printf(
" Something went terribly wrong. Please contact the admins with "be-quick-or-be-dead-3.c:%d". ",
59LL);
exit(0);
}
return alarm(3u);
}