GGLHY 发表于 2011-12-3 12:21:48

山重水复疑无路,柳暗花明又一村!某软件的一波三折OD手记

本帖最后由 GGLHY 于 2012-3-15 15:36 编辑

软件简介:
一款通过简单的打开/保存对话框就可以快速进入你喜爱的文件夹中的shell增强工具。你可以在任何地方通过单击右键打开**********菜单。打开 /保存对话框的“白色区域”在桌面和开始菜单中。在弹出菜单中,有一个新的条目-“快速文件夹”,包含你添加的文件夹列表。这样你就可以快速进入到这些文件夹中。




这软件与N大放出的FF文件(详见西卡学院)结合使用颇有二龙戏珠之感,有互补性哦!


             一、主程序的注册算法分析及验证                                       
                                                 A算法分析
输入注册信息后,利用万能断点的方法,我们可以来到: 004866C7|.E8 D0CEFCFF   CALL fastfold.0045359C
004866CC|.8B45 FC       MOV EAX,DWORD PTR SS:         ;用户名(本例ASCII "GglhyGYGz")
004866CF|.E8 94DCF7FF   CALL fastfold.00404368
004866D4|.83F8 07       CMP EAX,7                              ;用户名长度:7
004866D7|.7D 3E         JGE SHORT fastfold.00486717            ;小于7则错
004866D9|.6A 10         PUSH 10
(...省略部分代码...)
004866FE|.8B45 F4       MOV EAX,DWORD PTR SS:
00486701|.E8 62DEF7FF   CALL fastfold.00404568
00486706|.50            PUSH EAX                               ; |Text
00486707|.E8 E801F8FF   CALL <JMP.&user32.GetActiveWindow>   ; |[GetActiveWindow
0048670C|.50            PUSH EAX                               ; |hOwner
0048670D|.E8 1A04F8FF   CALL <JMP.&user32.MessageBoxA>         ; \MessageBoxA                出错啦!!
00486712|.E9 4F010000   JMP fastfold.00486866
00486717|>8D55 F0       LEA EDX,DWORD PTR SS:

接着F8来到:
0048682A|.8B55 CC       MOV EDX,DWORD PTR SS:          ;用户名(本例ASCII "GglhyGYGz")
0048682D|.8BC3          MOV EAX,EBX
0048682F|.E8 BC020000   CALL fastfold.00486AF0               ;F7
00486834|.8D55 C8       LEA EDX,DWORD PTR SS:
00486837|.8B83 08030000 MOV EAX,DWORD PTR DS:
0048683D|.E8 5ACDFCFF   CALL fastfold.0045359C
00486842|.8B55 C8       MOV EDX,DWORD PTR SS:
00486845|.8D83 38030000 LEA EAX,DWORD PTR DS:
0048684B|.E8 ACD8F7FF   CALL fastfold.004040FC
0048682F|.E8 BC020000   CALL fastfold.00486AF0               ;F700486AF0/$55            PUSH EBP                               ; 局部调用来自 0048682F
(...省略部分代码...)
00486B07|.8B45 F8       MOV EAX,DWORD PTR SS:         ;(ASCII "GglhyGYGz")
00486B0A|.E8 49DAF7FF   CALL fastfold.00404558
00486B0F|.33C0          XOR EAX,EAX
00486B11|.55            PUSH EBP
00486B12|.68 376C4800   PUSH fastfold.00486C37
00486B17|.64:FF30       PUSH DWORD PTR FS:
00486B1A|.64:8920       MOV DWORD PTR FS:,ESP
00486B1D|.837D F8 00    CMP DWORD PTR SS:,0
00486B21|.0F84 F5000000 JE fastfold.00486C1C
00486B27|.8D45 F4       LEA EAX,DWORD PTR SS:
00486B2A|.8B55 F8       MOV EDX,DWORD PTR SS:
00486B2D|.E8 0ED6F7FF   CALL fastfold.00404140
00486B32|.8B45 F4       MOV EAX,DWORD PTR SS:         ;(ASCII "GglhyGYGz")
00486B35|.E8 2ED8F7FF   CALL fastfold.00404368
00486B3A|.8BF8          MOV EDI,EAX                            ;用户名长度
00486B3C|.8BF7          MOV ESI,EDI
00486B3E|.4E            DEC ESI
00486B3F|.85F6          TEST ESI,ESI
00486B41|.7E 22         JLE SHORT fastfold.00486B65
00486B43|.BB 01000000   MOV EBX,1
00486B48|>8D45 F4       /LEA EAX,DWORD PTR SS:
00486B4B|.E8 70DAF7FF   |CALL fastfold.004045C0
00486B50|.8B55 F4       |MOV EDX,DWORD PTR SS:          ;(ASCII "GglhyGYGz")
00486B53|.8A541A FF   |MOV DL,BYTE PTR DS:      ;用户名依次每一位
00486B57|.8B4D F4       |MOV ECX,DWORD PTR SS:
00486B5A|.321419      |XOR DL,BYTE PTR DS:          ;与下一位 xor(最后一位与00 xor)
00486B5D|.885418 FF   |MOV BYTE PTR DS:,DL      ;结果保存起来
00486B61|.43            |INC EBX                               ;计数器EBX + 1
00486B62|.4E            |DEC ESI                               ;计数器ESI - 1
00486B63|.^ 75 E3         \JNZ SHORT fastfold.00486B48         ;用户名是否运算完毕
00486B65|>8D45 F4       LEA EAX,DWORD PTR SS:
00486B68|.E8 53DAF7FF   CALL fastfold.004045C0
00486B6D|.8B55 F4       MOV EDX,DWORD PTR SS:         ;得到结果,设为S1(本例200B04113E1E1E3D7A)    >=z
00486B70|.8A543A FF   MOV DL,BYTE PTR DS:         ;S1的最后2位
00486B74|.8B4D F4       MOV ECX,DWORD PTR SS:         ;S1
00486B77|.3211          XOR DL,BYTE PTR DS:               ;S1最后2位 xor S1第1-2位
00486B79|.885438 FF   MOV BYTE PTR DS:,DL         ;结果替换S1最后2位,设为S2(本例200B04113E1E1E3D5A)
00486B7D|.8D4D F0       LEA ECX,DWORD PTR SS:
00486B80|.8B55 F4       MOV EDX,DWORD PTR SS:         ;S2
00486B83|.8B45 FC       MOV EAX,DWORD PTR SS:
00486B86|.E8 BD000000   CALL fastfold.00486C48               ;很关键的CALL,F7。
00486B8B|.8B55 F0       MOV EDX,DWORD PTR SS:          ;K1
00486B8E|.8D45 F4       LEA EAX,DWORD PTR SS:
00486B91|.E8 AAD5F7FF   CALL fastfold.00404140
00486B96|.8B45 F4       MOV EAX,DWORD PTR SS:         ;K1
00486B99|.E8 CAD7F7FF   CALL fastfold.00404368
00486B9E|.8BD8          MOV EBX,EAX                            ;k1的长度
00486BA0|.83FB 02       CMP EBX,2
00486BA3|.7C 19         JL SHORT fastfold.00486BBE
00486BA5|>8D45 F4       /LEA EAX,DWORD PTR SS:
00486BA8|.E8 13DAF7FF   |CALL fastfold.004045C0
00486BAD|.8B55 F4       |MOV EDX,DWORD PTR SS:          ;K1
00486BB0|.8A541A FE   |MOV DL,BYTE PTR DS:      ;
00486BB4|.885418 FF   |MOV BYTE PTR DS:,DL      ;
00486BB8|.4B            |DEC EBX
00486BB9|.83FB 01       |CMP EBX,1
00486BBC|.^ 75 E7         \JNZ SHORT fastfold.00486BA5         ;这个循环就是复制K1第一位并放在K1第一位,去掉K1最后1位,设为K2
00486BBE|>8B45 F4       MOV EAX,DWORD PTR SS:         ;k2
00486BC1|.E8 A2D7F7FF   CALL fastfold.00404368
00486BC6|.8B55 F4       MOV EDX,DWORD PTR SS:         ;k2
00486BC9|.8A5C02 FF   MOV BL,BYTE PTR DS:         ;k2最后1位
00486BCD|.8D45 F4       LEA EAX,DWORD PTR SS:
00486BD0|.E8 EBD9F7FF   CALL fastfold.004045C0
00486BD5|.8818          MOV BYTE PTR DS:,BL               ;替换K2第1位,设为K3
00486BD7|.8B45 F4       MOV EAX,DWORD PTR SS:         ;k3
00486BDA|.E8 89D7F7FF   CALL fastfold.00404368
00486BDF|.8BF0          MOV ESI,EAX                            ;k3长度
00486BE1|.85F6          TEST ESI,ESI
00486BE3|.7E 27         JLE SHORT fastfold.00486C0C
00486BE5|.BB 01000000   MOV EBX,1
00486BEA|>8D45 F4       /LEA EAX,DWORD PTR SS:
00486BED|.E8 CED9F7FF   |CALL fastfold.004045C0
00486BF2|.8B55 F4       |MOV EDX,DWORD PTR SS:          ;k3
00486BF5|.0FB6541A FF   |MOVZX EDX,BYTE PTR DS:   ;K3依次每一位
00486BFA|.8B0D C80B4A00 |MOV ECX,DWORD PTR DS:         ;呵呵,准备查表咯
00486C00|.8A5411 D0   |MOV DL,BYTE PTR DS:       ;查表
00486C04|.885418 FF   |MOV BYTE PTR DS:,DL      ;查表得结果保存起来
00486C08|.43            |INC EBX
00486C09|.4E            |DEC ESI
00486C0A|.^ 75 DE         \JNZ SHORT fastfold.00486BEA
00486C0C|>8B45 FC       MOV EAX,DWORD PTR SS:         ;哈哈,EAX里是真码。
00486C0F|.05 34030000   ADD EAX,334
00486C14|.8B55 F4       MOV EDX,DWORD PTR SS:         ;真码出现!
00486C17|.E8 E0D4F7FF   CALL fastfold.004040FC
(...省略部分代码...)
00486C43   .5D            POP EBP
00486C44   .C3            RETN
00486B86|.E8 BD000000   CALL fastfold.00486C48;很关键的CALL,F7:00486C48/$55            PUSH EBP
(...省略部分代码...)
00486C80|.8BD8          MOV EBX,EAX
00486C82|.83FB 02       CMP EBX,2
00486C85|.7C 19         JL SHORT fastfold.00486CA0
00486C87|>8D45 FC       /LEA EAX,DWORD PTR SS:
00486C8A|.E8 31D9F7FF   |CALL fastfold.004045C0
00486C8F|.8B55 FC       |MOV EDX,DWORD PTR SS:          ;S2
00486C92|.8A541A FE   |MOV DL,BYTE PTR DS:
00486C96|.885418 FF   |MOV BYTE PTR DS:,DL
00486C9A|.4B            |DEC EBX
00486C9B|.83FB 01       |CMP EBX,1
00486C9E|.^ 75 E7         \JNZ SHORT fastfold.00486C87         ;循环是S2前2位复制放在S2的最前面并去掉最后2位设为S3
00486CA0|>8B45 FC       MOV EAX,DWORD PTR SS:         ;S3
00486CA3|.E8 C0D6F7FF   CALL fastfold.00404368
00486CA8|.8B55 FC       MOV EDX,DWORD PTR SS:         ;S3
00486CAB|.8A5C02 FF   MOV BL,BYTE PTR DS:         ;S3的最后2位
00486CAF|.8D45 FC       LEA EAX,DWORD PTR SS:
00486CB2|.E8 09D9F7FF   CALL fastfold.004045C0
00486CB7|.8818          MOV BYTE PTR DS:,BL               ;替换S3最前面2位,设为S4
00486CB9|.8D55 E8       LEA EDX,DWORD PTR SS:
00486CBC|.8B45 FC       MOV EAX,DWORD PTR SS:
00486CBF|.E8 A8F6FFFF   CALL fastfold.0048636C
00486CC4|.8D45 E8       LEA EAX,DWORD PTR SS:
00486CC7|.8D55 F8       LEA EDX,DWORD PTR SS:
00486CCA|.E8 29F6FFFF   CALL fastfold.004862F8
00486CCF|.8B55 F8       MOV EDX,DWORD PTR SS:         ;出现了一字符串,好像MD5的结果 (本例DBFF942F4365A206C01D94843311CA29)
00486CD2|.8D45 FC       LEA EAX,DWORD PTR SS:
00486CD5|.E8 66D4F7FF   CALL fastfold.00404140
00486CDA|.8BC7          MOV EAX,EDI
00486CDC|.E8 C7D3F7FF   CALL fastfold.004040A8
00486CE1|.8B45 FC       MOV EAX,DWORD PTR SS:         ;(本例DBFF942F4365A206C01D94843311CA29)
00486CE4|.E8 7FD6F7FF   CALL fastfold.00404368
00486CE9|.8BF0          MOV ESI,EAX                            ;长度为32位
00486CEB|.85F6          TEST ESI,ESI
00486CED|.7E 3D         JLE SHORT fastfold.00486D2C
00486CEF|.BB 01000000   MOV EBX,1
00486CF4|>8B45 FC       /MOV EAX,DWORD PTR SS:          ;出现的32位字符串(本例DBFF942F4365A206C01D94843311CA29)
00486CF7|.8A4418 FF   |MOV AL,BYTE PTR DS:      ;依次每一位
00486CFB|.3C 30         |CMP AL,30
00486CFD|.72 29         |JB SHORT fastfold.00486D28
00486CFF|.3C 39         |CMP AL,39
00486D01|.77 25         |JA SHORT fastfold.00486D28
00486D03|.8D45 E4       |LEA EAX,DWORD PTR SS:
00486D06|.8B55 FC       |MOV EDX,DWORD PTR SS:
00486D09|.8A541A FF   |MOV DL,BYTE PTR DS:
00486D0D|.E8 7ED5F7FF   |CALL fastfold.00404290
00486D12|.8B55 E4       |MOV EDX,DWORD PTR SS:
00486D15|.8BC7          |MOV EAX,EDI
00486D17|.E8 54D6F7FF   |CALL fastfold.00404370
00486D1C|.8B07          |MOV EAX,DWORD PTR DS:
00486D1E|.E8 45D6F7FF   |CALL fastfold.00404368
00486D23|.83F8 0A       |CMP EAX,0A
00486D26|.7D 04         |JGE SHORT fastfold.00486D2C
00486D28|>43            |INC EBX
00486D29|.4E            |DEC ESI
00486D2A|.^ 75 C8         \JNZ SHORT fastfold.00486CF4         ;这个循环其实就是取这字符串中的10位数字
00486D2C|>33C0          XOR EAX,EAX                            ;取完得到(本例"9424365206"),设为k1
(...省略部分代码...)
00486D5B   .5D            POP EBP
00486D5C   .C3            RETN
////////////////////////////////////////////////////////////////////////
好,咱先就此算法做一小结
1.用户名不能小于7位
2.依次取用户名每一位与下一位进行XOR运算,得到的结果设为S1
S1最后2位 xor S1前2位,得到的结果替换S1的最后2位,设为S2
S2去掉最后2位,取前2位复制并连接,设为S3
S3最后2位复制并取代S3的最前面2位,设为S4。(随便注意下S4的对应ASC字符)
3.标准MD5运算,得到字符串。并依次取该字符串中的10位数字,得到K1
4.K1去掉最后一位,复制第一位并连接,得到K2
K2最后1位替换K2第一位,设为K3
K3查表得到注册码。
////////////////////////////////////////////////////////////////////////


--------------------------------------------------------------------------
文中的表:
004A00A035 33 37 39 34 32 31 36 30 38         5379421608
--------------------------------------------------------------------------
本例S1
00DF0F34   20 0B 04 11 3E 1E 1E 3D 7A                         >=z

S2
00DF0F34   20 0B 04 11 3E 1E 1E 3D 5A                         >=Z

S3
00DF0F4C   20 20 0B 04 11 3E 1E 1E 3D                         >=

S4
00DF0F4C   3D 20 0B 04 11 3E 1E 1E 3D                     = >=

在MD5运算后才得到K1,而MD5基本是不可逆的,也就是说要想通过注册码倒推出用户名的想法基本是不现实的哈。

特别鸣谢:
文中的32位字符串确实是标准MD5运算的结果。不过刚开始的时候没弄明白是MD5了什么得到这个字符串的。后来与月之精灵大牛交流后才得知其实是MD5(S4的对应的ASC字符)的结果。
本例就是MD5(= >=) 得到:DBFF942F4365A206C01D94843311CA29
在此对月之精灵大神表示感谢!


呵呵,一般说来,能够在这里找到明码(不管是通过算法分析还是直接在内存中找到)总是让人心情愉快的事情!可是...
看来不妙,一旦有了可是,事情就不那么简单了。




                         B、 用户名的检测及要求
    如果输入的注册信息通过了上面A部分的检测,软件会将注册信息保存在注册表中。大家可以自己动手在注册表中看看写了哪些内容。提醒一下,不止一处哦。特别注意@255、@256、1a或a1。
即使保存了上面的注册信息,如果把时间往后调20天以上你会发现什么,呵呵?这个程序有点阴险的地方,并不是一改时间就可以看到问题的。多试几次多改几次就会有让人烦恼的NAG出来了。
什么NAG?呵呵大家自己动手试下吧。这样你就对这程序的认识就不仅仅停在我所表述的文字层面上了。

好吧,闲话少叙。我们看这里:0049CB70/$55            PUSH EBP                                 ;局部调用来自 0049B71D
0049CB71|.8BEC          MOV EBP,ESP
(...省略部分代码...)
0049CBB5|.BF 01000000   MOV EDI,1
0049CBBA|>8B55 FC       /MOV EDX,DWORD PTR SS:
0049CBBD|.8A543A FF   |MOV DL,BYTE PTR DS:
0049CBC1|.80C2 BF       |ADD DL,0BF
0049CBC4|.80EA 1A       |SUB DL,1A
0049CBC7|.72 08         |JB SHORT fastfold.0049CBD1
0049CBC9|.80C2 FA       |ADD DL,0FA
0049CBCC|.80EA 1A       |SUB DL,1A
0049CBCF|.73 51         |JNB SHORT fastfold.0049CC22
0049CBD1|>47            |INC EDI
0049CBD2|.48            |DEC EAX
0049CBD3|.^ 75 E5         \JNZ SHORT fastfold.0049CBBA
0049CBD5|>83FE 09       CMP ESI,9                              ; 用户名长度:9
0049CBD8|.75 48         JNZ SHORT fastfold.0049CC22            ;用户名长度不是9位就错!
0049CBDA|.8B45 FC       MOV EAX,DWORD PTR SS:         ; 用户名
0049CBDD|.8A00          MOV AL,BYTE PTR DS:               ; 用户名第1位
0049CBDF|.3C 5A         CMP AL,5A                              
0049CBE1|.77 3F         JA SHORT fastfold.0049CC22
0049CBE3|.3C 41         CMP AL,41
0049CBE5|.72 3B         JB SHORT fastfold.0049CC22             ; 显然只能是大写字母
0049CBE7|.8B45 FC       MOV EAX,DWORD PTR SS:
0049CBEA|.8A40 02       MOV AL,BYTE PTR DS:             ; 用户名第3位
0049CBED|.3C 7A         CMP AL,7A
0049CBEF|.77 31         JA SHORT fastfold.0049CC22
0049CBF1|.3C 61         CMP AL,61
0049CBF3|.72 2D         JB SHORT fastfold.0049CC22             ; 显然只能是小写字母
0049CBF5|.8B45 FC       MOV EAX,DWORD PTR SS:
0049CBF8|.8A40 05       MOV AL,BYTE PTR DS:             ; 用户名第6位
0049CBFB|.3C 5A         CMP AL,5A
0049CBFD|.77 23         JA SHORT fastfold.0049CC22
0049CBFF|.3C 41         CMP AL,41
0049CC01|.72 1F         JB SHORT fastfold.0049CC22             ; 只能是大写字母
0049CC03|.8B45 FC       MOV EAX,DWORD PTR SS:
0049CC06|.8A40 07       MOV AL,BYTE PTR DS:             ; 第8位
0049CC09|.3C 5A         CMP AL,5A
0049CC0B|.77 15         JA SHORT fastfold.0049CC22
0049CC0D|.3C 41         CMP AL,41
0049CC0F|.72 11         JB SHORT fastfold.0049CC22             ; 只能是大写字母
0049CC11|.8B45 FC       MOV EAX,DWORD PTR SS:
0049CC14|.8A4430 FF   MOV AL,BYTE PTR DS:         ; 第9位
0049CC18|.3C 7A         CMP AL,7A
0049CC1A|.77 06         JA SHORT fastfold.0049CC22
0049CC1C|.3C 61         CMP AL,61
0049CC1E|.72 02         JB SHORT fastfold.0049CC22             ; 只能是小写字母
0049CC20|.B3 01         MOV BL,1
0049CC22|>33C0          XOR EAX,EAX
0049CC24|.5A            POP EDX
0049CC25|.59            POP ECX
0049CC26|.59            POP ECX
0049CC27|.64:8910       MOV DWORD PTR FS:,EDX
0049CC2A|.68 3FCC4900   PUSH fastfold.0049CC3F
0049CC2F|>8D45 FC       LEA EAX,DWORD PTR SS:
0049CC32|.E8 7174F6FF   CALL fastfold.004040A8
0049CC37\.C3            RETN
0049CC38   .^ E9 6F6EF6FF   JMP fastfold.00403AAC
0049CC3D   .^ EB F0         JMP SHORT fastfold.0049CC2F
0049CC3F   .8BC3          MOV EAX,EBX
0049CC41   .5F            POP EDI
0049CC42   .5E            POP ESI
0049CC43   .5B            POP EBX
0049CC44   .59            POP ECX
0049CC45   .5D            POP EBP
0049CC46   .C3            RETN
呵呵,看到了吧。作者耍了个手段。还记得A部分算法分析里有个:
004866D4|.83F8 07       CMP EAX,7                              ;用户名长度:7
004866D7|.7D 3E         JGE SHORT fastfold.00486717            ;小于7则错
其实用户名的真正长度应该为9,且:
第1、6、8位为大写字母
第3、9位为小写字母。


                                                                           
                   三、DLL里的注册信息验证

    **** Hidden Message *****
好啦,这个软件的注册与检测是由主程序和DLL共同完成的。防破解安全上的设计也比较巧妙,满足基本要求的注册信息会在注册表里保存,但注册信息分开验证,还是具有一定的隐蔽性的。OD这个软件还真是一波三折啊,累坏人啦!总的来说这是我耗费时间和精力比较多的一个软件。
如果大家想自己先按要求设定真正的注册码,从而逆推出用户名,那基本上是难于上青天。光MD5那里就够你喝一壶的了。





                                                         关于暴破
                                                   
说实话,这个软件的爆破并不太难。这里给出部分关键提示:20天的试用期在程序中(DLL也有)是CMP EAX,14 ,未注册或伪注册的标志是a1。
大家有空了可以自己OD下看看。限于篇幅,我这里就不再赘述了。




PS:话说我自己OD出的注册信息是第一遍就找成功了的。要知道MD5基本上是不可逆的,一遍就能找到合乎要求的用户名这个运气只能说太好了。看来赶紧去买彩票去咯~~~

月之精灵 发表于 2011-12-3 12:24:25

占位寻租。学习

GGLHY 发表于 2011-12-3 12:50:03

楼上的,你那沙发地段多少钱一平?我要租。。。哈哈:handshake

foxjinlin 发表于 2011-12-3 21:53:43

前排学习

这软件的注册算法比较恐怖

GGLHY 发表于 2011-12-4 01:26:22

前排学习

这软件的注册算法比较恐怖
foxjinlin 发表于 2011-12-3 21:53 https://www.chinapyg.com/images/common/back.gif

    :handshake
    呵呵,算法倒好搞定。可是问题在于DLL的验证里有对注册码1、3、6、8位的限制。换句话说通过算法得到的注册码还必须同时满足这4位上的要求。最关键的是注册码是通过MD5转换后查表得到的。而MD5基本是不可逆的,你无法通过倒推的方法得出用户名。所以只能通过类似"穷举"用户名某一位或几位字母的方法来测试是否符合了对注册码1、3、6、8位的要求。

飘云 发表于 2011-12-4 01:40:03

无穷无尽的顶起。。妈妈说。。字数一定要长。。。。

GGLHY 发表于 2011-12-4 03:20:49

无穷无尽的顶起。。妈妈说。。字数一定要长。。。。
飘云 发表于 2011-12-4 01:40 https://www.chinapyg.com/images/common/back.gif


    呵呵:handshake 。兄弟也还没休息哈!

dodolovely 发表于 2011-12-5 14:43:09

谢谢,很厉害

bbs200908 发表于 2011-12-6 09:08:44

厉害..见试了。。。。。。

辰龙飞飞 发表于 2011-12-6 11:13:27

谢谢楼主提供
页: [1] 2 3 4
查看完整版本: 山重水复疑无路,柳暗花明又一村!某软件的一波三折OD手记