[原創]看雪CTF2017第二題lelfeiCM的writeup

anhkgg發表於2019-02-25


主要有一下幾大點:


0. 定位演算法位置


由於是console程式,並且沒有隱藏字串,透過OD/IDA找到關鍵字串,所在函式就是關鍵演算法函式:


.data:00409058 aWellDone       db 'WELL DONE!',0Ah,0   ; DATA XREF: _main:loc_401257o
.data:00409064 aWrongKey___    db 'WRONG KEY...',0Ah,0 ; DATA XREF: _main+231o
.data:00409072                 align 4
.data:00409074 aKeyFormatError db 'key format error...',0Ah,0 ; DATA XREF: _main+9Ao


其實就在main函式中,然後看獲取輸入之後幹了什麼。

首先檢查輸入長度是不是在8到20之間,不是提示key len error

.text:00401066                 cmp     ecx, 8
.text:00401069                 jl      loc_40127A
.text:0040106F                 cmp     ecx, 14h
.text:00401072                 jg      loc_40127A
.text:00401078                 xor     esi, esi
.text:0040107A                 xor     edx, edx
.text:0040107C                 test    ecx, ecx

是不是都是數值,不是就提示key format error...

.text:00401082                 jle     short loc_4010AC
.text:00401084
.text:00401084 loc_401084:                             ; CODE XREF: _main+94j
.text:00401084                 mov     al, [esp+edx+4138h+key]
.text:00401088                 cmp     al, 30h
.text:0040108A                 jle     short loc_401090
.text:0040108C                 cmp     al, 39h
.text:0040108E                 jle     short loc_401091
.text:00401090
.text:00401090 loc_401090:                             ; CODE XREF: _main+8Aj
.text:00401090                 inc     esi
.text:00401091
.text:00401091 loc_401091:                             ; CODE XREF: _main+8Ej
.text:00401091                 inc     edx
.text:00401092                 cmp     edx, ecx
.text:00401094                 jl      short loc_401084
.text:00401096                 test    esi, esi
.text:00401098                 jz      short loc_4010AC
.text:0040109A                 push    offset aKeyFormatError ; "key format error...\n"
.text:0040109F                 call    f_printf_401BE0


下面接著就是演算法的重要部分了,一看到下面的函式,就知道有點小類結構了

.text:004012C0 ; KEY_OBJ1 *__thiscall f_keyobj_init_4012C0(KEY_OBJ1 *this)
.text:004012C0 f_keyobj_init_4012C0 proc near          ; CODE XREF: _main+B3p
.text:004012C0                                         ; f_keyobj_calc_mul_401730+29p ...
.text:004012C0                 push    esi
.text:004012C1                 mov     esi, ecx
.text:004012C3                 mov     dword ptr [esi], offset off_4080C8
.text:004012C9                 call    ds:GetTickCount
.text:004012CF                 mov     ecx, esi
.text:004012D1                 mov     [esi+200Ch], eax
.text:004012D7                 mov     [esi+2008h], eax
.text:004012DD                 call    f_keyobj_init_seed1_401A60
.text:004012E2                 mov     eax, esi
.text:004012E4                 pop     esi
.text:004012E5                 retn
.text:004012E5 f_keyobj_init_4012C0 endp


1. 演算法類結構分析,各類函式的功能分析


先把類結構大致整理出來,方面後續分析

00000000 KEY_OBJ1        struc ; (sizeof=0x2010) ; XREF: _mainr
00000000                                         ; f_keyobj_calc_mul_401730r
00000000 vtable_4080C8   dd ?
00000004 cur_calc_pos    dd ? //結果長度
00000008 seed_array_1024_1 dd 1024 dup(?) //儲存key的值
00001008 seed_array_1024 dd 1024 dup(?) //儲存序號
00002008 TickCnt_key_seed dd ?
0000200C TickCnt1        dd ?
00002010 KEY_OBJ1        ends

然後就是幾個關鍵函式:

1.1 初始化資料

.text:00401A60 ; char *__thiscall f_keyobj_init_seed1_401A60(KEY_OBJ1 *this)
...
.text:00401A8F                 call    f_kyeobj_getindex_4019E0 //更加GetTickCount獲取隨機index,用於打亂序號的順序增加分析難度
...
.text:00401ABA                 mov     esi, [ecx]
.text:00401ABC                 sub     ecx, 4
.text:00401ABF                 mov     [eax], esi
.text:00401AC1                 add     eax, 4
.text:00401AC4                 dec     edx

這個地方首先就想到了每次GetTickCount不一樣,那麼演算法怎麼保證結果相同呢,便想到肯定跟index順序無關,後面驗證果然是,我就把401A60給patch了一下,然初始化的序號結構沒有打亂順序,保持0-0x3ff,如下

//nop了00401A8F呼叫的迴圈部分                 
.text:00401A8F                 call    f_kyeobj_getindex_4019E0 
//這裡其實就是seed_array_1024[1023],不讓它倒過來賦值,修改為lea     ecx, [esi+1008h]
.text:00401AAE                 lea     ecx, [esi+2004h]


這樣之後,就可以很方便檢視資料變換,觀察這兩個欄位即可

00000004 cur_calc_pos    dd ? //結果長度
00000008 seed_array_1024_1 dd 1024 dup(?) //儲存key的值


後面所有相關函式中有關index轉換的也不用關注,因為他變來變去都是0-0x3ff,就只需要關注具體資料操作了。

然後其他函式功能分析也就簡單了。

下面簡單列一下,不做詳細說明了(很簡單,就是陣列操作過來過去的)

.text:004014E0 ; int __thiscall f_keyojb_key1_4014E0(void *this, const char *key) //將輸入的key儲存到seed_array_1024_1 中,字元轉為數值,每個值存一個dword
.text:00401970 ; void __thiscall f_keyobj_key1_s2_401970(KEY_OBJ1 *this) //數值大於10,取餘存當前index位置,取商和index+1位置求和儲存,其實就是進位處理(後面才醒悟)
.text:00401730 ; signed int __userpurge f_keyobj_calc_mul_401730@<eax>(int a1@<eax>, int keyobj0@<ecx>, signed int a3)//用a3取商做右位移,a3取餘做加法,其實就是做乘法運算
text:00401840 ; signed int __userpurge f_keyobj_mul2_401840@<eax>(int a1@<eax>, int a2@<ecx>, KEY_OBJ1 *a3)//兩個KEY_OBJ做乘法


2. 醒悟演算法究竟是個什麼玩意


輸入的key關鍵處理部分

.text:004010E0                 push    9
.text:004010E2                 lea     ecx, [esp+413Ch+keyobj]
.text:004010E9                 call    f_keyobj_calc_mul_401730 ;
...
.text:0040110B                 lea     eax, [esp+4138h+keyobj1]
.text:00401112                 lea     ecx, [esp+4138h+keyobj]
.text:00401119                 push    eax
.text:0040111A                 mov     byte ptr [esp+413Ch+var_4], 1
.text:00401122                 call    f_keyobj_mul2_401840
...
.text:00401127                 push    9
.text:00401129                 lea     ecx, [esp+413Ch+keyobj]
.text:00401130                 mov     esi, eax
.text:00401132                 call    f_keyobj_calc_mul_401730 ;

先前想著輸入的key用9做位移,做加法,幹麼呢...一直繞不清,後來重新看f_keyobj_key1_s2_401970,覺得是進位處理,一下子就靈光了,這是實現乘法運算(1024位的乘法,真實折騰,nb)。

這樣演算法也基本清楚了。

key*9*key*9*(...) => result


怎麼校驗的呢?

1. 計算結果長度必須是奇數

.text:00401154                 call    f_keyobj_curpos_4013A0
.text:00401159                 and     eax, 80000001h
.text:0040115E                 jns     short loc_401165
.text:00401160                 dec     eax
.text:00401161                 or      eax, 0FFFFFFFEh
.text:00401164                 inc     eax
.text:00401165
.text:00401165 loc_401165:                             ; CODE XREF: _main+15Ej
.text:00401165                 cmp     eax, 1

2. result[len/2] == key[0]

.text:00401175                 call    f_keyobj_curpos_4013A0
.text:0040117A                 sar     eax, 1
.text:0040117C                 push    eax
.text:0040117D                 lea     ecx, [esp+413Ch+keyobj]
.text:00401184                 call    f_keyobj_check1_4013B0
.text:00401189                 push    0
.text:0040118B                 lea     ecx, [esp+413Ch+keyobj1]
.text:00401192                 mov     edi, eax
.text:00401194                 call    f_keyobj_check1_4013B0
.text:00401199                 cmp     edi, eax
.text:0040119B                 lea     ecx, [esp+4138h+keyobj1]
.text:004011A2                 jnz     short loc_40121C

3. 高位部分和key相同(跳過比較那個位元組)

.text:004011D0                 lea     ecx, [esp+4144h+keyobj1]
.text:004011D7                 push    esi
.text:004011D8                 push    ecx
.text:004011D9                 lea     ecx, [esp+414Ch+keyobj]
.text:004011E0                 call    f_keyobj_check2_4013E0

4. 低位部分和key逆序(跳過比較那個位元組)

text:004011F6                 lea     edx, [esp+413Ch+keyobj1]
.text:004011FD                 push    eax
.text:004011FE                 push    1
.text:00401200                 push    0
.text:00401202                 push    edx
.text:00401203                 lea     ecx, [esp+414Ch+keyobj]
.text:0040120A                 call    f_keyobj_check2_4013E0


感覺結果應該是這一個樣子的:

1234567->1234567654321 //中間因為長度折騰了好久,後面查了才知道這是迴文數,翻半天沒有什麼演算法,指令碼已經跑起來了

怎麼求逆呢?演算法不好,那就指令碼跑吧!


3. 指令碼跑

i = 11111111#
while True:
    break
    is1 = str(i)
    is2_len = len(is1) 
    
    is1 = is1[:is2_len-1]
    is2 = is1[::-1]
    
    k = i*9*9*i
        
    ks1 = ''
    ks = ''
    while True:
       
        #print i, k
        #break
        ks1 = str(k)
        
        lll = len(ks1)/2
        
        if len(ks1) > 2*is2_len:
            #print 'long out - 1', i, len(ks1), 2*is2_len
            break
        
        if (is2_len + len(ks1))>1024:
            #print 'long out - 1', i, is2_len + len(ks1)
            break
        
        if (len(ks1)%2!=0) and (is1[0:1] == ks1[lll:lll+1]):
            print  'get -success1 > ', i, is1, k
            break

        if len(ks1)>1024:
            #print 'long out', i
            break 
        
        k = k * i*9

    ls2_len1 = is2_len-1
    ks = ks1[:ls2_len1]
    
    if ((is1 == ks) and (is2 == ks1[(-1*ls2_len1):])):
        print  'get -success > ', i, is1, k
        print ''
    
    i += 1
    
    if i % 10000000 == 0:
        print '...', i
    
    if i > 99999999999999999999:
        break


4. 總結

結果最後跑出來是

get -success >  12345679 1234567 12345678987654321

因為程式碼中處理字元存為數值是倒著的,所以key應該是97654321






[推薦]看雪企服平臺,提供安全分析、定製專案開發、APP等級保護、滲透測試等安全服務!

相關文章