-
-
[原创]KCTF2021秋 万事俱备 writeup
-
2021-12-7 21:11 16112
-
e语言32位,ida拖进去看看,发现了zlib,随即运行起来看看进程,多了个python27.exe
把temp目录下面的 pub 整个目录copy出来, 发现可以正常验证通过,说明跟E语言没关系,就是一个python题
用官方版python27执行脚本check.py,发现报错,所以,python27.exe是个改版的。
python.dll拖进ida,提示pdb路径,知道python版本,2.7.18
下一个python源码,编译release,64位出来,跟python.dll对比,发现opcode改了
经过N......个小时,把opcode找对,下面的opcode.h,编译出来的python可以正常通过check.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 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 | / * Instruction opcodes for compiled code * / #define SLICE 0 #define SLICE_1 1 #define SLICE_2 2 #define SLICE_3 3 #define STORE_SLICE 4 #define STORE_SLICE_1 5 #define STORE_SLICE_2 6 #define STORE_SLICE_3 7 #define DELETE_SLICE 8 #define DELETE_SLICE_1 9 #define DELETE_SLICE_2 10 #define DELETE_SLICE_3 11 #define DUP_TOP 13 #define BINARY_MODULO 14 #define PRINT_NEWLINE 15 #define ROT_FOUR 16 #define INPLACE_ADD 17 #define BINARY_AND 18 #define BINARY_RSHIFT 19 #define INPLACE_MULTIPLY 20 #define POP_BLOCK 22 #define DELETE_SUBSCR 23 #define PRINT_NEWLINE_TO 24 #define UNARY_INVERT 25 #define INPLACE_FLOOR_DIVIDE 26 #define IMPORT_STAR 28 #define INPLACE_OR 29 #define POP_TOP 30 #define END_FINALLY 31 #define UNARY_POSITIVE 32 #define INPLACE_POWER 33 #define INPLACE_XOR 37 #define PRINT_EXPR 39 #define BINARY_SUBSCR 40 #define INPLACE_LSHIFT 41 #define WITH_CLEANUP 42 #define INPLACE_AND 45 #define RETURN_VALUE 46 #define BINARY_TRUE_DIVIDE 48 #define BREAK_LOOP 50 #define EXEC_STMT 51 #define ROT_TWO 52 #define BINARY_SUBTRACT 53 #define BUILD_CLASS 54 #define ROT_THREE 56 #define UNARY_NOT 57 #define STOP_CODE 58 #define PRINT_ITEM 59 #define YIELD_VALUE 60 #define PRINT_ITEM_TO 62 #define INPLACE_TRUE_DIVIDE 63 #define STORE_MAP 64 #define NOP 65 #define BINARY_LSHIFT 66 #define INPLACE_MODULO 67 #define BINARY_MULTIPLY 69 #define GET_ITER 70 #define BINARY_DIVIDE 71 #define INPLACE_RSHIFT 72 #define STORE_SUBSCR 73 #define INPLACE_DIVIDE 74 #define BINARY_FLOOR_DIVIDE 76 #define BINARY_POWER 77 #define BINARY_ADD 81 #define LOAD_LOCALS 83 #define BINARY_OR 85 #define INPLACE_SUBTRACT 86 #define UNARY_CONVERT 87 #define BINARY_XOR 88 #define UNARY_NEGATIVE 89 #define CALL_FUNCTION 90 #define SETUP_LOOP 93 #define SETUP_EXCEPT 130 #define SETUP_FINALLY 111 #define LOAD_NAME 94 #define COMPARE_OP 95 #define STORE_GLOBAL 98 #define CALL_FUNCTION_VAR 99 #define CALL_FUNCTION_KW 100 #define CALL_FUNCTION_VAR_KW 101 #define STORE_ATTR 102 #define BUILD_SLICE 105 #define BUILD_TUPLE 106 #define UNPACK_SEQUENCE 107 #define FOR_ITER 108 #define LOAD_FAST 109 #define DUP_TOPX 110 #define STORE_NAME 112 #define CONTINUE_LOOP 113 #define DELETE_GLOBAL 114 #define JUMP_FORWARD 115 #define BUILD_SET 116 #define JUMP_ABSOLUTE 118 #define STORE_FAST 119 #define STORE_DEREF 122 #define POP_JUMP_IF_FALSE 92 #define POP_JUMP_IF_TRUE 120 #define JUMP_IF_FALSE_OR_POP 128 #define JUMP_IF_TRUE_OR_POP 123 #define IMPORT_NAME 124 #define LOAD_DEREF 125 #define MAKE_FUNCTION 126 #define DELETE_NAME 127 #define LOAD_CONST 131 #define LOAD_CLOSURE 132 #define BUILD_LIST 133 #define RAISE_VARARGS 134 #define IMPORT_FROM 135 #define MAKE_CLOSURE 136 #define DELETE_ATTR 137 #define LOAD_GLOBAL 138 #define BUILD_MAP 139 #define LOAD_ATTR 140 #define LIST_APPEND 141 #define DELETE_FAST 142 #define SETUP_WITH 143 #define EXTENDED_ARG 145 #define SET_ADD 146 #define MAP_ADD 147 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / #define HAVE_ARGUMENT 90 /* Opcodes from here have an argument: */ enum cmp_op {PyCmp_LT = Py_LT, PyCmp_LE = Py_LE, PyCmp_EQ = Py_EQ, PyCmp_NE = Py_NE, PyCmp_GT = Py_GT, PyCmp_GE = Py_GE, PyCmp_IN, PyCmp_NOT_IN, PyCmp_IS, PyCmp_IS_NOT, PyCmp_EXC_MATCH, PyCmp_BAD}; #define HAS_ARG(op) ((op) >= HAVE_ARGUMENT) |
用python反编译工具,替换opcode,运行直接崩溃
用python反汇编一看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | [Disassembly] 0 LOAD_CONST 0 : 0x42DB1D32A8D2872F2A53EFD66A2L 3 STORE_FAST 16 : �������u���� ȩ�ੳ�G��Ǣ��L2$ 6 JUMP_ABSOLUTE 11 9 <INVALID> 10 <INVALID> 11 NOP 12 LOAD_FAST 16 : �������u���� ȩ�ੳ�G��Ǣ��L2$ 15 LOAD_CONST 1 : 0x3849DC465F5559FE912DC348448L 18 COMPARE_OP 3 (! = ) 21 POP_JUMP_IF_FALSE 11110 24 JUMP_ABSOLUTE 30 27 <INVALID> 28 <INVALID> 29 <INVALID> 30 LOAD_FAST 16 : �������u���� ȩ�ੳ�G��Ǣ��L2$ 33 LOAD_CONST 2 : 0x28C3F2ACCDBCFA7333B7D99FA80L 36 COMPARE_OP 3 (! = ) 39 POP_JUMP_IF_FALSE 8158 42 JUMP_ABSOLUTE 46 45 NOP 46 LOAD_FAST 16 : �������u���� ȩ�ੳ�G��Ǣ��L2$ |
全是垃圾指令,而且有INVALID,说明还有混淆,无奈放弃。
直接使用源码编译,单步调试
源码中有个lltrace,打开后,python崩溃。。
发现在输入完name/serial后打开,才能成功。
。。。。。。中间省略N小时。。。。。。。。。
最终,过滤掉垃圾代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | if (lltrace) { if (opcode = = JUMP_ABSOLUTE || opcode = = LOAD_FAST || opcode = = LOAD_CONST || opcode = = STORE_FAST || opcode = = NOP || (opcode = = COMPARE_OP && oparg = = 3 ) || opcode = = INPLACE_XOR ) { ; g_myDbgTrace = 0 ; } else { g_myDbgTrace = 1 ; if (HAS_ARG(opcode)) { printf( "instr tracing: %d: opcode = %d[%s], oparg = %d\n" , f - >f_lasti, opcode, opcode_name[opcode], oparg); } else { printf( "instr tracing: %d: opcode = %d[%s]\n" , f - >f_lasti, opcode, opcode_name[opcode]); } } } |
注意:COMPARE_OP ,oparg == 3的时候是垃圾,其他是正常的if语句
增加几个宏:
1 2 3 4 | #define SHOW_TOP() ((void)(lltrace && prtrace(TOP(), "top")), TOP()) #define SHOW_SECOND() ((void)(lltrace && prtrace(SECOND(), "second")), SECOND()) #define SHOW_THIRD() ((void)(lltrace && prtrace(THIRD(), "third")), THIRD()) #define SHOW_SET_TOP(v) ((void)(lltrace && prtrace(v, "result")), SET_TOP(v)) |
把关键操作用show打印出调用前后堆栈信息,开始log,先通过COMPARE_OP,发现KCTF最终比较的是 'KCTF@021GoodLuck',这个字符串,16字节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | push 'I\x97V\x92\x02\xdep\x83\x89\x19\xb1\x0e\xa0\x03P\x03' instr tracing: 2734 : opcode = 95 [COMPARE_OP_A], oparg = 2 pop 'KCTF@021GoodLuck' top 'I\x97V\x92\x02\xdep\x83\x89\x19\xb1\x0e\xa0\x03P\x03' instr tracing: 2537 : opcode = 92 [POP_JUMP_IF_FALSE_A], oparg = 2351 pop False instr tracing: 3515 : opcode = 138 [LOAD_GLOBAL_A], oparg = 0 push { 577 : 'helloctf_pediy_Archaia' , 187 : 'The serial number was entered incorrectly!' , 421 : 'decode' , 72 : 'append' , 169 : 'hashlib' , 10 : 'input serial:' , 336 : 'The serial number was entered incorrectly!' , 232 : 'The serial number was entered incorrectly!' , 178 : '@' , 308 : 'The serial number was entered incorrectly!' , 757 : 'input username:' , 694 : 'hex' , 489 : 'The serial number was entered incorrectly!' , 116 : 'append' , 26 : 'kctf2021GoodLuck' , 507 : 'md5' , 220 : '@' , 446 : 'upper' , 447 : 'Congratulations, serial number is correct ^-^' } instr tracing: 4071 : opcode = 40 [BINARY_SUBSCR] pop 308 top { 577 : 'helloctf_pediy_Archaia' , 187 : 'The serial number was entered incorrectly!' , 421 : 'decode' , 72 : 'append' , 169 : 'hashlib' , 10 : 'input serial:' , 336 : 'The serial number was entered incorrectly!' , 232 : 'The serial number was entered incorrectly!' , 178 : '@' , 308 : 'The serial number was entered incorrectly!' , 757 : 'input username:' , 694 : 'hex' , 489 : 'The serial number was entered incorrectly!' , 116 : 'append' , 26 : 'kctf2021GoodLuck' , 507 : 'md5' , 220 : '@' , 446 : 'upper' , 447 : 'Congratulations, serial number is correct ^-^' } result 'The serial number was entered incorrectly!' instr tracing: 4360 : opcode = 59 [PRINT_ITEM] pop 'The serial number was entered incorrectly!' |
然后看中间过程,又是漫长的N。。。。。小时
先md5字符串:helloctf_pediy_Archaia,结果
bfdc823fca7d85034d70f650df268108,全小写字符串
扩展5遍,得到256字节的数组
这个数组作为 rc4的key,rc4是变种的,rc4_setup_key不是标准的,rc4_crypt也不是标准的,等setup_key结束后,抓到rc4_table
1 2 3 4 | pop None ext_pop 'helloctf_pediy_Archaia' ext_pop [ 98 , 30 , 106 , 149 , 9 , 136 , 33 , 174 , 26 , 128 , 32 , 48 , 116 , 69 , 244 , 5 , 122 , 239 , 79 , 147 , 197 , 64 , 16 , 10 , 53 , 188 , 135 , 89 , 120 , 12 , 73 , 160 , 34 , 169 , 96 , 179 , 54 , 29 , 191 , 76 , 215 , 180 , 70 , 84 , 181 , 15 , 211 , 157 , 59 , 75 , 218 , 133 , 132 , 161 , 242 , 237 , 99 , 18 , 199 , 162 , 83 , 183 , 102 , 156 , 137 , 163 , 61 , 190 , 110 , 227 , 3 , 94 , 142 , 201 , 158 , 27 , 184 , 178 , 39 , 77 , 74 , 62 , 115 , 22 , 126 , 95 , 234 , 186 , 38 , 243 , 103 , 203 , 107 , 101 , 219 , 36 , 41 , 4 , 196 , 140 , 165 , 200 , 93 , 145 , 45 , 195 , 141 , 185 , 249 , 155 , 8 , 117 , 113 , 65 , 224 , 63 , 170 , 233 , 60 , 23 , 131 , 111 , 35 , 52 , 80 , 166 , 212 , 146 , 204 , 228 , 100 , 2 , 238 , 235 , 88 , 189 , 58 , 17 , 114 , 44 , 47 , 214 , 171 , 6 , 209 , 207 , 121 , 21 , 182 , 194 , 91 , 150 , 192 , 25 , 139 , 252 , 7 , 173 , 86 , 28 , 104 , 11 , 223 , 127 , 125 , 14 , 251 , 124 , 40 , 87 , 231 , 246 , 50 , 82 , 20 , 221 , 130 , 241 , 172 , 42 , 97 , 129 , 205 , 220 , 144 , 225 , 49 , 138 , 57 , 222 , 250 , 193 , 206 , 43 , 13 , 217 , 164 , 245 , 148 , 1 , 202 , 90 , 109 , 248 , 253 , 92 , 187 , 226 , 19 , 46 , 67 , 31 , 105 , 143 , 153 , 78 , 123 , 236 , 56 , 208 , 66 , 176 , 118 , 51 , 175 , 85 , 255 , 177 , 254 , 112 , 213 , 37 , 210 , 167 , 72 , 24 , 232 , 0 , 168 , 216 , 230 , 198 , 134 , 154 , 240 , 229 , 152 , 159 , 151 , 55 , 81 , 119 , 108 , 71 , 247 , 68 ] ext_pop <function O00 at 0x0000015EAD5C2D78 > |
然后单步逆了crypt过程,处理了两遍,前一遍多异或一次161,后16轮多异或一次61,注意后一轮的i是从16开始的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | void rc4_crypt_mod(unsigned char * s, unsigned char * Data, unsigned long Len ) / / 加解密 { int i = 0 , j = 0 , t = 0 ; unsigned long k = 0 ; unsigned char tmp; for (k = 0 ; k < Len ; k + + ) { i = (i + 1 ) % 256 ; j = (j + s[i]) % 256 ; tmp = s[i]; s[i] = s[j]; / / 交换s[x]和s[y] s[j] = tmp; t = (s[i] + s[j]) % 256 ; Data[k] ^ = s[t] ^ 161 ; / / ^ 161 } for (k = 0 ; k < Len ; k + + ) { i = (i + 1 ) % 256 ; j = (j + s[i]) % 256 ; tmp = s[i]; s[i] = s[j]; / / 交换s[x]和s[y] s[j] = tmp; t = (s[i] + s[j]) % 256 ; Data[k] ^ = s[t] ^ 61 ; / / ^ 61 } } |
然后的算法,叫 “喵指令”,跟第三题 中娅之戒里面的算法很像,不过这是16位长度的,一共8个,有一个交换顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 交换顺序 验证是这样映射 = = = = = = = = = = = = = > dx[ 0 ] = di[ 3 ]; dx[ 1 ] = di[ 4 ]; dx[ 2 ] = di[ 1 ]; dx[ 3 ] = di[ 2 ]; dx[ 4 ] = di[ 7 ]; dx[ 5 ] = di[ 0 ]; dx[ 6 ] = di[ 5 ]; dx[ 7 ] = di[ 6 ]; < = = = = = = = = = = = = = keygen这样映射 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 喵指令: / / di,A,B,C,D,R,S,U,V是 16bit A = d0 ^ d1; B = d2 + d3; C = d4 - d5; D = HW(d6 ^ d7); S = (A & B) | ((~A) & C) ; d6^ = S;d7^ = S; d6<<< = D;d7<<< = D; R = (uint16)(((uint32)A * S)>>D) + 24 ; d4 + = R;d5 + = R; U = R^C; d2 + = U;d3 - = U; V = (R & S) | (S & U) | (U & R); d0^ = V;d1^ = V; |
最终keygen代码:
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 | void rc4_crypt_mod(unsigned char * s, unsigned char * Data, unsigned long Len ) / / 加解密 { int i = 0 , j = 0 , t = 0 ; unsigned long k = 0 ; unsigned char tmp; for (k = 0 ; k < Len ; k + + ) { i = (i + 1 ) % 256 ; j = (j + s[i]) % 256 ; tmp = s[i]; s[i] = s[j]; / / 交换s[x]和s[y] s[j] = tmp; t = (s[i] + s[j]) % 256 ; Data[k] ^ = s[t] ^ 161 ; / / ^ 161 } for (k = 0 ; k < Len ; k + + ) { i = (i + 1 ) % 256 ; j = (j + s[i]) % 256 ; tmp = s[i]; s[i] = s[j]; / / 交换s[x]和s[y] s[j] = tmp; t = (s[i] + s[j]) % 256 ; Data[k] ^ = s[t] ^ 61 ; / / ^ 61 } } void miao() { unsigned char good[ 16 ] = { 0x4B , 0x43 , 0x54 , 0x46 , 0x40 , 0x30 , 0x32 , 0x31 , 0x47 , 0x6F , 0x6F , 0x64 , 0x4C , 0x75 , 0x63 , 0x6B }; uint16_t * di = (uint16_t * )good; uint16_t a, b, c, d, r, s, u, v; a = di[ 0 ] ^ di[ 1 ]; b = di[ 2 ] + di[ 3 ]; c = di[ 4 ] - di[ 5 ]; d = __popcnt16(di[ 6 ] ^ di[ 7 ]); s = (a & b) | ((~a) & c); r = (uint16_t)(((uint32_t)a * s) >> d) + 24 ; u = r ^ c; v = (r & s) | (s & u) | (u & r); di[ 0 ] ^ = v; di[ 1 ] ^ = v; di[ 2 ] - = u; di[ 3 ] + = u; di[ 4 ] - = r; di[ 5 ] - = r; di[ 6 ] = _rotr16(di[ 6 ], d); di[ 7 ] = _rotr16(di[ 7 ], d); di[ 6 ] ^ = s; di[ 7 ] ^ = s; / / 下面是ok 的 unsigned char ret[ 16 ] = { 0 }; uint16_t * dx = (uint16_t * )ret; dx[ 0 ] = di[ 3 ]; dx[ 1 ] = di[ 4 ]; dx[ 2 ] = di[ 1 ]; dx[ 3 ] = di[ 2 ]; dx[ 4 ] = di[ 7 ]; dx[ 5 ] = di[ 0 ]; dx[ 6 ] = di[ 5 ]; dx[ 7 ] = di[ 6 ]; unsigned char ctx_good[ 256 ] = { 98 , 30 , 106 , 149 , 9 , 136 , 33 , 174 , 26 , 128 , 32 , 48 , 116 , 69 , 244 , 5 , 122 , 239 , 79 , 147 , 197 , 64 , 16 , 10 , 53 , 188 , 135 , 89 , 120 , 12 , 73 , 160 , 34 , 169 , 96 , 179 , 54 , 29 , 191 , 76 , 215 , 180 , 70 , 84 , 181 , 15 , 211 , 157 , 59 , 75 , 218 , 133 , 132 , 161 , 242 , 237 , 99 , 18 , 199 , 162 , 83 , 183 , 102 , 156 , 137 , 163 , 61 , 190 , 110 , 227 , 3 , 94 , 142 , 201 , 158 , 27 , 184 , 178 , 39 , 77 , 74 , 62 , 115 , 22 , 126 , 95 , 234 , 186 , 38 , 243 , 103 , 203 , 107 , 101 , 219 , 36 , 41 , 4 , 196 , 140 , 165 , 200 , 93 , 145 , 45 , 195 , 141 , 185 , 249 , 155 , 8 , 117 , 113 , 65 , 224 , 63 , 170 , 233 , 60 , 23 , 131 , 111 , 35 , 52 , 80 , 166 , 212 , 146 , 204 , 228 , 100 , 2 , 238 , 235 , 88 , 189 , 58 , 17 , 114 , 44 , 47 , 214 , 171 , 6 , 209 , 207 , 121 , 21 , 182 , 194 , 91 , 150 , 192 , 25 , 139 , 252 , 7 , 173 , 86 , 28 , 104 , 11 , 223 , 127 , 125 , 14 , 251 , 124 , 40 , 87 , 231 , 246 , 50 , 82 , 20 , 221 , 130 , 241 , 172 , 42 , 97 , 129 , 205 , 220 , 144 , 225 , 49 , 138 , 57 , 222 , 250 , 193 , 206 , 43 , 13 , 217 , 164 , 245 , 148 , 1 , 202 , 90 , 109 , 248 , 253 , 92 , 187 , 226 , 19 , 46 , 67 , 31 , 105 , 143 , 153 , 78 , 123 , 236 , 56 , 208 , 66 , 176 , 118 , 51 , 175 , 85 , 255 , 177 , 254 , 112 , 213 , 37 , 210 , 167 , 72 , 24 , 232 , 0 , 168 , 216 , 230 , 198 , 134 , 154 , 240 , 229 , 152 , 159 , 151 , 55 , 81 , 119 , 108 , 71 , 247 , 68 }; unsigned char ctx[ 256 ] = { 0 }; memcpy(ctx, ctx_good, 256 ); rc4_crypt_mod(ctx, (unsigned char * )ret, 16 ); return ; } |
ret里面打印成大写16进制即可
flag: AD0A1F8179ABE48ED3B073F840DA52A7
[招生]科锐逆向工程师培训46期预科班将于 2023年02月09日 正式开班