Loading... # Reverse(十二) ## decode_me https://ctf.bugku.com/challenges/detail/id/413.html ida64反编译,如下主函数很简洁明了 ~~~c int __cdecl main(int argc, const char **argv, const char **envp) { char v3; // bl char buf; // [rsp+17h] [rbp-29h] BYREF ssize_t v6; // [rsp+18h] [rbp-28h] int fd; // [rsp+24h] [rbp-1Ch] unsigned int v8; // [rsp+28h] [rbp-18h] char v9; // [rsp+2Fh] [rbp-11h] if ( argc != 2 ) exit(1); fd = open(argv[1], 0, 0LL); if ( fd == -1 ) exit(1); v8 = 0; v9 = 0; while ( v8 <= 0x224 ) { v6 = read(fd, &buf, 1uLL); // 每次读一个字符到buf if ( v6 <= 0 ) break; v3 = byte_404060[v8]; // 可以获取 v9 |= v3 ^ sub_4012D8((unsigned int)buf, v8++); } if ( !v9 ) // 需保证v9=0 puts("[+] Correct one!"); return 0; } ~~~ 分析sub_4012D8函数,其中a1是读取的字符,a2是下标 ~~~c __int64 __fastcall sub_4012D8(__int64 a1, __int64 a2) { __int64 v3; // [rsp+0h] [rbp-8h] v3 = sub_401291(); return v3 ^ sub_401240(a2); } ~~~ 发现a1貌似没被用?看下sub_401291函数,可以看到不对劲,只能去分析汇编代码 ~~~c // positive sp value has been detected, the output may be wrong! void sub_401291() { __asm { jmp off_404388[rax] } // 要去分析off_404388 } ~~~ ![image-20240206094727473.png](http://xherlock.top/usr/uploads/2024/02/3606204125.png) 上述汇编代码为 ~~~assembly sub_401291 proc near mov rax, 0FFFFFFFFFFFFFFF8h jmp short loc_4012AF loc_4012AF: add rax, 8 ; rax置0,从0开始下标取函数 jmp off_404388[rax] ; 此时跳转loc_4012AC函数 ~~~ ![image-20240206104222384.png](http://xherlock.top/usr/uploads/2024/02/3695512129.png) ~~~assembly loc_4012AC: push rbx ; 入栈,追朔rbx值,可以保存的正是主函数里的数组逐个遍历赋的值 jmp short $+2 ; 一条语句一般就是2个字节,所以$+2代表下一条指令,由下图可知再次跳转到loc_4012AF ~~~ ![image-20240206103443930.png](http://xherlock.top/usr/uploads/2024/02/627774736.png) 每次rax都会+8,因此每次jmp的函数不同;但查看图状视图可以看到除了loc_4012C1最后return其他均回到loc_4012AF ![image-20240206103737673.png](http://xherlock.top/usr/uploads/2024/02/3847508716.png) 下面按顺序分析每个函数作用 ~~~assembly loc_40129A: jmp loc_4012A0 loc_4012A0: jmp short loc_4012AF ; 直接返回了 loc_4012A2: lea rbx, unk_404288 ; 一个256字节大小的数组,将地址赋给了rbx jmp short loc_4012AF loc_4012CD: push rax ; 入栈,保存rax值 mov al, dil ; rdi低八位赋给rax低八位,rdi存的buf值 xlat ; 由下图可知这个指令根据偏移查表,bx存放的前面unk_404288初始地址,al存放正是主函数buf字符的ascii值,相等于偏移量 mov rbx, rax ; 查找到的数组元素值赋给rbx pop rax ; 出栈,恢复rax值 jmp short loc_4012AF ~~~ ![image-20240206114544288.png](http://xherlock.top/usr/uploads/2024/02/2781508221.png) ~~~assembly loc_4012C1: mov rax, rbx ; 将最终数组找到的值赋给rax pop rbx ; 出栈,恢复主函数里的数组逐个遍历赋的值 retn ; 退出函数 ~~~ 综上sub_401291函数作用是,传入文件读取的字符ascii值(buf),取unk_404288[buf] 接下来该计算v3 ^ sub_401240(a2),查看伪代码发现还是很怪,得分析汇编代码 ~~~c __int64 __fastcall sub_401240(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5) { __int128 v6; // rax v6 = 0LL; BYTE8(v6) = 1; return off_4043C8(a1, a2, *((_QWORD *)&v6 + 1), a4, a5); } ~~~ 分析汇编代码发现和上一个十分相似,直接全部贴上来 ~~~assembly sub_401240 proc near ; CODE XREF: sub_4012D8+19↓p jmp short loc_40126F mov rsp, rbp pop rbp retn loc_401247: xor r8, r8 ; r8清0 jmp short loc_40125D mov rax, 0FFFFFFFFFFFFFFF8h jmp short loc_40125D loc_401255: xadd r8, rdx ; 先交换值再相加 loop loc_401255 ; 很重要,每次cx=cx-1直到cx=0,而cx正好在前面loc_401283中=a2+1 jmp short $+2 loc_40125D: add rax, 8 ; rax恢复为0,开始遍历取函数 jmp off_4043C8[rax] ; 具体顺序如下图 mov rax, rsi jmp short loc_40125D loc_40126C: xchg rax, r8 ; 交换值 retn loc_40126F: xor rax, rax ; rax置0,由于运算结果等于0,ZF标志位置1 cqo ; 扩展双字,即从64位扩展到128位,具体是将eax寄存器的值进行扩展,高位置于rdx中,因此rdx=0 stc ; CF位置1 setz dl ; ZF位标志赋给dl,即rdx=1 sub rax, 8 ; 雷同 jmp short loc_40125D add rsp, 28h retn loc_401283: ; 第一次这里 add rdi, rcx ; 朔源发现在sub_4012D8中赋值rdi为a2的值,也就是主函数里的v8;rcx是一个地址值,做的处理是rdi=rdi+rcx sub rcx, rdi ; rcx=rcx-rdi=rcx-(rdi+rcx)=-rdi neg rcx ; rcx取补码即rdi inc rcx ; rcx++=rdi+1,也就是a2+1 jmp short loc_40125D ~~~ ![image-20240206121112755.png](http://xherlock.top/usr/uploads/2024/02/3082584728.png) 综上,上述汇编代码c语言逻辑如下 ~~~c int sub_401240(int a2) { int r8 = 0; int rdx = 1; for (int i = 0; i < a2 + 1; i++) { int temp = r8; r8 += rdx; rdx = r8; } } ~~~ 脚本逆向 ~~~python byte_404060 = [0x7B, 0xED, 0x51, 0x57, 0xFD, 0x11, 0x5E, 0x41, 0x6E, 0xAB, 0xA2, 0x0C, 0xF0, 0x2A, 0x29, 0x97, 0xD9, 0x67, 0x2A, 0x24, 0x9D, 0x64, 0xBF, 0x74, 0x42, 0x7D, 0x80, 0x8B, 0xEA, 0x63, 0x25, 0x4B, 0x0E, 0xAB, 0x85, 0x2C, 0x32, 0x67, 0x5A, 0x87, 0x2F, 0xA4, 0x67, 0x25, 0x8D, 0x0C, 0xB5, 0xA4, 0xD9, 0xEE, 0xCE, 0xBE, 0xA7, 0xB0, 0xF9, 0x19, 0xF1, 0x2D, 0x83, 0x72, 0xF1, 0x1B, 0xB1, 0xF7, 0x01, 0x9A, 0xFA, 0xEF, 0xDE, 0xC4, 0x9F, 0x98, 0x7D, 0xCE, 0x3A, 0x91, 0xF9, 0x84, 0x85, 0xFC, 0x8E, 0x18, 0xB0, 0x4A, 0x75, 0x71, 0x6C, 0x47, 0x81, 0x34, 0xD9, 0xF6, 0x4C, 0x47, 0x8F, 0xDF, 0x1E, 0x37, 0xFA, 0x8F, 0x29, 0x73, 0x0F, 0x9E, 0xBE, 0x0C, 0x4A, 0xAA, 0xD1, 0xFB, 0x09, 0x07, 0x47, 0x22, 0x57, 0x1A, 0xBD, 0x90, 0xBC, 0xE2, 0xCD, 0x33, 0xBA, 0xC8, 0x37, 0xFB, 0xA7, 0x7F, 0x0E, 0xEC, 0xC7, 0xDC, 0x41, 0x98, 0xF1, 0x49, 0xD5, 0x54, 0xB6, 0x5F, 0x20, 0xFB, 0x59, 0xB1, 0x32, 0xE3, 0xC9, 0xFE, 0x69, 0x30, 0x71, 0xF9, 0xB0, 0xAF, 0xC6, 0x4C, 0x05, 0x61, 0x40, 0x24, 0x41, 0x20, 0xF7, 0x41, 0xDE, 0xF5, 0x2B, 0x18, 0x83, 0x02, 0x89, 0x40, 0x9B, 0x04, 0x4B, 0x5D, 0x2E, 0x58, 0x91, 0xCA, 0x35, 0x1A, 0x76, 0x20, 0x75, 0xA7, 0xCE, 0x91, 0xFA, 0x34, 0x6D, 0x71, 0x79, 0xCD, 0x40, 0x1F, 0xCE, 0x46, 0x75, 0xCA, 0x76, 0x4F, 0x95, 0xE1, 0x36, 0x1D, 0x9A, 0x17, 0xFF, 0x84, 0x17, 0x15, 0x5E, 0x6D, 0x89, 0x6C, 0x33, 0xA8, 0xDE, 0x08, 0x66, 0x92, 0xE7, 0x27, 0x1A, 0x95, 0xEB, 0x48, 0xB7, 0xF6, 0xF1, 0xF1, 0x15, 0x37, 0x71, 0x02, 0x70, 0x27, 0x8D, 0x1C, 0x4D, 0xB5, 0x20, 0x9B, 0x1E, 0x0A, 0x7E, 0xCF, 0x18, 0xFB, 0xF2, 0x9E, 0x65, 0xA1, 0x6D, 0xC3, 0x81, 0xAA, 0x6C, 0x77, 0xAE, 0xFD, 0xBD, 0x2B, 0xFD, 0xE9, 0xCD, 0x8C, 0xC1, 0x90, 0xB4, 0x68, 0xE9, 0x3F, 0xD2, 0xAF, 0x52, 0x45, 0xCE, 0xE9, 0x01, 0xBA, 0x21, 0xC5, 0x4E, 0x7D, 0xAD, 0xD4, 0x2D, 0xB9, 0x9E, 0x81, 0xBD, 0xC3, 0x92, 0xAD, 0x3B, 0x28, 0x05, 0x5B, 0xE5, 0x41, 0xFE, 0x50, 0x85, 0x04, 0xE7, 0xB4, 0x78, 0x83, 0xC3, 0x4C, 0x9A, 0x3D, 0xDE, 0xF8, 0xBB, 0x50, 0xCE, 0xBD, 0x19, 0x73, 0x5A, 0xCB, 0x52, 0x9B, 0x4E, 0xF3, 0x31, 0xF3, 0x9D, 0xBD, 0x9E, 0x5D, 0x6D, 0x38, 0xB2, 0xEA, 0x74, 0xDB, 0xF6, 0x3E, 0x9B, 0xA9, 0xE9, 0x99, 0x81, 0x49, 0x9A, 0xE2, 0x89, 0x17, 0x89, 0x1A, 0x4D, 0x37, 0xDE, 0xA4, 0xFD, 0x18, 0xFC, 0x93, 0x2E, 0x61, 0xC9, 0x1E, 0x6E, 0xDD, 0x3D, 0xD3, 0x11, 0x6F, 0x03, 0x0C, 0x76, 0xB4, 0x41, 0x14, 0xFE, 0xB1, 0xE6, 0x07, 0x9D, 0x4C, 0xF9, 0xF3, 0x51, 0x68, 0xE9, 0xCA, 0xF5, 0x59, 0x60, 0xDB, 0xA1, 0x6B, 0xAB, 0x2A, 0xD8, 0x61, 0xD1, 0x53, 0x1B, 0x81, 0x3A, 0x6D, 0xA2, 0x74, 0xE7, 0xD5, 0xBA, 0x4C, 0xA9, 0x64, 0x25, 0x4E, 0xBD, 0xAB, 0x31, 0xFB, 0x95, 0xD2, 0x4E, 0x09, 0xAF, 0x6B, 0xF1, 0x41, 0x38, 0xFD, 0x19, 0x1F, 0x2E, 0x99, 0xCC, 0xBC, 0x3A, 0xBE, 0x55, 0xE1, 0xBE, 0xC4, 0x83, 0x4C, 0x45, 0x7B, 0xD5, 0xC4, 0xE2, 0x92, 0xB7, 0xB5, 0x11, 0xC4, 0x27, 0x98, 0xBE, 0x69, 0xCD, 0x0C, 0x41, 0x26, 0x22, 0xA9, 0x86, 0xBF, 0xEB, 0x1C, 0xCD, 0x65, 0xDA, 0x37, 0x0F, 0x80, 0xE7, 0xE2, 0x1E, 0xEB, 0x39, 0x8F, 0xFB, 0x92, 0x4C, 0x76, 0x3D, 0x4A, 0x0F, 0x39, 0x98, 0x4D, 0xEB, 0x43, 0x65, 0xD5, 0xA0, 0x80, 0x03, 0x1A, 0x66, 0x8B, 0x80, 0xBC, 0x73, 0x06, 0xA4, 0xBB, 0xA2, 0x79, 0x68, 0x0E, 0x10, 0x9A, 0xBD, 0x01, 0xD3, 0xD0, 0xF2, 0xF4, 0x04, 0x04, 0x17, 0x1C, 0xE8, 0x3D, 0x0E, 0x56, 0x5E, 0xA3, 0x5D, 0x1B, 0x26, 0x7B, 0x5A, 0xB7, 0x3B, 0x59, 0x60, 0x1B, 0x1D, 0x2F, 0xE9, 0x75, 0x14, 0x24, 0x41, 0x8B, 0x7E, 0xE1, 0x3D, 0x00, 0x00, 0x00] unk_404288 = [0x24, 0xDD, 0xC1, 0x89, 0x0A, 0xCF, 0x83, 0xE2, 0xC6, 0xFD, 0x7F, 0x46, 0x44, 0x25, 0x9D, 0x2E, 0x27, 0x20, 0xB8, 0xE1, 0x35, 0x39, 0x33, 0xB1, 0x90, 0xC8, 0x30, 0xA9, 0x98, 0x75, 0x73, 0xEA, 0xF8, 0x36, 0x40, 0x23, 0xA4, 0x29, 0x69, 0x5D, 0x15, 0x91, 0xC4, 0x0B, 0xE4, 0xAC, 0x37, 0x95, 0x3C, 0x63, 0x52, 0x85, 0xE0, 0x1E, 0xF3, 0xFF, 0x82, 0x84, 0xB5, 0xBB, 0x97, 0x92, 0x96, 0x12, 0x8E, 0x4A, 0xD6, 0x49, 0x7A, 0x9B, 0x8B, 0x34, 0x0C, 0x8F, 0xD5, 0xEF, 0x2C, 0x5A, 0x3A, 0xCA, 0xC5, 0x6E, 0xA0, 0x67, 0x0D, 0x8C, 0x1F, 0xF5, 0x07, 0x1D, 0x5B, 0x4F, 0x9F, 0xAB, 0x05, 0xA6, 0x81, 0x53, 0x3F, 0xFB, 0xF6, 0xEC, 0x0F, 0x4E, 0x42, 0x9C, 0x08, 0x6B, 0xBA, 0xF2, 0x4B, 0x5F, 0x19, 0x14, 0x54, 0xC2, 0x4C, 0x1C, 0x5C, 0x71, 0xFE, 0xA2, 0xA1, 0x1B, 0x03, 0xD8, 0x64, 0x11, 0x26, 0x6D, 0x0E, 0x18, 0xCB, 0x70, 0xEB, 0x51, 0x62, 0x68, 0x2D, 0x43, 0x59, 0x01, 0x00, 0x77, 0x58, 0xAA, 0xBD, 0xE6, 0x31, 0xEE, 0x87, 0xDF, 0x1A, 0x2F, 0x55, 0xB6, 0xF0, 0x6A, 0x50, 0x32, 0x65, 0x06, 0xB4, 0x76, 0xD9, 0x22, 0x6C, 0xBE, 0xE8, 0xA5, 0x9A, 0x6F, 0x48, 0x57, 0xC9, 0x04, 0xF9, 0xCD, 0xD4, 0xDB, 0x21, 0xE9, 0x2B, 0xAE, 0xDE, 0x7C, 0x7B, 0x45, 0xF4, 0xC0, 0x09, 0x79, 0x93, 0xFA, 0xE5, 0x4D, 0xA3, 0xCE, 0xBF, 0xB3, 0x7D, 0x56, 0x3B, 0x94, 0xB0, 0x86, 0x61, 0x9E, 0xE3, 0xFC, 0xD0, 0x8A, 0x2A, 0x66, 0x80, 0xB7, 0xDA, 0xF7, 0x16, 0xC7, 0x38, 0xA7, 0xC3, 0xD3, 0xD1, 0x5E, 0x47, 0x8D, 0xA8, 0x10, 0x41, 0xDC, 0x60, 0x88, 0x78, 0x3D, 0xED, 0xBC, 0x3E, 0xF1, 0xAD, 0xAF, 0x72, 0x17, 0x02, 0x7E, 0xD7, 0xB9, 0x99, 0x28, 0x74, 0xCC, 0xB2, 0xE7, 0xD2, 0x13] def sub_401240(a2): r8 = 0 rdx = 1 for i in range(a2+1): temp = rdx rdx = r8 r8 += temp return r8 def sub_401291(a1): return unk_404288[a1] #def sub_4012D8(a1, a2): # v3 = sub_401291(a1) # return v3 ^ sub_401240(a2) flag = '' for i in range(0x225): v3 = byte_404060[i] buf = v3 ^ (sub_401240(i)%256) for j in range(len(unk_404288)): if buf == unk_404288[j]: flag += chr(j) print(flag) # shellmates{x86_has_WA4AY_Too_M4nY_IN$TRUC710N$} ~~~ ## 签退-ctfshow uncompyle6反编译:uncompyle6 -o re3.py re3.pyc ~~~python # uncompyle6 version 3.9.0 # Python bytecode version base 2.7 (62211) # Decompiled from: Python 3.9.12 (main, Apr 4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)] # Embedded file name: re3.py # Compiled at: 2020-03-06 17:43:28 import string c_charset = string.ascii_uppercase + string.ascii_lowercase + string.digits + '()' flag = 'BozjB3vlZ3ThBn9bZ2jhOH93ZaH9' # 长度正好4的倍数,末尾无.说明原来字节数为3的倍数 def encode(origin_bytes): c_bytes = [ ('{:0>8}').format(str(bin(b)).replace('0b', '')) for b in origin_bytes ] # 字节转为二进制 resp = '' nums = len(c_bytes) // 3 remain = len(c_bytes) % 3 integral_part = c_bytes[0:3 * nums] while integral_part: tmp_unit = ('').join(integral_part[0:3]) # 每次取前三个字节(二进制字符串)拼成长度为24的二进制格式字符串 tmp_unit = [ int(tmp_unit[x:x + 6], 2) for x in [0, 6, 12, 18] ] # 每个字节分别转为二进制数字 resp += ('').join([ c_charset[i] for i in tmp_unit ]) # 下标取字符数组 integral_part = integral_part[3:] # 去除前三个字节 if remain: remain_part = ('').join(c_bytes[3 * nums:]) + (3 - remain) * '0' * 8 # 补0使得其长度同样为24 tmp_unit = [ int(remain_part[x:x + 6], 2) for x in [0, 6, 12, 18] ][:remain + 1] resp += ('').join([ c_charset[i] for i in tmp_unit ]) + (3 - remain) * '.' return rend(resp) def rend(s): def encodeCh(ch): f = lambda x: chr((ord(ch) - x + 2) % 26 + x) # 全部按字母顺序右移2位 if ch.islower(): return f(97) if ch.isupper(): return f(65) return ch return ('').join(encodeCh(c) for c in s) ~~~ 解密脚本(ps:题目给的太简单正好3的倍数即无点号,可以直接不考虑remain写代码,这里我全部做了代码逆向,并附上了测试案例) ~~~python def decode(c): c = rrend(c) # 查找.出现次数 index, remain = 0, 0 while True: index = c.find('.', index) if index == -1: break remain += 1 index += 1 remain_part, resp = '', '' if remain: # 如果有. remain = 3 - remain tmp_unit = ('').join([bin(c_charset.index(i))[2:].zfill(6) for i in c[-4:-4+remain+1]]) + (3 - remain) * '0' * 6 remain_part = ('').join([chr(int(tmp_unit[x:x+8], 2)) for x in [0, 8, 16]][:remain+1]) integral_part = c[:-4] if remain else c while integral_part: tmp_unit = ('').join([bin(c_charset.index(i))[2:].zfill(6) for i in integral_part[0:4]]) # 每次取前四个字符(4*6)转为二进制格式字符串 tmp_unit = [int(tmp_unit[x:x + 8], 2) for x in [0, 8, 16]] resp += ('').join([ chr(i) for i in tmp_unit ]) integral_part = integral_part[4:] # 去除前4个字符 resp += remain_part return resp def rrend(s): def decodeCh(ch): f = lambda x: chr((ord(ch) - x- 2) % 26 + x) # 全部按字母顺序左移2位 if ch.islower(): return f(97) if ch.isupper(): return f(65) return ch return ('').join(decodeCh(c) for c in s) print(decode(flag)) # test c = encode(bytearray('xherlock', 'utf-8')) print(c) print(decode(c)) c = encode(bytearray('ctfshow', 'utf-8')) print(c) print(decode(c)) ''' out: flag{c_t_f_s_h_0_w_!} gIjneozxA2u. xherlock A3Toe2jxfy.. ctfshow ''' ~~~ ## 真的是签到 https://ctf.show/challenges#%E7%9C%9F%E7%9A%84%E6%98%AF%E7%AD%BE%E5%88%B0-207 **压缩包伪加密**:找504b0102后面的0900改为0000 **aspack脱壳**:https://down.52pojie.cn/Tools/Unpackers/这里找aspack unpacker 最后upx脱壳,ida反编译得到核心代码函数 ~~~c int sub_4012F0() { int v1[23]; // [esp+30h] [ebp-168h] BYREF size_t i; // [esp+8Ch] [ebp-10Ch] char Str[264]; // [esp+90h] [ebp-108h] BYREF sub_401990(); sub_401510(); scanf("%s", Str); if ( strlen(Str) == 18 ) // 长度为18 { i = strlen(Str) - 1; memcpy(v1, &unk_402000, 0x48u); while ( 1 ) { Str[i] ^= i; if ( !i ) break; --i; } strrev(Str); for ( i = 0; i != 18; ++i ) { if ( Str[i] != v1[i] ) { printf("%s\n", "try again"); return 0; } } printf("%s\n", "You Win"); } else { printf("%s\n", "try again"); } return 0; } ~~~ ~~~python u = [0x6c, 0x2f, 0x30, 0x31, 0x32, 0x33, 0xb6, 0xbf, 0xa0, 0xcf, 0x7c, 0x71, 0x6a, 0x6c, 0x70, 0x64, 0x75, 0x63][::-1] flag = [] for i in range(len(u)): flag.append(u[i]^i) print(bytes(flag).decode('gb2312')) ~~~ ## android-app-100 jeb加载分析: ~~~java package com.example.ctf2; import android.app.Activity; import android.os.Build; import android.os.Bundle; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends Activity { Button a; EditText b; TextView c; int d; String e; static { System.loadLibrary("adnjni"); } public MainActivity() { super(); this.d = 123; this.e = "Code"; } public native int IsCorrect(String arg1) { // 这个判断要去上面jni调用的c库里分析 } public void onCreate(Bundle arg3) { super.onCreate(arg3); this.setContentView(2130903040); this.a = this.findViewById(2131230721); // 按钮 this.b = this.findViewById(2131230720); this.c = this.findViewById(2131230722); this.e = Build.SERIAL; // 获取手机序列号,应该是判断不是模拟器 this.d = 114366; this.a.setOnClickListener(new a(this)); // 监听点击事件, 里面的a是一个类 } public native int processObjectArrayFromNative(String arg1) { } } ~~~ ~~~java package com.example.ctf2; import android.util.Log; import android.view.View$OnClickListener; import android.view.View; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; class a implements View$OnClickListener { a(MainActivity arg1) { this.a = arg1; // 这是传入的参数也就是输入字符串 super(); } public void onClick(View arg8) { new String(" "); String v0 = this.a.b.getText().toString(); Log.v("EditText", this.a.b.getText().toString()); // b是文本按钮 new String(""); int v1 = this.a.processObjectArrayFromNative(v0); int v2 = this.a.IsCorrect(v0); // 保证v2==1 v0 = String.valueOf(this.a.d + v1) + " "; // d=114366 try { MessageDigest v1_1 = MessageDigest.getInstance("MD5"); v1_1.update(v0.getBytes()); byte[] v1_2 = v1_1.digest(); // v0的md5值 StringBuffer v3 = new StringBuffer(); int v0_2; for(v0_2 = 0; v0_2 < v1_2.length; ++v0_2) { v3.append(Integer.toString((v1_2[v0_2] & 255) + 256, 16).substring(1)); } if(v2 == 1 && this.a.e != "unknown") { this.a.c.setText("Sharif_CTF(" + v3.toString() + ")"); } if(v2 == 1 && this.a.e == "unknown") { this.a.c.setText("Just keep Trying :-)"); } if(v2 == 0) { this.a.c.setText("Just keep Trying :-)"); } return; } catch(NoSuchAlgorithmException v0_1) { v0_1.printStackTrace(); return; } } } ~~~ ida分析对应的so文件,第一个Java_com_example_ctf2_MainActivity_IsCorrect函数,发现传入while大循环的只有v10,v10要么等于0,要么非0,经过测试只有v10=0时返回的v11=1,即需要保证v9=v12="ef57f3fe3cf603c03890ee588878c0ec5" ~~~c int __fastcall Java_com_example_ctf2_MainActivity_IsCorrect(int a1, int a2, int a3) { int v4; // r6 int v5; // r0 int v6; // r1 char *v9; // [sp+14h] [bp-4Ch] int v10; // [sp+1Ch] [bp-44h] int v11; // [sp+20h] [bp-40h] char v12[60]; // [sp+24h] [bp-3Ch] BYREF v9 = (char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0); // 较难理解但猜测是输入的字符串 strcpy(v12, "ef57f3fe3cf603c03890ee588878c0ec"); v4 = 0; v12[33] = '5'; // 注意中间少个v12[32]=0,相当于v12="ef57f3fe3cf603c03890ee588878c0ec" v10 = j_strcmp(v9, v12); v5 = 1701458332; while ( 1 ) // { while ( 1 ) { while ( 1 ) { v6 = v5; v5 = 809244963; if ( v6 <= 1701458331 ) break; v5 = -333293478; if ( v10 ) v5 = -158041539; } if ( v6 <= 809244962 ) break; (*(void (__fastcall **)(int, int, char *))(*(_DWORD *)a1 + 680))(a1, a3, v9); v11 = v4; v5 = -326599761; } if ( v6 != -158041539 ) // 必须返回v11=1,因此这里if必须进去过一次 { v4 = 1; v5 = -158041539; if ( v6 != -333293478 ) break; } } return v11; } ~~~ 综上输入的字符串="ef57f3fe3cf603c03890ee588878c0ec",接着看第二个函数Java_com_example_ctf2_MainActivity_processObjectArrayFromNative ~~~c int __fastcall Java_com_example_ctf2_MainActivity_processObjectArrayFromNative(int a1, int a2, int a3) { int v3; // r0 int v4; // r0 int v5; // r0 char v6; // r1 char v8[40]; // [sp-50h] [bp-A8h] BYREF int v9; // [sp-28h] [bp-80h] BYREF int v10; // [sp-20h] [bp-78h] BYREF int v11; // [sp-18h] [bp-70h] BYREF int v12; // [sp-10h] [bp-68h] BYREF _DWORD v13[6]; // [sp-8h] [bp-60h] BYREF const char *v14; // [sp+10h] [bp-48h] int v15; // [sp+14h] [bp-44h] int v16; // [sp+18h] [bp-40h] int v17; // [sp+1Ch] [bp-3Ch] int v18; // [sp+20h] [bp-38h] int v19; // [sp+24h] [bp-34h] _DWORD *v20; // [sp+2Ch] [bp-2Ch] int *v21; // [sp+30h] [bp-28h] int *v22; // [sp+34h] [bp-24h] int *v23; // [sp+38h] [bp-20h] int *v24; // [sp+3Ch] [bp-1Ch] char v25; // [sp+40h] [bp-18h] int v26; // [sp+44h] [bp-14h] v16 = a3; v15 = a1; v3 = -1661035768; while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( v3 <= -1303766071 ) v3 = 2063008300; if ( v3 > 441419317 ) break; v3 = 1800572839; if ( !v25 ) v3 = 441419318; } if ( v3 > 867851767 ) break; v3 = 1405326207; } if ( v3 <= 1405326206 ) break; if ( v3 == 1405326207 ) { *v24 = j_lrand48(); (*(void (__fastcall **)(_DWORD, int, int))(*(_DWORD *)*v20 + 680))(*v20, *v21, *v23); v26 = *v22; v3 = 867851768; } else if ( v3 == 1800572839 ) { *v22 = 92060626; v3 = 441419318; } else { v20 = v13; v21 = &v12; v22 = &v11; v23 = &v10; v24 = &v9; v13[0] = v15; v12 = v16; v17 = 0; v11 = 0; v4 = (*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)v15 + 676))(v15, v16, 0); *v23 = v4; v14 = (const char *)*v23; v13[5] = v8; v18 = 101; qmemcpy(v8, "ef57f3fe3cf603c03890ee588878c0ec", 32); v19 = 53; v13[4] = 55; v13[3] = 99; v8[32] = v17; // 正是0,和上一个函数里一样 v8[33] = 53; v5 = j_strcmp(v14, v8); // 相等因此v5=0 v6 = 1; if ( v5 ) v6 = v17; v25 = v6; v3 = -18897425; } } return v26; } ~~~ 跑一遍上述代码,注释掉错误信息后得到返回值v26=92060626 因此回到上面java代码中的onclick(已简化和填充数据),稍微改写下即可打印flag ~~~java import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class sharif { public static void main(String args[]) { String v0 = ""; v0 = String.valueOf(114366 + 92060626) + " "; // d=114366 try { MessageDigest v1_1 = MessageDigest.getInstance("MD5"); v1_1.update(v0.getBytes()); byte[] v1_2 = v1_1.digest(); // v0的md5值 StringBuffer v3 = new StringBuffer(); int v0_2; for (v0_2 = 0; v0_2 < v1_2.length; ++v0_2) { v3.append(Integer.toString((v1_2[v0_2] & 255) + 256, 16).substring(1)); } System.out.println("Sharif_CTF(" + v3.toString() + ")"); return; } catch(NoSuchAlgorithmException v0_1) { v0_1.printStackTrace(); return; } } } ~~~ ## NET(APK-逆向2) 题目名称原来是APK但不太对的样子,因为exe是NET编写的所以写个NET标题,dnspy打开 ~~~c# using System; using System.Diagnostics; using System.IO; using System.Net.Sockets; using System.Text; namespace Rev_100 { // Token: 0x02000002 RID: 2 internal class Program { // Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250 private static void Main(string[] args) { string hostname = "127.0.0.1"; int port = 31337; TcpClient tcpClient = new TcpClient(); try { Console.WriteLine("Connecting..."); tcpClient.Connect(hostname, port); } catch (Exception) { Console.WriteLine("Cannot connect!\nFail!"); return; } Socket client = tcpClient.Client; string text = "Super Secret Key"; string text2 = Program.read(); client.Send(Encoding.ASCII.GetBytes("CTF{")); foreach (char x in text) { client.Send(Encoding.ASCII.GetBytes(Program.search(x, text2))); } client.Send(Encoding.ASCII.GetBytes("}")); client.Close(); tcpClient.Close(); Console.WriteLine("Success!"); } // Token: 0x06000002 RID: 2 RVA: 0x0000213C File Offset: 0x0000033C private static string read() { string fileName = Process.GetCurrentProcess().MainModule.FileName; // 获取的正是当前exe文件的名字 string[] array = fileName.Split(new char[] { '\\' }); string path = array[array.Length - 1]; string result = ""; using (StreamReader streamReader = new StreamReader(path)) { result = streamReader.ReadToEnd(); } return result; } // Token: 0x06000003 RID: 3 RVA: 0x000021B0 File Offset: 0x000003B0 private static string search(char x, string text) { int length = text.Length; for (int i = 0; i < length; i++) { if (x == text[i]) { int value = i * 1337 % 256; return Convert.ToString(value, 16).PadLeft(2, '0'); } } return "??"; } } } ~~~ 两种方法: 1. 直接开端口 ![image-20240208173838910.png](http://xherlock.top/usr/uploads/2024/02/2969505186.png) ![image-20240208173822329.png](http://xherlock.top/usr/uploads/2024/02/2435118845.png) 2. 写python脚本f.open读取exe,略 ## serial-150-SUCTF ida64反编译发现伪代码未成功 ![image-20240221093858384.png](http://xherlock.top/usr/uploads/2024/02/694109677.png) 但是在linux里可以正常运行,只好远程linux调试查看,首先在main开头处下断点,启动调试后会停在开头 ![image-20240221094750063.png](http://xherlock.top/usr/uploads/2024/02/710945217.png) 调试过程中发现ida警告RIP指向了已经定义指令的地址,合理猜测这是伪代码无法生成的原因 ![image-20240221094948112.png](http://xherlock.top/usr/uploads/2024/02/3576314301.png) 同意yes然后F8会重新编译出正确代码,如下可以看到字符串提示输入key,以及打印指令 ![image-20240221095615938.png](http://xherlock.top/usr/uploads/2024/02/595866040.png) 接着又会碰到警告RIP,同样做法,同时也会发现中间有部分db代码无用 ![image-20240221095859177.png](http://xherlock.top/usr/uploads/2024/02/2957577379.png) 接着运行需要输入key,linux输入1234,F8发现输入已入栈 ![image-20240221100208309.png](http://xherlock.top/usr/uploads/2024/02/1529889109.png) 再次让ida修复RIP指向地址指令,如下,作用是获取输入key长度和16比较(合理猜测输入长度为16,否则就不会跳转) ~~~assembly .text:0000000000400A19 lea rax, [rbp-200h] .text:0000000000400A20 mov rdi, rax .text:0000000000400A23 call _strlen .text:0000000000400A23 .text:0000000000400A28 cmp rax, 10h .text:0000000000400A2C jz short near ptr loc_400A3B+1 ~~~ 如图不断运行发现来到了字符串打印说明key错误的地方 ![image-20240221100732769.png](http://xherlock.top/usr/uploads/2024/02/4021020694.png) 重新运行,输入abcd...,够16位跳转后的汇编代码如下,发现提取了key第一个字符和'E'比较,合理猜测首字母为E ~~~assembly .text:0000000000400A3C loc_400A3C: ; CODE XREF: .text:0000000000400A2C↑j .text:0000000000400A3C movzx eax, byte ptr [rbp-200h] .text:0000000000400A43 cmp al, 45h ; 'E' .text:0000000000400A45 jz short near ptr loc_400A54+1 ~~~ 修改ZF标志位跳转,如下,作用是提取第一个字符和最后一个字符相加,和0x98比较 ~~~assembly .text:0000000000400A55 movzx eax, byte ptr [rbp-200h] ; CODE XREF: .text:0000000000400A45↑j .text:0000000000400A5C movsx edx, al .text:0000000000400A5F movzx eax, byte ptr [rbp-1F1h] .text:0000000000400A66 movsx eax, al .text:0000000000400A69 add eax, edx .text:0000000000400A6B cmp eax, 9Bh .text:0000000000400A70 jz short near ptr loc_400A7F+1 ~~~ 修改ZF标志位跳转,如下,发现回到了第二个字符比较,和前面逻辑一样 ~~~assembly .text:0000000000400A80 movzx eax, byte ptr [rbp-1FFh] ; CODE XREF: .text:0000000000400A70↑j .text:0000000000400A87 cmp al, 5Ah ; 'Z' .text:0000000000400A89 jz short near ptr loc_400A98+1 .text:0000000000400A99 movzx eax, byte ptr [rbp-1FFh] ; CODE XREF: .text:0000000000400A89↑j .text:0000000000400AA0 movsx edx, al .text:0000000000400AA3 movzx eax, byte ptr [rbp-1F2h] .text:0000000000400AAA movsx eax, al .text:0000000000400AAD add eax, edx .text:0000000000400AAF cmp eax, 9Bh .text:0000000000400AB4 jz short near ptr loc_400AC3+1 ~~~ 所以循环个8轮,即可得到整个key:EZ9dmq4c8g9G7bAV 上面这种反抗反编译的做法可以称为花指令 https://www.52pojie.cn/thread-1536489-1-1.html ## echo-server ida反编译无法阅读代码,分析发现使用了**花指令**混淆,首先分析main ~~~assembly ; Attributes: bp-based frame fuzzy-sp ; int __cdecl main() main proc near ; __unwind { push ebp mov ebp, esp and esp, 0FFFFFFF0h sub esp, 10h mov eax, ds:stdin mov dword ptr [esp+4], 0 ; buf mov [esp], eax ; stream call _setbuf mov eax, ds:stdout mov dword ptr [esp+4], 0 ; buf mov [esp], eax ; stream call _setbuf mov ds:dword_804A088, 1 mov dword ptr [esp], offset s ; " **************\n "... call _puts call near ptr loc_80487C1+3 ;这里有些奇怪+3 mov eax, 0 leave retn ; } // starts at 80488E0 main endp ~~~ `call near ptr loc_80487C1+3`这条指令会来到下图,提示sp-analysis failed,说明存在花指令 ![image-20240221180626860.png](http://xherlock.top/usr/uploads/2024/02/2276009407.png) call的地址很不正常,因此按U取消定义转为机器代码,这时伪代码可以看到跳转地址0x80487C1 ~~~c int __cdecl sub_804875D(unsigned __int8 *a1, unsigned int a2) { unsigned __int8 *v2; // eax char v3; // zf int result; // eax unsigned __int8 *v5; // [esp+18h] [ebp-10h] unsigned int i; // [esp+1Ch] [ebp-Ch] v5 = a1; if ( a1 ) { for ( i = 0; i < a2; ++i ) { v2 = v5++; printf("%02X", *v2); } } else { printf("NULL"); } result = putchar(10); if ( !v3 ) { if ( v3 ) JUMPOUT(0x80487C1); } return result; } ~~~ 双击查看0x80487C1,可以明显看到花指令 ![image-20240221181303165.png](http://xherlock.top/usr/uploads/2024/02/1452093717.png) 下方unwind处有大量数据,在unwind上按下C转为code,如下,发现前半部分恢复成功,但是中间jmp指令跳到了自身中间,因此按下U将其转为机器码 ![image-20240221182403682.png](http://xherlock.top/usr/uploads/2024/02/154171.png) 看到了0xEB可知其是一个花指令,因此在其后按下C转为c代码 ![image-20240221182842531.png](http://xherlock.top/usr/uploads/2024/02/1829779376.png) 依然出现了奇怪jmp地址,但是上面的jz恒成立跳转,因此不再转为机器代码,同时发现后面mov里引用了这个地址的数据,因此按D转为数据 ![image-20240221182931614.png](http://xherlock.top/usr/uploads/2024/02/3568315815.png) 再往后看发现还有一处标红,同样操作转为正确代码 ![image-20240221183250285.png](http://xherlock.top/usr/uploads/2024/02/2543108865.png) ![image-20240221183326245.png](http://xherlock.top/usr/uploads/2024/02/1805056515.png) 重新检查发现下面代码还存在问题: ~~~assembly .text:08048848 A1 88 A0 04 08 mov eax, ds:dword_804A088 .text:0804884D 85 C0 test eax, eax .text:0804884F 74 15 jz short loc_8048866 ; eax是否为0来判断 .text:0804884F .text:08048851 .text:08048851 loc_8048851: ; CODE XREF: .text:08048857↓j .text:08048851 66 B8 EB 05 mov ax, 5EBh .text:08048855 31 C0 xor eax, eax .text:08048857 74 F9 jz short near ptr loc_8048851+1 ; 一定会跳转 ~~~ 检查dword_804A088引用发现被硬编码为1,因此ZF=0,跳转不成立会接着运行loc_8048851 ![image-20240221192324297.png](http://xherlock.top/usr/uploads/2024/02/1070467894.png) 再由于`xor eax, eax`值一定为0,所以ZF=1,跳转到loc_8048851+1,由于此时进入了原有指令中间,因此U再C重新生成汇编代码 ~~~assembly .text:08048851 66 db 66h ; f .text:08048852 ; --------------------------------------------------------------------------- .text:08048852 .text:08048852 loc_8048852: ; CODE XREF: .text:08048857↓j .text:08048852 B8 EB 05 31 C0 mov eax, 0C03105EBh .text:08048857 74 F9 jz short loc_8048852 ~~~ 由上述代码可知会不断循环(ZF=1,mov不改变ZF)跳转。因此要想办法绕开这个永久循环,可以从最外边的JZ改为IMP(经试验JNZ不行!!!) 修改方法首先右键assemble看JMP对应机器代码EB,然后右键patch-change bytes(下图是改成JNZ的,懒得换图了) ![image-20240221205805987.png](http://xherlock.top/usr/uploads/2024/02/3283821596.png) 最后对上面所有db 0E8h进行NOP,不然ida仍无法生成伪代码;此时F5可以看到反编译出的伪代码了 ~~~c unsigned int sub_80487C4() { size_t v0; // eax size_t v1; // eax unsigned __int8 *v3; // [esp+14h] [ebp-74h] unsigned __int8 s; // [esp+18h] [ebp-70h] BYREF _BYTE v5[3]; // [esp+19h] [ebp-6Fh] BYREF unsigned int v6; // [esp+7Ch] [ebp-Ch] v6 = __readgsdword(0x14u); memset(&s, 0, 0x14u); read(0, &s, 0x14u); if ( !strncmp((const char *)&s, "F1@gA", 5u) ) { puts("You are very close! Now patch me~"); v0 = strlen((const char *)&s); v3 = (unsigned __int8 *)MD5(v5, v0, 0); sub_804875D(v3, 0x10u); } else { v1 = strlen((const char *)&s); sub_804875D(&s, v1 - 1); } fflush(stdout); return __readgsdword(0x14u) ^ v6; } ~~~ 最后还要说,什么破代码对环境需求这么高,死活搞不定环境,只能静态分析咋加密的,分析又晕了,直接拿`F1@gA`md5加密不对,直到查看到平台上其他大佬的分析 > 反编译+patch后核心代码 > > ``` > memset(cStack_74,0,0x14); > read(0,cStack_74,0x14); > iVar2 = strncmp((char *)cStack_74,"F1@gA",5); > if (iVar2 == 0) { > puts("You are very close! Now patch me~"); > sVar3 = strlen((char *)cStack_74); > puVar4 = MD5(cStack_74 + 1,sVar3,(uchar *)0x0); > FUN_0804875d_base16(puVar4,0x10); > } > ``` > > 已知输入的字符串是"F1@gA",但是用read函数读取,末尾加了一个"\n",因此strlen会返回6。因此MD5函数从输入字符串的第二字节开始取6字节,最后一个字节是memset填充的0,因此实际用来算MD5的字符串是`"1@gA\n\x00"`,这样得到的MD5才是程序算出的结果。有点反直觉。 然后最省事的python脚本 ~~~python import hashlib md5_object = hashlib.md5() md5_object.update(b'1@gA\n\x00') md5_result = md5_object.hexdigest() print(md5_result.upper()) # F8C60EB40BF66919A77C4BD88D45DEF4 ~~~ ## re5-packed-movement 首先upx脱壳(命令行)发现ida反编译全是mov,属于movfuscator混淆。找到了github项目demovfuscator,但是题目真的让人头大,好不容易配好了环境,放进去没法跑起来,也找不到原因,用的太少了,项目不太成熟 因此只能采用硬分析去看ida里的数据,因为movfuscator还难以处理复杂c代码,所以原始flag很可能明文存储 首先字符串搜索,查到wrong flag ![image-20240222154312014.png](http://xherlock.top/usr/uploads/2024/02/963660564.png) 查询引用竟然发现有70个,可以猜测是逐字符串对比 ![image-20240222154435155.png](http://xherlock.top/usr/uploads/2024/02/2199825188.png) 根据单个字符比对,必然有一行是mov xx, 'x'这样的格式,去找发现,mov R2开头的都是这种格式。Alt+B进行二进制搜索 ![image-20240222164306814.png](http://xherlock.top/usr/uploads/2024/02/3794817468.png) 结果正是flag: ![image-20240222164341014.png](http://xherlock.top/usr/uploads/2024/02/2301118912.png) 使用idapython编写脚本提取(新版本的idc) ![image-20240222165255918.png](http://xherlock.top/usr/uploads/2024/02/1678809385.png) ~~~python start = 0x080493DB end = 0x0805F8CC flag = '' for i in range(end-start+1): if get_wide_dword(start+i) == 0x206805c7: flag += chr(get_wide_byte(start+i+6)) print(flag) ~~~ ## 76号 upx+ida+花指令(找到有问题的jmp UC大法,nop掉0xeb),创建函数得到如下main代码 ~~~c int __cdecl main() { int v0; // ebx int v2; // [esp+18h] [ebp-Ch] BYREF void *v3; // [esp+1Ch] [ebp-8h] BYREF v2 = 0; v3 = 0; __printf_chk(1, "Password: "); v0 = getline(&v3, &v2, stdin); if ( v0 >= 0 && (v0 = sub_8048580((int)v3, 0)) != 0 ) __printf_chk(1, "Correct!"); else __printf_chk(1, "Incorrect!"); free(v3); return v0; } ~~~ 核心判断是sub_8048580函数,需要保证返回值不为0 ~~~c _BOOL4 __cdecl sub_8048580(int a1, int a2) { char v3; // al _BOOL4 result; // eax char v5[128]; // [esp+Ch] [ebp-A0h] BYREF unsigned int v6; // [esp+8Ch] [ebp-20h] v6 = __readgsdword(0x14u); while ( 1 ) // 要想办法使得return不为0 { memset(v5, 0, sizeof(v5)); v3 = *(_BYTE *)(a1 + a2); // a2相当于偏移值或index,a1基地址 v5[(v3 + 64) % 128] = 1; switch ( v3 ) // 看字符ascii值跳转 { case '\n': return a2 == 13 && v5[74] != 0; // 必须到达字符串结尾且返回不为0 case '0': if ( a2 || !v5[112] ) // v3='0'时v5[112]=v5[('0'+64)%128]=1恒成立 return 0; a2 = 1; continue; case '1': if ( a2 == 14 && v5[113] ) goto LABEL_12; return 0; case '2': if ( a2 == 20 && v5[114] ) goto LABEL_15; return 0; case '3': if ( a2 != 89 || !v5[115] ) return 0; a2 = 90; continue; case '4': if ( a2 != 15 || !v5[116] ) return 0; a2 = 16; continue; case '5': if ( a2 != 14 || !v5[117] ) return 0; LABEL_12: a2 = 15; continue; case '6': if ( a2 != 12 || !v5[118] ) return 0; a2 = 13; continue; case '7': if ( a2 != 5 || !v5[119] ) return 0; a2 = 6; continue; case '8': result = 0; if ( v5[121] ) return a2 == 33 || a2 == 2; return result; case '9': if ( a2 != 1 || !v5[121] ) return 0; a2 = 2; continue; case 'a': if ( a2 != 35 || !v5[33] ) return 0; a2 = 36; continue; case 'b': if ( a2 != 11 || !v5[34] ) return 0; a2 = 12; continue; case 'c': if ( a2 != 32 || !v5[33] ) return 0; a2 = 33; continue; case 'd': if ( a2 != 3 || !v5[36] ) return 0; a2 = 4; continue; case 'e': if ( a2 != 7 || !v5[37] ) return 0; a2 = 8; continue; case 'f': if ( !v5[38] || a2 != 8 && a2 != 4 ) return 0; goto LABEL_53; case 'g': return a2 == 12 && v5[52] != 0; case 'h': if ( a2 != 13 || !v5[39] ) return 0; a2 = 14; continue; case 'i': if ( a2 != 9 || !v5[41] ) return 0; a2 = 10; continue; case 'j': if ( a2 != 10 || !v5[42] ) return 0; a2 = 11; continue; case 'k': return a2 == 12 && v5[43] != 0; case 'l': if ( a2 != 19 || !v5[44] ) return 0; a2 = 20; continue; case 'm': if ( a2 != 17 || !v5[45] ) return 0; a2 = 18; continue; case 'n': return a2 == 18 && v5[45] != 0; case 'o': if ( !v5[46] || a2 != 6 && a2 != 28 ) return 0; LABEL_53: ++a2; continue; case 'p': if ( a2 != 30 || !v5[48] ) return 0; a2 = 31; break; case 'q': if ( a2 != 29 || !v5[49] ) return 0; a2 = 30; break; case 'r': if ( a2 != 20 || !v5[50] ) return 0; LABEL_15: a2 = 21; break; case 's': if ( a2 != 25 || !v5[51] ) return 0; a2 = 26; break; case 't': return a2 == 24 && v5[50] != 0; case 'u': if ( a2 != 26 || !v5[53] ) return 0; a2 = 27; break; case 'v': if ( a2 != 2 || !v5[54] ) return 0; a2 = 3; break; case 'w': if ( a2 != 6 || !v5[55] ) return 0; a2 = 7; break; case 'x': if ( a2 != 22 || !v5[56] ) return 0; a2 = 23; break; case 'y': if ( a2 != 23 || !v5[57] ) return 0; a2 = 24; break; case 'z': return a2 == 21 && v5[33] != 0; default: return 0; } } } ~~~ 如上sub_8048580是一个冗长的switch判断,但是已知开头a2=0,结尾为'\n',可以手动推密码 开始a2=0,因此只能a1[a2]='0',其他的都会返回0,此时经过switch a2=1,再去找满足a2=1时不返回0的判断分支,依次类推 但是到最后一位时即'b'后,a2=12此时存在好几种同时满足条件的值,尝试后发现'g'是正确的(flag{09vdf7wefijbg}) 题目有bug,存在多种情况可以得到password correct ![image-20240222182725526.png](http://xherlock.top/usr/uploads/2024/02/2167474616.png) ## Windows_Reverse2-DDCTF 重点是Aspack手动脱壳,发现之前也是ollydbg脱不了壳,使用x32dbg就可以 1. 载入exe F9 运行直到pushad ![image-20240224084922709.png](http://xherlock.top/usr/uploads/2024/02/654042536.png) 2. F8到下一句call,并下硬件断点(根据ESP定律) ![image-20240224085224002.png](http://xherlock.top/usr/uploads/2024/02/1401197104.png) 3. F9运行直到硬件断点处,此时看到的jne+push+ret是aspack的标志 ![image-20240224085324111.png](http://xherlock.top/usr/uploads/2024/02/4183257887.png) 4. F8直到ret返回,此时是一个call+jump,此处为真正的程序入口点(OEP) ![image-20240224085458073.png](http://xherlock.top/usr/uploads/2024/02/3232225865.png) 5. 在此处dump内存到文件,x32dbg的Scylla用来dump的教程:https://www.cnblogs.com/-rvy-/p/16927032.html 首先插件打开scylla,先dump到一个新文件,再分别点击左侧的IAT Autosearch和Get Imports,最后点击右侧的Fix Dump和Dump,会自动保存为之前dump文件名+_SCY的文件,此时还不能正常运行,根据https://blog.csdn.net/weixin_53349587/article/details/123454162的做法,使用CFF Explorer可以修复程序重定位信息 ![image-20240224182617689.png](http://xherlock.top/usr/uploads/2024/02/3195679456.png) 6. 打开ida发现已脱壳 ~~~c int __cdecl main(int argc, const char **argv, const char **envp) { char Buffer[1024]; // [esp+8h] [ebp-C04h] BYREF char v5[1024]; // [esp+408h] [ebp-804h] BYREF char v6[1024]; // [esp+808h] [ebp-404h] BYREF memset(v5, 0, sizeof(v5)); memset(v6, 0, sizeof(v6)); printf("input code:"); scanf("%s", v5); if ( !sub_6611F0(v5) ) // 先验证v5 { printf("invalid input\n"); exit(0); } sub_661240(v5, (int)v6); // v5加密成v6 memset(Buffer, 0, sizeof(Buffer)); sprintf(Buffer, "DDCTF{%s}", v6); // v6 = 'reverse+' if ( !strcmp(Buffer, aDdctfReverse) ) // 'DDCTF{reverse+}' printf("You've got it !!! %s\n", Buffer); else printf("Something wrong. Try again...\n"); return 0; } char __usercall sub_6611F0@<al>(const char *a1@<esi>) { int v1; // eax int v2; // edx int v3; // ecx char v4; // al v1 = strlen(a1); v2 = v1; if ( v1 && v1 % 2 != 1 ) // v1偶数,说明长度为偶数位 { v3 = 0; if ( v1 <= 0 ) return 1; while ( 1 ) { v4 = a1[v3]; if ( (v4 < '0' || v4 > '9') && (v4 < 'A' || v4 > 'F') ) // 字符范围 break; if ( ++v3 >= v2 ) return 1; // 必须在这里返回 } } return 0; } ~~~ 分析sub_661240函数,a1是我们输入的字符串,a2是准备接收的字符串 ~~~c int __usercall sub_661240@<eax>(const char *a1@<esi>, void *a2) { int v2; // edi int v3; // edx char v4; // bl char v5; // al char v6; // al unsigned int v7; // ecx char v9; // [esp+Bh] [ebp-405h] char v10[1024]; // [esp+Ch] [ebp-404h] BYREF v2 = strlen(a1); memset(v10, 0, sizeof(v10)); v3 = 0; if ( v2 > 0 ) { v4 = v9; do // 都是字符串转十六进制数字 { v5 = a1[v3]; // 奇数位 if ( (unsigned __int8)(v5 - '0') > 9u ) { if ( (unsigned __int8)(v5 - 'A') <= 5u ) v9 = v5 - 55; } else { v9 = a1[v3] - 48; } v6 = a1[v3 + 1]; // 偶数位 if ( (unsigned __int8)(v6 - '0') > 9u ) { if ( (unsigned __int8)(v6 - 'A') <= 5u ) v4 = v6 - 55; } else { v4 = a1[v3 + 1] - 48; } v7 = (unsigned int)v3 >> 1; // 0...len/2-1 v3 += 2; v10[v7] = v4 | (16 * v9); // 相当于16*v9+v4 } while ( v3 < v2 ); } return sub_661000(v2 / 2, a2); } ~~~ 由上分析可知,v10长度为我们输入的一半,每次取两个字符做一个加密运算,但是分析sub_661000发现只传入了一半长度值和未被赋值的a2 结合代码和汇编发现v10被赋值给了ecx,同样传到了函数中,只是ida未识别出来 ![image-20240224115315062.png](http://xherlock.top/usr/uploads/2024/02/3816443154.png) 如下图,ecx正是v2,赋给了v4,后面都是通过v4进行赋值比较 ![image-20240224115451863.png](http://xherlock.top/usr/uploads/2024/02/2360000575.png) ~~~c int __cdecl sub_661000(int a1, void *a2) { char *v2; // ecx int v3; // ebp char *v4; // edi int v5; // esi unsigned __int8 v6; // bl int i; // esi int v8; // edi int v9; // edi size_t v10; // esi void *v11; // edi const void *p_Src; // eax unsigned __int8 v14; // [esp+14h] [ebp-38h] BYREF unsigned __int8 v15; // [esp+15h] [ebp-37h] unsigned __int8 v16; // [esp+16h] [ebp-36h] char v17; // [esp+18h] [ebp-34h] char v18; // [esp+19h] [ebp-33h] char v19; // [esp+1Ah] [ebp-32h] char j; // [esp+1Bh] [ebp-31h] void *v21; // [esp+1Ch] [ebp-30h] char v22[4]; // [esp+20h] [ebp-2Ch] BYREF void *Src; // [esp+24h] [ebp-28h] BYREF size_t Size; // [esp+34h] [ebp-18h] unsigned int v25; // [esp+38h] [ebp-14h] int v26; // [esp+48h] [ebp-4h] v3 = a1; v4 = v2; v21 = a2; std::string::string(v22); v5 = 0; v26 = 0; if ( a1 ) { do { *(&v14 + v5) = *v4; v6 = v15; ++v5; --v3; ++v4; if ( v5 == 3 ) { v17 = v14 >> 2; v18 = (v15 >> 4) + 16 * (v14 & 3); v19 = (v16 >> 6) + 4 * (v15 & 0xF); j = v16 & 0x3F; for ( i = 0; i < 4; ++i ) std::string::operator+=(v22, (unsigned __int8)byte_663020[(unsigned __int8)*(&v17 + i)] ^ 0x76); v5 = 0; } } while ( v3 ); if ( v5 ) { if ( v5 < 3 ) { memset(&v14 + v5, 0, 3 - v5); v6 = v15; } v18 = (v6 >> 4) + 16 * (v14 & 3); v17 = v14 >> 2; v19 = (v16 >> 6) + 4 * (v6 & 0xF); v8 = 0; for ( j = v16 & 0x3F; v8 < v5 + 1; ++v8 ) std::string::operator+=(v22, (unsigned __int8)byte_663020[(unsigned __int8)*(&v17 + v8)] ^ 0x76); // byte_663020为64位 if ( v5 < 3 ) { v9 = 3 - v5; do { std::string::operator+=(v22, '='); // base64的特征 --v9; } while ( v9 ); } } } v10 = Size; v11 = v21; memset(v21, 0, Size + 1); p_Src = Src; if ( v25 < 0x10 ) p_Src = &Src; memcpy(v11, p_Src, v10); v26 = -1; return std::string::~string(); } ~~~ 发现函数应该是base64加密,同时编码表做了替换,但是异或0x76即可变成正常编码表 ![image-20240224120012193.png](http://xherlock.top/usr/uploads/2024/02/3459304558.png) 最后只需base64解密'reverse+',中间看起来复杂,实际上是混淆的结果,得到十六进制转为大写(题目输入限制了范围) 最后修改:2024 年 02 月 24 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,请随意赞赏