Reverse(十二)

decode_me

https://ctf.bugku.com/challenges/detail/id/413.html

ida64反编译,如下主函数很简洁明了

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是下标

__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函数,可以看到不对劲,只能去分析汇编代码

// positive sp value has been detected, the output may be wrong!
void sub_401291()
{
  __asm { jmp     off_404388[rax] }    // 要去分析off_404388
}

image-20240206094727473.png

上述汇编代码为

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

loc_4012AC:                           
push    rbx                    ; 入栈,追朔rbx值,可以保存的正是主函数里的数组逐个遍历赋的值
jmp     short $+2            ; 一条语句一般就是2个字节,所以$+2代表下一条指令,由下图可知再次跳转到loc_4012AF

image-20240206103443930.png

每次rax都会+8,因此每次jmp的函数不同;但查看图状视图可以看到除了loc_4012C1最后return其他均回到loc_4012AF

image-20240206103737673.png

下面按顺序分析每个函数作用

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

loc_4012C1:
mov     rax, rbx            ; 将最终数组找到的值赋给rax
pop     rbx                    ; 出栈,恢复主函数里的数组逐个遍历赋的值
retn                        ; 退出函数

综上sub_401291函数作用是,传入文件读取的字符ascii值(buf),取unk_404288[buf]

接下来该计算v3 ^ sub_401240(a2),查看伪代码发现还是很怪,得分析汇编代码

__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);
}

分析汇编代码发现和上一个十分相似,直接全部贴上来

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

综上,上述汇编代码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;
    }
}

脚本逆向

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

# 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写代码,这里我全部做了代码逆向,并附上了测试案例)

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反编译得到核心代码函数

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;
}
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加载分析:

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) {
    }
}
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"

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

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

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打开

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

image-20240208173822329.png

  1. 写python脚本f.open读取exe,略

serial-150-SUCTF

ida64反编译发现伪代码未成功

image-20240221093858384.png

但是在linux里可以正常运行,只好远程linux调试查看,首先在main开头处下断点,启动调试后会停在开头

image-20240221094750063.png

调试过程中发现ida警告RIP指向了已经定义指令的地址,合理猜测这是伪代码无法生成的原因

image-20240221094948112.png

同意yes然后F8会重新编译出正确代码,如下可以看到字符串提示输入key,以及打印指令

image-20240221095615938.png

接着又会碰到警告RIP,同样做法,同时也会发现中间有部分db代码无用

image-20240221095859177.png

接着运行需要输入key,linux输入1234,F8发现输入已入栈

image-20240221100208309.png

再次让ida修复RIP指向地址指令,如下,作用是获取输入key长度和16比较(合理猜测输入长度为16,否则就不会跳转)

.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

重新运行,输入abcd...,够16位跳转后的汇编代码如下,发现提取了key第一个字符和'E'比较,合理猜测首字母为E

.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比较

.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标志位跳转,如下,发现回到了第二个字符比较,和前面逻辑一样

.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

; 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

call的地址很不正常,因此按U取消定义转为机器代码,这时伪代码可以看到跳转地址0x80487C1

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

下方unwind处有大量数据,在unwind上按下C转为code,如下,发现前半部分恢复成功,但是中间jmp指令跳到了自身中间,因此按下U将其转为机器码

image-20240221182403682.png

看到了0xEB可知其是一个花指令,因此在其后按下C转为c代码

image-20240221182842531.png

依然出现了奇怪jmp地址,但是上面的jz恒成立跳转,因此不再转为机器代码,同时发现后面mov里引用了这个地址的数据,因此按D转为数据

image-20240221182931614.png

再往后看发现还有一处标红,同样操作转为正确代码

image-20240221183250285.png

image-20240221183326245.png

重新检查发现下面代码还存在问题:

.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

再由于xor eax, eax值一定为0,所以ZF=1,跳转到loc_8048851+1,由于此时进入了原有指令中间,因此U再C重新生成汇编代码

.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

最后对上面所有db 0E8h进行NOP,不然ida仍无法生成伪代码;此时F5可以看到反编译出的伪代码了

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@gAmd5加密不对,直到查看到平台上其他大佬的分析

反编译+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脚本

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

查询引用竟然发现有70个,可以猜测是逐字符串对比

image-20240222154435155.png

根据单个字符比对,必然有一行是mov xx, 'x'这样的格式,去找发现,mov R2开头的都是这种格式。Alt+B进行二进制搜索

image-20240222164306814.png

结果正是flag:

image-20240222164341014.png

使用idapython编写脚本提取(新版本的idc)

image-20240222165255918.png

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代码

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

_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

Windows_Reverse2-DDCTF

重点是Aspack手动脱壳,发现之前也是ollydbg脱不了壳,使用x32dbg就可以

  1. 载入exe F9 运行直到pushad

image-20240224084922709.png

  1. F8到下一句call,并下硬件断点(根据ESP定律)

image-20240224085224002.png

  1. F9运行直到硬件断点处,此时看到的jne+push+ret是aspack的标志

image-20240224085324111.png

  1. F8直到ret返回,此时是一个call+jump,此处为真正的程序入口点(OEP)

image-20240224085458073.png

  1. 在此处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

  1. 打开ida发现已脱壳
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是准备接收的字符串

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

如下图,ecx正是v2,赋给了v4,后面都是通过v4进行赋值比较

image-20240224115451863.png

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

最后只需base64解密'reverse+',中间看起来复杂,实际上是混淆的结果,得到十六进制转为大写(题目输入限制了范围)

最后修改:2024 年 02 月 24 日
如果觉得我的文章对你有用,请随意赞赏