因为疫情,本来的北京国际会议中心的总决赛改成了线上,模式也从原来的awdplus改成了CTF解题赛,每个方向2个题。
以下是此次比赛中的两道逆向题,其中第二题因为在题目中嵌入了一个rsa密码学问题,比赛时也是一直卡在这里。
abc 题目中加了下面这种混淆,就是把要用的字符串或数据运算解密出来:
然后很多的同一类型花指令,简单用idapython去除一下就好,主要看看函数大体就可以了,调试就很快。
1 2 3 4 5 6 7 8 9 10 11 12 from ida_bytes import *addr = 0x400640 while addr <= 0x401592 : if get_byte(addr) == 0xe8 and get_byte(addr+1 ) == 0x0 : for i in range(17 ): if i not in [10 , 11 , 12 , 13 , 14 ]: patch_byte(addr+i, 0x90 ) elif i == 10 : patch_byte(addr+i, 0xe8 ) addr += 1 print('*' *100 )
对输入下内存断点,断到处理输入的核心函数:
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 void __fastcall __noreturn sub_401308 (__int64 a1, __int64 a2) { unsigned __int64 v2; __int64 v3; char *v4; int v5; if ( *(int *)(v3 - 16 ) >= v2 ) { *(_BYTE *)(v3 - 128 ) = __ROR4__(__ROL4__(-1109410466 , 15 ) ^ 0xDEADBEEF , 10 ); *(_BYTE *)(v3 - 127 ) = __ROR4__(__ROL4__(-1109410466 , 15 ) ^ 0xDEADBEEF , 10 ); *(_BYTE *)(v3 - 126 ) = __ROR4__(__ROL4__(-2048934564 , 15 ) ^ 0xDEADBEEF , 10 ); for ( *(_DWORD *)(v3 - 16 ) = __ROR4__(__ROL4__(2111815003 , 15 ) ^ 0xDEADBEEF , 10 ); (unsigned __int64)*(int *)(v3 - 16 ) < 3 ; *(_DWORD *)(v3 - 16 ) = *(_DWORD *)(v3 - 16 ) - 118 + 119 ) { sub_4014B2(); *(_BYTE *)(*(int *)(v3 - 16 ) + v3 - 128 ) = ~*(_BYTE *)(*(int *)(v3 - 16 ) + v3 - 128 ); } sub_401718(); } else { ++*(_DWORD *)(v3 - 16 ); sub_40133C(); v5 = *v4; switch ( v5 ) { case '#' : sub_400A65(); break ; case '$' : sub_40085B(a1, a2); break ; case '%' : sub_400B6D(); break ; case '@' : sub_40095D(a1, a2); break ; default : sub_4013EE(); sub_400C6F(a1, a2); break ; } } }
调试可以知道,我们的输入要对应case语句的4个参数。
看到第一个case对应的处理函数:就是对box进行数据换位置。
再看最后的判断条件:
从上可以抽象出来是一个4*4的华容道游戏。
开始我是写的一个dfs来搜索,因为限定条件比较少,很花时间,算法太菜了:
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 #include <stdio.h> #include <stdlib.h> #include <string.h> char enc[] = {1 , 0x0A , 2 , 3 , 5 , 0x0D , 6 , 4 , 9 , -1 , 7 , 0x0B , 0x0E , 0x0F , 0x0C , 8 };char d[] = "#$%@" ;char char flag[200 ];void fun1 (char *a, int index) { char tmp = a[index]; a[index] = a[index+1 ]; a[index+1 ] = tmp; } void fun2 (char *a, int index) { char tmp = a[index]; a[index] = a[index-4 ]; a[index-4 ] = tmp; } void fun3 (char *a, int index) { char tmp = a[index]; a[index] = a[index-1 ]; a[index-1 ] = tmp; } void fun4 (char *a, int index) { char tmp = a[index]; a[index] = a[index+4 ]; a[index+4 ] = tmp; } int find_index (char *a) { int index = 0 ; for (int i = 0 ; i < 16 ; i++) { if (a[i] == -1 ) { index = i; break ; } } return index; } int check (char *a) { for (int i = 0 ; i < 15 ; i++) { if (a[i] != i+1 ) return 0 ; } return 1 ; } void dfs (char *a, int step ) { if (check(a)) { printf ("found: %s" , flag); } if (step > 16 ) return ; puts (flag); for (int i = 0 ; i < 4 ; i++) { char *tmp = (char *)alloca(16 ); memcpy (tmp, enc, 16 ); int index = find_index(enc); if ((i == 0 && (index%4 ) >= 3 ) || (i == 1 && (index/4 ) <= 0 ) || (i == 2 && (index%4 ) <= 0 ) || (i == 3 && (index/4 ) >= 3 )) continue ; if (i == 0 ) { flag[step ] = d[i]; fun1(enc, index); } else if (i == 1 ) { flag[step ] = d[i]; fun2(enc, index); } else if (i == 2 ) { flag[step ] = d[i]; fun3(enc, index); } else if (i == 3 ) { flag[step ] = d[i]; fun4(enc, index); } dfs(enc, step +1 ); memcpy (enc, tmp, 16 ); } } int main (void ) { for (int i = 0 ; i < 16 ; i++) { printf ("%d, " , enc[i]); } dfs(enc, 0 ); }
最后找到github上一个解华容道的项目https://github.com/Dpxx/Klotski-15puzzles
快的离谱:
最后转化一下:$$##$$%%@@##$$%%@@
en 这个题目最后也只有2解。我比赛时一直卡在rsa算法上,没想到逆向题还真就嵌入一个rsa的题。
通过这个题学习基本的pyc文件,对对抗混淆做好基础。
看题目的给的密文,加上看pyc文件中的字符串可以猜测是rsa加密。
尝试使用uncompyle6对该pyc文件反编译,报错。
这个问题了解过的原因基本上就是对字节码加了混淆,如下面举例这个。
1 2 3 4 5 6 7 | 1 0 JUMP_ABSOLUTE [71 06 00 ] 6 | 3 LOAD_CONST [64 FF FF] 65535 (FAKE!) | >> 6 LOAD_CONST [64 00 00 ] 0 (Hello World) | 9 PRINT_ITEM [47 -- --] | 10 PRINT_NEWLINE [48 -- --] | 11 LOAD_CONST [64 01 00 ] 1 (None ) | 14 RETURN_VALUE [53 -- --]
我们看到第二条指令3 LOAD_CONST [64 FF FF] 65535 ,对于如何解析这个指令我们可以看到这两篇官方文档:https://docs.python.org/2/library/dis.html https://github.com/Python/cpython/blob/2.7/Include/opcode.h
64 FF FF 是实际在pyc文件中存放的字节码,也是这一条指令的组成,其中64是表示操作指令,FF FF表示所带参数。(在字节码对象的co_code中分为无参数指令(1字节)和有参数指令(3字节))。
所以第二条指令的意思就是加载代码对象的常量表的第65535项到栈顶,常量表是存储了一个PyCodeObject中的常量的数组。而这里的index是65535,显然是超过了这个数组的大小。报的错也是数组越界。
但是第一条指令是一个绝对跳转,它跳到了编号为6的位置(也就是用>>符号标明了的)进而跳过他后面一条错误指令(3 LOAD_CONST)的执行。
所以实际执行的指令其实是这样:
1 2 3 4 5 | 6 LOAD_CONST [64 00 00 ] 0 (Hello World) | 9 PRINT_ITEM [47 -- --] | 10 PRINT_NEWLINE [48 -- --] | 11 LOAD_CONST [64 01 00 ] 1 (None) | 14 RETURN_VALUE [53 -- --]
我们实验一下如何看一个PyCodeObject中常量的个数,如下所示。
1 2 3 4 5 6 7 8 9 10 11 >>> def p () :... a = 12 ... b = 13 ... >>> >>> p.__code__<code object p at 0x000002509037CEA0 , file "<stdin>" , line 1 > >>> p = p.__code__>>> p.co_consts(None , 12 , 13 ) >>>
这里提到了PyCodeObject,那什么是PyCodeObject呢。
从code.h头文件中找到它的结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 typedef struct { PyObject_HEAD int co_argcount; int co_nlocals; int co_stacksize; int co_flags; PyObject *co_code; PyObject *co_consts; PyObject *co_names; PyObject *co_varnames; PyObject *co_freevars; PyObject *co_cellvars; PyObject *co_filename; PyObject *co_name; int co_firstlineno; PyObject *co_lnotab; void *co_zombieframe; PyObject *co_weakreflist; } PyCodeObject;
在我们写好python代码,执行的时候它是先编译成pyc字节码,再通过解释执行pyc字节码。pyc文件PyCodeObject对象的持久化保存方式。 它按照python代码中代码的作用域来划分成PyCodeObject的。
写了如下简单的py代码来实验:
它就应该是有3个PyCodeObject的,一个整体的module,和funa,q
1 2 3 4 5 6 7 8 9 10 11 12 13 def funa () : a = 1 print(a) class q () : a = 2 def funb (self) : b = 3 print(b) funa() q().funb()
编译:
1 python2 -m py_compile 1.py
载入生成的1.pyc文件到010editor使用模板查看:一个大的module对象的object consts包括了funa,q的PyCodeObject对象。
整体上来说这个pyc文件格式是很简单的,很多结构都是:类型+长度+内容 的格式。
回到做题的时候来。上面是用绝对跳转跳过错误代码的混淆字节码方式,其它还有虚假分支,重叠之类等,他们的原理其实都是差不多的,和x86平台下加的一般花指令极其类似。
然后回到这个题目上来,因为使用uncomple6没有反编译成功,我去尝试了使用marshal 来反序列化pyc文件中的PyCodeObject,再使用diss模块进行反汇编。
注意:
pyc文件由三部分组成magic number + 源代码文件信息 + PyCodeObject
不同版本的python编译的pyc文件的magic number + 源代码文件信息 都是不同的。
python2.7中除了4字节的magic number 还有4字节的时间戳,所以PyCodeObject 在第8个字节后面。
python3.5与python3.6在python2.7的基础上还多了4字节的源文件大小,所以PyCodeObject 在12字节后面。
python3.7以上还增加了校验hash值,当4-8字节为全0时表示没有启用hash校验,其后的8-16字节表示时间戳与源文件大小;但当4-8字节为0100 0000或者0300 0000时,8-16字节表示文件的hash值。所以PyCodeObject 在16字节后面。
我们来看一下这个pyc文件头部信息:420D0D0A 表示python版本为3.7,它的4-8字节全0表明后面的8-16字节为时间戳与源文件大小。
从上可以知道这个文件的PyCodeObject在16字节后面,因此下面的代码中使用的f.seek(16)
1 2 3 4 5 6 import marshal, diswith open('en.pyc' , 'rb' ) as f: f.seek(16 ) code = marshal.load(f) f.close() print(dis.dis(code))
可是在使用marshal进行反序列化时就出现了问题。
1 2 3 4 Traceback (most recent call last): File "1.py" , line 4, in <module> code = marshal.load(f) ValueError: bad marshal data (unknown type code)
这是python版本不匹配出现的问题,该pyc文件是python3.7编译的,而我的环境是python3.8。
最后使用pycdas 成功反汇编了一部分,看到没有反汇编成功的提示:
1 Error disassembling en.pyc: vector::_M_range_check: __n (which is 63) >= this->size() (which is 14)
根据提示把对应的数字改小就可以了。
最后得到全部的反汇编结果:可能部分指令是不正确的,因为我上面对pyc文件的修改也是大概来的。
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 en.pyc (Python 3.7 ) [Code] File Name: /somewhere/encrypt.py Object Name: <module> Arg Count: 0 KW Only Arg Count: 0 Locals: 0 Stack Size: 3 Flags: 0x00000040 (CO_NOFREE) [Names] 'gmpy2' 'g' 'Crypto.Util.number' 'long_to_bytes' 'bytes_to_long' 'random' 'gen_num' 'gen_prime' 'po' 'e2' '__name__' 'sys' 'len' 'argv' 'encode' 'base64' 'B' 'b64decode' 'flag' [Var Names] [Free Vars] [Cell Vars] [Constants] 0 None ( 'long_to_bytes' 'bytes_to_long' ) [Code] File Name: /somewhere/encrypt.py Object Name: gen_num Arg Count: 1 KW Only Arg Count: 0 Locals: 3 Stack Size: 6 Flags: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE) [Names] 'range' 'random' 'choice' [Var Names] 'n_bits' 'res' 'i' [Free Vars] [Cell Vars] [Constants] None 0 1 [Disassembly] 0 LOAD_CONST 1 : 0 2 STORE_FAST 1 : res 4 SETUP_LOOP 50 (to 56 ) 6 LOAD_GLOBAL 0 : range 8 LOAD_FAST 0 : n_bits 10 CALL_FUNCTION 1 12 GET_ITER 14 FOR_ITER 38 (to 54 ) 16 STORE_FAST 2 : i 18 LOAD_FAST 1 : res 20 LOAD_CONST 1 : 0 22 COMPARE_OP 3 (!=) 24 POP_JUMP_IF_FALSE 34 26 LOAD_FAST 1 : res 28 LOAD_CONST 2 : 1 30 INPLACE_LSHIFT 32 STORE_FAST 1 : res 34 LOAD_FAST 1 : res 36 LOAD_GLOBAL 1 : random 38 LOAD_METHOD 2 : choice 40 LOAD_CONST 1 : 0 42 LOAD_CONST 2 : 1 44 BUILD_LIST 2 46 CALL_METHOD 1 48 UNARY_POSITIVE 50 STORE_FAST 1 : res 52 JUMP_ABSOLUTE 14 54 POP_BLOCK 56 LOAD_FAST 1 : res 58 RETURN_VALUE 'gen_num' [Code] File Name: /somewhere/encrypt.py Object Name: gen_prime Arg Count: 1 KW Only Arg Count: 0 Locals: 3 Stack Size: 3 Flags: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE) [Names] 'gen_num' 'g' 'is_prime' [Var Names] 'n_bits' 'res' 'b' [Free Vars] [Cell Vars] [Constants] None 1 0 [Disassembly] 0 LOAD_GLOBAL 0 : gen_num 2 LOAD_FAST 0 : n_bits 4 CALL_FUNCTION 1 6 STORE_FAST 1 : res 8 SETUP_LOOP 54 (to 64 ) 10 LOAD_GLOBAL 1 : g 12 LOAD_METHOD 2 : is_prime 14 LOAD_FAST 1 : res 16 CALL_METHOD 1 18 POP_JUMP_IF_TRUE 62 20 LOAD_CONST 1 : 1 22 STORE_FAST 2 : b 24 SETUP_LOOP 34 (to 60 ) 26 LOAD_FAST 2 : b 28 LOAD_CONST 2 : 0 30 COMPARE_OP 3 (!=) 32 POP_JUMP_IF_FALSE 58 34 LOAD_FAST 1 : res 36 LOAD_FAST 2 : b 38 BINARY_XOR 40 LOAD_FAST 1 : res 42 LOAD_FAST 2 : b 44 BINARY_AND 46 LOAD_CONST 1 : 1 48 BINARY_LSHIFT 50 ROT_TWO 52 STORE_FAST 1 : res 54 STORE_FAST 2 : b 56 JUMP_ABSOLUTE 26 58 POP_BLOCK 60 JUMP_ABSOLUTE 10 62 POP_BLOCK 64 LOAD_FAST 1 : res 66 RETURN_VALUE 'gen_prime' [Code] File Name: /somewhere/encrypt.py Object Name: po Arg Count: 3 KW Only Arg Count: 0 Locals: 5 Stack Size: 2 Flags: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE) [Names] [Var Names] 'a' 'b' 'n' 'res' 'aa' [Free Vars] [Cell Vars] [Constants] None 1 0 [Disassembly] 0 LOAD_CONST 1 : 1 2 STORE_FAST 3 : res 4 LOAD_FAST 0 : a 6 STORE_FAST 4 : aa 8 SETUP_LOOP 52 (to 62 ) 10 LOAD_FAST 1 : b 12 LOAD_CONST 2 : 0 14 COMPARE_OP 3 (!=) 16 POP_JUMP_IF_FALSE 60 18 LOAD_FAST 1 : b 20 LOAD_CONST 1 : 1 22 BINARY_AND 24 LOAD_CONST 1 : 1 26 COMPARE_OP 2 (==) 28 POP_JUMP_IF_FALSE 42 30 LOAD_FAST 3 : res 32 LOAD_FAST 4 : aa 34 BINARY_MULTIPLY 36 LOAD_FAST 2 : n 38 BINARY_MODULO 40 STORE_FAST 3 : res 42 LOAD_FAST 4 : aa 44 LOAD_FAST 4 : aa 46 INPLACE_MULTIPLY 48 STORE_FAST 4 : aa 50 LOAD_FAST 1 : b 52 LOAD_CONST 1 : 1 54 INPLACE_RSHIFT 56 STORE_FAST 1 : b 58 JUMP_ABSOLUTE 10 60 POP_BLOCK 62 LOAD_FAST 3 : res 64 LOAD_FAST 2 : n 66 BINARY_MODULO 68 RETURN_VALUE 'po' [Code] File Name: /somewhere/encrypt.py Object Name: e2 Arg Count: 1 KW Only Arg Count: 0 Locals: 14 Stack Size: 5 Flags: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE) [Names] 'type' 'bytes' 'AssertionError' 'len' 'bytes_to_long' 'gen_prime' 'g' 'next_prime' 'long_to_bytes' 'pow' 'print' 'str' 'digits' 'hex' [Var Names] 'm' 'l' 'm1' 'm2' 'p' 'q' 'pp' 'qq' 'e' 'ee' 'n' 'nn' 'c1' 'c2' [Free Vars] [Cell Vars] [Constants] None 512 2 1024 2333 65535 [Disassembly] 0 LOAD_GLOBAL 0 : type 2 LOAD_FAST 0 : m 4 CALL_FUNCTION 1 6 LOAD_GLOBAL 1 : bytes 8 COMPARE_OP 2 (==) 10 POP_JUMP_IF_TRUE 32 12 LOAD_GLOBAL 2 : AssertionError 14 RAISE_VARARGS 1 16 LOAD_GLOBAL 11 : str 18 LOAD_FAST 11 : nn 20 CALL_FUNCTION 31 22 LOAD_CONST 1 : 512 24 COMPARE_OP 0 (<) 26 POP_JUMP_IF_TRUE 47 28 LOAD_GLOBAL 2 : AssertionError 30 RAISE_VARARGS 1 32 LOAD_GLOBAL 3 : len 34 LOAD_FAST 0 : m 36 CALL_FUNCTION 1 38 LOAD_CONST 2 : 2 40 BINARY_FLOOR_DIVIDE 42 STORE_FAST 1 : l 44 LOAD_GLOBAL 4 : bytes_to_long 46 LOAD_FAST 0 : m 48 LOAD_CONST 0 : None 50 LOAD_FAST 1 : l 52 BUILD_SLICE 2 54 BINARY_SUBSCR 56 CALL_FUNCTION 1 58 STORE_FAST 2 : m1 60 LOAD_GLOBAL 4 : bytes_to_long 62 LOAD_FAST 0 : m 64 LOAD_FAST 1 : l 66 LOAD_CONST 0 : None 68 BUILD_SLICE 2 70 BINARY_SUBSCR 72 CALL_FUNCTION 1 74 STORE_FAST 3 : m2 76 LOAD_GLOBAL 5 : gen_prime 78 LOAD_CONST 3 : 1024 80 CALL_FUNCTION 1 82 STORE_FAST 4 : p 84 LOAD_GLOBAL 5 : gen_prime 86 LOAD_CONST 3 : 1024 88 CALL_FUNCTION 1 90 STORE_FAST 5 : q 92 LOAD_GLOBAL 6 : g 94 LOAD_METHOD 7 : next_prime 96 LOAD_FAST 4 : p 98 LOAD_CONST 4 : 2333 100 BINARY_ADD 102 CALL_METHOD 1 104 STORE_FAST 6 : pp 106 LOAD_GLOBAL 6 : g 108 LOAD_METHOD 7 : next_prime 110 LOAD_FAST 5 : q 112 LOAD_CONST 4 : 2333 114 BINARY_ADD 116 CALL_METHOD 1 118 STORE_FAST 7 : qq 120 LOAD_GLOBAL 6 : g 122 LOAD_METHOD 7 : next_prime 124 LOAD_CONST 5 : 65535 126 CALL_METHOD 1 128 STORE_FAST 8 : e 130 LOAD_GLOBAL 6 : g 132 LOAD_METHOD 7 : next_prime 134 LOAD_FAST 8 : e 136 CALL_METHOD 1 138 STORE_FAST 9 : ee 140 LOAD_FAST 4 : p 142 LOAD_FAST 5 : q 144 BINARY_MULTIPLY 146 STORE_FAST 10 : n 148 LOAD_FAST 6 : pp 150 LOAD_FAST 7 : qq 152 BINARY_MULTIPLY 154 STORE_FAST 11 : nn 156 LOAD_GLOBAL 8 : long_to_bytes 158 LOAD_GLOBAL 9 : pow 160 LOAD_FAST 2 : m1 162 LOAD_FAST 8 : e 164 LOAD_FAST 10 : n 166 CALL_FUNCTION 3 168 CALL_FUNCTION 1 170 STORE_FAST 12 : c1 172 LOAD_GLOBAL 8 : long_to_bytes 174 LOAD_GLOBAL 9 : pow 176 LOAD_FAST 3 : m2 178 LOAD_FAST 9 : ee 180 LOAD_FAST 11 : nn 182 CALL_FUNCTION 3 184 CALL_FUNCTION 1 186 STORE_FAST 13 : c2 188 LOAD_GLOBAL 10 : print 190 LOAD_GLOBAL 11 : str 192 LOAD_FAST 10 : n 194 CALL_FUNCTION 1 196 LOAD_FAST 11 : nn 198 LOAD_METHOD 12 : digits 200 CALL_METHOD 0 202 LOAD_FAST 12 : c1 204 LOAD_FAST 13 : c2 206 BINARY_ADD 208 LOAD_METHOD 13 : hex 210 CALL_METHOD 0 212 CALL_FUNCTION 3 214 POP_TOP 216 LOAD_FAST 12 : c1 218 LOAD_FAST 13 : c2 220 BINARY_ADD 222 RETURN_VALUE 'e2' '__main__' 2 1 b'ZmxhZ3t0aGlzaXNhZmFrZWZsYWdhZ2Fhc2FzaGRhc2hkc2hkaH0=' [Disassembly] 0 JUMP_ABSOLUTE 4 2 LOAD_CONST 11 : '__main__' 4 LOAD_CONST 0 : 0 6 LOAD_CONST 1 : None 8 IMPORT_NAME 0 : gmpy2 10 STORE_NAME 1 : g 12 LOAD_CONST 0 : 0 14 LOAD_CONST 2 : ('long_to_bytes' , 'bytes_to_long' ) 16 IMPORT_NAME 2 : Crypto.Util.number 18 IMPORT_FROM 3 : long_to_bytes 20 STORE_NAME 3 : long_to_bytes 22 IMPORT_FROM 4 : bytes_to_long 24 STORE_NAME 4 : bytes_to_long 26 POP_TOP 28 LOAD_CONST 0 : 0 30 LOAD_CONST 1 : None 32 IMPORT_NAME 5 : random 34 STORE_NAME 5 : random 36 LOAD_CONST 3 : <CODE> gen_num 38 LOAD_CONST 4 : 'gen_num' 40 MAKE_FUNCTION 0 42 STORE_NAME 6 : gen_num 44 LOAD_CONST 5 : <CODE> gen_prime 46 LOAD_CONST 6 : 'gen_prime' 48 MAKE_FUNCTION 0 50 STORE_NAME 7 : gen_prime 52 LOAD_CONST 7 : <CODE> po 54 LOAD_CONST 8 : 'po' 56 MAKE_FUNCTION 0 58 STORE_NAME 8 : po 60 LOAD_CONST 9 : <CODE> e2 62 LOAD_CONST 10 : 'e2' 64 MAKE_FUNCTION 0 66 STORE_NAME 9 : e2 68 LOAD_NAME 10 : __name__ 70 LOAD_CONST 11 : '__main__' 72 COMPARE_OP 2 (==) 74 POP_JUMP_IF_FALSE 144 76 LOAD_CONST 0 : 0 78 LOAD_CONST 1 : None 80 IMPORT_NAME 11 : sys 82 STORE_NAME 11 : sys 84 LOAD_NAME 12 : len 86 LOAD_NAME 11 : sys 88 LOAD_ATTR 13 : argv 90 CALL_FUNCTION 1 92 LOAD_CONST 12 : 2 94 COMPARE_OP 5 (>=) 96 POP_JUMP_IF_FALSE 118 98 LOAD_NAME 9 : e2 100 LOAD_NAME 11 : sys 102 LOAD_ATTR 13 : argv 104 LOAD_CONST 13 : 1 106 BINARY_SUBSCR 108 LOAD_METHOD 14 : encode 110 CALL_METHOD 0 112 CALL_FUNCTION 1 114 POP_TOP 116 JUMP_FORWARD 26 (to 144 ) 118 LOAD_CONST 0 : 0 120 LOAD_CONST 1 : None 122 IMPORT_NAME 15 : base64 124 STORE_NAME 16 : B 126 LOAD_NAME 16 : B 128 LOAD_METHOD 17 : b64decode 130 LOAD_CONST 14 : b'ZmxhZ3t0aGlzaXNhZmFrZWZsYWdhZ2Fhc2FzaGRhc2hkc2hkaH0=' 132 CALL_METHOD 1 134 STORE_NAME 18 : flag 136 LOAD_NAME 9 : e2 138 LOAD_NAME 18 : flag 140 CALL_FUNCTION 1 142 POP_TOP 144 LOAD_CONST 1 : None 146 RETURN_VALUE
都是很简单的代码,稍微熟悉下指令结构就能很快知道代码的功能。
开始是一个gen_numer的方法:
1 2 3 4 5 6 def gen_num (n_bits) : res = 1 for i in range(n_bits): if res != 0 : res <<= 1 res |= random.choice([0 , 1 ])
然后gem_prime的方法:
1 2 3 4 5 6 7 def gen_prime (n_bits) : res = gen_num(n_bits) while !gmpy2.is_prime(res): b = 1 while b != 0 : res, b = res^b, (res&b) << 1 return res
接着是一个powmod运算:这个它是用模重复平方实现的。
1 2 3 4 5 6 7 8 9 10 11 def po (a, b, n) : res = 1 aa = a while b != 0 : if b&1 == 1 : res = res*aa%n aa *= aa b >> 1 res = res%n return res
再就是本题的关键加密函数e2,从反汇编结果来看这里也是对代码加了混淆的,因为我们的参数是bytes形式的,所以肯定跳过那个异常执行,但是反汇编或反编译器工具就没有那么智能了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def e2 (m) : if type(m) == bytes: l = len(m)//2 m1 = bytes_to_long(m[:l]) m2 = bytes_to_long(m[l:]) p = gen_prime(1024 ) q = gen_prime(1024 ) pp = gmpy2.next_prime(p+2333 ) qq = gmpy2.next_prime(q+2333 ) e = gmpy2.next_prime(65535 ) ee = gmpy2.next_prime(e) n = p*q nn = pp*qq c1 = long_to_bytes(pow(m1, e, n)) c2 = long_to_bytes(pow(m2, ee, nn)) print(n) print(nn) print((c1+c2).hex())
最后就是main函数的地方:
1 2 3 4 5 6 if __name__ == '__main__' : if len(sys.argv) >= 2 : e2(sys.argv[1 ].encode()) else : flag = base64.b64decode('ZmxhZ3t0aGlzaXNhZmFrZWZsYWdhZ2Fhc2FzaGRhc2hkc2hkaH0=' ) e2(flag)
至此我将整个pyc文件手动的读代码恢复到了原始的py文件。
比赛时我也只是关心了那个加密函数e2,生成素数算法就是随机生成素数而已。
加密就是用2对rsa公钥加密对我们明文分为2组加密,然后密文给了2组公钥的n与nn:
1 2 3 4 5 6 7 8 p = q = pp = gmpy2.next_prime(p+2333) qq = gmpy2.next_prime(q+2333) n = p*q nn = pp*qq n与nn已知
开始还以为可以直接用factor把n分解出来,并不行,比赛时也是卡在了怎么根据以上的条件求解出p,q,pp,qq
以前在逆向中遇到的rsa都是n可以直接分解的,这次还真就在逆向中放一个要利用信安数学相关知识来分解n的题。
1 10300808326934539089496666241808264289631957459372648156286399524715435483257526083909012656240599916663153630994400397551123967736269088895097145175999170121832669199408651009372742448196961434772052680714475103278704994244915332148949308972258132346198706995878511207707020032852291909422169657384059305615332901933166314692127020030962059133945677194815714731744932280037687773557589292839426111679593131496468880818820566335362063945141576571029271455695757725169819071536590541808603312689890186432713168331831945391117398124164372551511615664022982639779869597584768094658974144703654232643726744397158318139843 10300808326934539089496666241808264289631957459372648156286399524715435483257526083909012656240599916663153630994400397551123967736269088895097145175999170121832669199408651009372742448196961434772052680714475103278704994244915332148949308972258132346198706995878511207707020032852291909422169657384059306119730985949350246133999803589372738154347587848281413687500584822677442973180875153089761224816081452749380588888095064009160267372694200256546854314017937003988172151851703041691419537865664897608475932582537945754540823276273979713144072687287826518630644255675609067675836382036436064703619178779628644141463 22 cca5150ca0bb2132f68302dc7441e52b91ae7252e44cc13ed83e58253a9aaaa55e095ba36748dff7ea21fff553f8c4656e77a508b64da054f1381b7e2d0600bcec6ed9e1cc8d14c2362aaef7a972a714f88e5afb2d39e2d77d0c22a449ca2cfb0802c138f20e0ecbd3c174151cdb8e8ca6d89aa3c503615ebfbc851af5ac51dcfa8b5869b775b57a27b9e4346979180d89b303cae2c5d9e6cabb3c9947837bd8f92333532d4b54dd72ea354000600066328f6f4329147df195ec78a7ab9d39973ce0fd6511e7a0de54737bee64476ba531604f0375b08adf7d768c41ba9e2ba88468d126561a134de79dc0217c1c56d219ca6747103618e46f35281feb9e6050c93e32e26e21ee2c3d495f60db2fad9f9a5c570c9f97aee698024ebff6163ef26e32958872db7c593d7f41f90981b8db45aa01085be1e61f7603ecf3d5c032dd90dea791cd9825299548c0cbe7dadabc157048a7fd5cd4bcb1cfeaf0bd2d679f66ceb0b1c33ec04bd20317f872c85d500a3475833f983fdee59b3f61a731e2a8b9a60bd7d840f46e97f06dd4fd8ad1cb4d13a82da01938801c33835ceaf34e1cf62ebdde7ac68b17c2a236b64ffacd2a0e7258571ce570871aea9ff309df63c0a3abcfa0c05d159a82f9fa3f3ad73944e4ae33c3432c8b65c0d6fe9b560220b14abe5886188fc1e6afa4bb4395669618387224422acf20b519af902225e270