该CrackMe要求输入合适的六位数字,正确的话会输出flag.

  1. 可以通过E Debug Event得到关键函数的位置,或者是查找对字符串Right的引用,定位到函数sub40173A()。
  2. 通过动态调试,可知sub401CE7用于获得输入框的数据.
  3. 通过跟踪,sub401CDB用于调用一个函数指针,虽然IDA识别该函数有3个参数,但实际上,sub401CDB()的原型应该是:
    sub401CDB(void *func, int argCnt,  
     int64 arg0_Value, int arg0_Type,  
     int64 arg1_Value, int arg1_Type, ...)

    即sub401CDB可以接受不定长的参数。此外func的原型应有如下形式:

    func(int64 *retValue, int argCnt, Arg *argVector);

    Arg可以视为

    struct Arg {
     int64 arg1_Value;  
     int arg1_Type;  
    }
  4. sub4021D0函数将输入的字符串转为类C++格式的字符串:
    00000000 vcstr           struc ; (sizeof=0x9, mappedto_160)
    00000000 field_0         dd ?
    00000004 len             dd ?
    00000008 cstr            db ?
    00000009 vcstr           ends

    5中的toDouble还用到以下数据结构:

    00000000 vcvec struc ; (sizeof=0x14, mappedto_163) ; XREF: toDouble/r
    00000000 field_0 dd ?                            ; XREF: toDouble+72/w
    00000004 field_4 dd ?
    00000008 cstr dd ?                               ; XREF: toDouble+57/r
    0000000C maxLen dd ?
    00000010 len dd ?                                ; XREF: toDouble+53/r
    00000014 vcvec ends
  5. sub40173A()调用获得输入字符串的MD5值,之后设置dword_48FDE4为0,调用sub44C9B0()检查是否收到消息(使用PeekMessageA)。当dword_48FDE4为1时,停止检查。查找对dword_48FDE4的引用,到函数sub4010F2()。
    下面的函数符合sub401CDB的参数要求,对几个关键函数的参数argVector按C语言形式进行转换,其等价形式如下:

    sub0402000:
    char getItem(char s[], int i); // 返回字符串s的s[i], i以1为基
    sub0401FD0:
    char* ch2Str(char c); // 将c转为字符串格式
    sub0401F70:
    char* subStr(char s[], int base, int len); // base以1为基
    sub0401E30:
    double fmod(double f, double div0, double div1); // 根据参数的个数,对f进行连续取余,返回余数,如f % div0 % div1 % ...  
    sub0402330:
    char* toStr(Type v); // 泛型toString
    sub0402070:
    double toDouble(Type v); // 泛型toDouble
    sub0448C30:
    char* md5(vcstr *str) // 计算字符串vcstr的md5值,并返回该值的字符串(32字节长)

    其它函数:

    sub0401004:
    double double2Int(double v)
    sub0401096:
    char* concat(int n@ecx, char* str0, char* str1); // 返回n个字符串连接

    函数sub4010F2()对md5字符串进行各种运算后,将结果的指针保存到0x048FDE8处。

  6. sub40173A()对5得到的字符串进行运算后生成Key。
    使用该Key对0x046EDFA处的数据进行解密

    sub_449010:
    char* des(ECipher *cipher, char *key, int unk1); // DES解密
    .rdata:0046EDFA dword_46EDFA dd 1                       ; DATA XREF: sub_40173A+37B
    .rdata:0046EDFE len dd 30h
    .rdata:0046EE02 data db 0Bh, 0Dh, 0Eh, 7Fh, 1, 52h, 1Ch, 65h, 0F8h, 9Dh, 66h, 0EFh, 96h, 3Ch
    .rdata:0046EE02 db 3Fh, 97h, 0E1h, 19h, 59h, 0Bh, 0F0h, 56h, 73h, 2Dh, 48h, 55h, 2Dh, 97h
    .rdata:0046EE02 db 2Dh, 59h, 44h, 93h, 36h, 43h, 0D4h, 0ABh, 12h, 0A5h, 0B8h, 0EFh, 80h
    .rdata:0046EE02 db 8Fh, 0Bh, 87h, 53h, 1Fh, 44h, 55h

    实际被解密数据为0x0046EE02处的长0x30的字符串。
    与一般的DES不同,易语言的DES加密,加密前会在数据前面插入数据的长度,加密的密钥按genDESkey()进行变换。解密后的字符串以"CTF"开头。
    在分析完代码后即可写出如下爆破脚本。

from hashlib import md5
from itertools import product

def md5dig(b):
    hasher = md5()
    hasher.update(b)
    return hasher.hexdigest()

def atof(s):
    n_str = ""
    for c in s:
        if c.isdigit():
            n_str += c
        else:
            break
    if len(n_str) == 0:
        n_str = "0.0"
    return float(n_str)

def getkey(p):
    d = md5dig(p.encode()).encode()
    # -------sub4010F2-start
    s = (d[0] + d[5] + d[7]) // 3
    s1 = chr(s)

    n1 = atof(d[1: 1 + 4].decode())
    n2 = 3

    r1 = n1 % n2
    s2 = str(int(r1))

    r2 = atof(d[13: 13 + 2].decode()) % 26.0
    s3 = chr(int(r2 + 60.0))

    v41 = 0xFFFFF6B4
    for i in d:
        v41 += i

    s4 = chr(v41 & 0xff)

    d2 = md5dig(d)
    r3 = float(ord(d2[0])) % 90.0
    s5 = str(int(r3))

    v4 = s1 + s2 + s3 + s4 + s5
    # -------sub4010F2-end v4 = [0x048FDE8]
    # -------sub40173A-start
    s6 = v4[0]
    s7= chr(int(ord(v4[1]) + 72.0))
    s8 = v4[2: 2 + 3]
    key = s6 + s7 + s8
    # -------sub40173A-end
    return bytes(map(ord, key))



from Crypto.Cipher import DES
from string import printable
from struct import unpack

def genDESkey(eKey):
    buf = [0] * 8
    i = 0
    for b in eKey:
        buf[i] ^= b
        i += 1
        i %= 8
    k = bytes(buf)
    k = bswp(k) # 因为DES标准没有对位的顺序规定,易语言与Crypto库的不一致,故变换
    return k


def tryDecrypt(key, cipher):
    des = DES.new(key)

    plain_bytes = des.tryDecrypt(cipher)
    plain_len = unpack("<i", plain_bytes[:4])[0]
    try:
        # print(plain_bytes[4: 4 + 3].decode())
        if plain_bytes[4: 4 + 3].decode() == "CTF":
            print(plain_bytes)
            return True
    except:
        return False
    return False

# 0x0046EE02
secret = bytes([
    0x0B, 0x0D, 0x0E, 0x7F, 0x01, 0x52, 0x1C, 0x65, 0xF8, 0x9D, 0x66, 0xEF, 0x96, 0x3C, 0x3F, 0x97,
    0xE1, 0x19, 0x59, 0x0B, 0xF0, 0x56, 0x73, 0x2D, 0x48, 0x55, 0x2D, 0x97, 0x2D, 0x59, 0x44, 0x93,
    0x36, 0x43, 0xD4, 0xAB, 0x12, 0xA5, 0xB8, 0xEF, 0x80, 0x8F, 0x0B, 0x87, 0x53, 0x1F, 0x44, 0x55 
])

# bitswap the bytes in bs
def bswp(bs):
    r = []
    for i in bs:
        bin_s = bin(i)[2:].encode()
        re_s = bin_s[::-1].ljust(8, b'0')
        r.append(int(re_s, 2))
    return bytes(r)

for i in range(1000000):
    p = "%06d" % i
    eKey = getkey(p)
    k = genDESkey(eKey)
    if tryDecrypt(k, secret):
        print(p, k)

Flag:

温馨提示: 此处内容需要评论本文后刷新才能查看,支付2元即可直接查看所有Flag。

小广告:关于获取西普实验吧所有Flag请点击这里查看索引

查看所有Flag文章需要输入密码,需要获取文章密码的童鞋请扫描下面微信或支付宝二维码捐助至少2元(老哥,捐多捐少是个缘分)之后发送支付凭证号联系我获取,Flag大全地址:Flag大全

新功能:捐款的小伙伴请联系我把自己的注册邮箱加入网站白名单,可以免回复看到本站所有Flag

PS:本站不是实验吧的官方站点,纯粹是个人博客,收取Flag费用仅是维持服务器费用,做站不易,且行窃珍惜!

微信二维码:
支付宝二维码: