#writeup# picoctf 2018 be-quick-or-be-dead 系列

picoctf

be-quick-or-be-dead-1

image-20200503010543744

程序逻辑比较简单:

  • 设定一个时间计数器,一旦某个函数运行超时就退出
  • 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

image-20200504102044209

典型的斐波拉契数列,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 typeC typePython type
c_bool_Boolbool (1)
c_charchar1-character string
c_wcharwchar_t1-character unicode string
c_bytecharint/long
c_ubyteunsigned charint/long
c_shortshortint/long
c_ushortunsigned shortint/long
c_intintint/long
c_uintunsigned intint/long
c_longlongint/long
c_ulongunsigned longint/long
c_longlong__int64 or long longint/long
c_ulonglongunsigned __int64 or unsigned long longint/long
c_floatfloatfloat
c_doubledoublefloat
c_longdoublelong doublefloat
c_char_pchar * (NUL terminated)string or None
c_wchar_pwchar_t * (NUL terminated)unicode or None
c_void_pvoid *int/long or None

本题需要将结果转换为 unsigned long,所以选择 c_uint 即可。注意一点,这里只做了类型转换,还需要提取value,否则系统会提示操作符类型不一致。

image-20200504103917657

接下来将 calculate_key 中的 call fib => mov eax, 5D023A41

image-20200504103544030

运行patch之后的程序,得到flag

image-20200504103701101

be-quick-or-be-dead-3

相比第二题,第三题仅仅是生成key的算法有区别:

image-20200504104141214

很明显,还是个递归算法导致运行超时。根据前面的经验,直接写出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);
}

原文地址:https://www.cnblogs.com/handt/p/12826212.html