*本文作者:新希望鲜牛奶,本文属FreeBuf原创奖励计划,未经许可禁止转载。

本文通过对Android源码中NFC部分的简单分析,实现了另外一种设置UID的方式,可用于部分场景下的门禁卡模拟。

一、背景

本人就读于西南地区某大学,学校于2016年为学生宿舍楼大门安装了NFC门禁系统。这个时候手机的NFC技术已经相当成熟,网上充斥着各种手机模拟门禁、刷公交的帖子,各大手机厂商也与公交公司合作共同推进手机刷公交的进步。于是我也试着看看能不能用手机来刷开宿舍的门禁。我通过Acr122u将校园卡的UID写入一张MIFARE® Classic 1K兼容卡片后,成功刷开了宿舍的大门。

从08年NXP公司的MIFARE® Classic Cards被攻破后,M1卡就不再具有安全性,在如身份识别、电子钱包等需要一定安全性的场景下逐渐被安全性更高CPU卡取代。但是由于CPU卡本生比M1卡成本高,并且某些工程中大量使用的M1卡及相关系统全面更新将会是一大笔支出,加之新系统建设时监管不严,目前仍有部分工程中使用着M1卡。可笑的是16年安装的门禁居然是通过UID来进行身份验证(即使我们校园卡是复旦CPU卡)。安全建设的实施情况可见一斑。既然已经确定了它通过UID进行身份识别,那接下来的工作便是在手机上来模拟这样一张具有固定UID的卡片了。

二、原理分析

NFC设备有三种工作模式:Tag Reader/Writer、Peer to Peer、Card Emulation模式,详情可参见NFC Forum的介绍。现在很多安卓手机都具有NFC芯片,安卓系统也从Android 4.4开始原生提供了NFC卡片模拟的实现,即HCE。但是Android系统提供的卡模拟API是工作在国际智能卡标准ISO 7816-4下,同时Android也明确指出了使用ISO/IEC 14443-3协议中用于冲突检测的UID进行身份识别是不安全的,所以Android也没有提供控制UID的相关API,详情可参见这里。因此我们使用Android手机来进行卡模拟时,通过读卡器读到的UID通常是以 0×08 开头的随即值,这是ISO/IEC 14443-3标准的Anticollision部分要求的。当然这一点,不同的厂家有不同的实现,并且目前流行于Android平台的Broadcom和NXP这两家公司的芯片通常都可以通过修改配置文件的方式来指定UID。

如果在配置文件中没有指定UID,将由NFCC(NFC Controler)产生随机值。基于这点,网上有很多热心网友写了指定UID的教程,可以参见这里这里。甚至有人写了相应软件来更方便的修改UID。后来有些手机厂商甚至在自家应用中添加了门禁卡模拟的功能,比如(18年初?)更新的小米钱包。有些门禁是要读取卡内的除UID以外的其他信息的,M1卡它可能读取加密或不加密的Sector,而CPU卡你也很难知道它会读取哪个DF里的信息,以及是否需要密钥认证。因此通用的门禁模拟软件还大多停留在UID的模拟上,本文也只讨论如何设置固定的NFCID1。

三、修改配置文件

经过前面的分析,我开始在Mi 5s Plus手机上进行尝试。这款手机采用了NXP的 pn551 芯片,在文档AN11690.pdf中介绍了NXP的NFC芯片在Android下的移植过程。从文档中我们得知在Android O平台上的移植需要用到 libnfc-brcm.conf、libnfc-nxp.conf 这两个配置文件,在Android P上则变为了 libnfc-nci.conf 和 libnfc-nxp.conf 这两个配置文件。我在手机上刷入了LineageOS 15.1,在 /vendor/etc/ 路径下可以找到这两个配置文件。通过修改libnfc-brcm.conf中的APPL_TRACE_LEVEL和PROTOCOL_TRACELEVEL日志级别可以在logcat中看到NCI协议栈及NFC HAL层详细的调试信息,libnfc-nxp.conf中修改NXPLOG*_LOGLEVEL来更改日志级别。按照前面帖子介绍的方式修改了NFA_DM_START_UP_CFG和NXP_CORE_CONF,杀死 com.android.nfc 进程重启NFC服务。

NFC服务有个 android:persistent=”true” 属性, ActivityManager 检测到进程被杀死后会自动重启它。从logcat中可以看到两个配置文件均被加载了,但是读卡器读到的UID仍然是 0×08 开头的NFCID3。使用小米钱包的门禁模拟功能应该是可以成功的,看网上的介绍说支持Mi 5s Plus,但我不想为了刷个门禁刷回MIUI。于是我开始尝试着用其它的方式来解决问题。

四、安卓系统如何与NFC硬件交流

LineageOS源代码clone到本地Lineageos目录下,确保能为Mi 5s Plus设备正常编译。以下实验均在此目录下完成。我们首先通过AN11690.pdf中的一幅图来整体认识一下NFC在Android平台的实现。Android NFC stack overview安卓底层是基于Linux内核的,因此驱动一个硬件设备的Linux设备驱动必不可少。代码位于 Lineageos/kernel/xiaomi/msm8996/drivers/nfc,编译后在内核镜像中。

HAL意为硬件抽象层,运行在用户空间,与内核中实现设备基本操作的Linux设备驱动共同组成完整的设备驱动。HAL的最初目的是规避Linux内核GPL协议,现在已发展为规范设备驱动程序编写,便于移植。详情可以参见这里Android Treble详细分析。Android O开始强制使用HIDL来定义HAL接口,NFC HAL代码位于 Lineageos/hardware/interfaces/nfc,编译后生成 [email protected] , [email protected] , [email protected], 启动NFC HAL的脚本 [email protected]

NCI层实现了NFC协议栈,上层通过它与NFCC进行通信。NCI的实现与蓝牙协议栈在Android的实现类似。代码位于 Lineageos/system/nfc,编译后生成 libnfc-nci.so 以及 nfc_nci.msm8996.so

通过JNI实现Android框架中Java代码与NCI中的代码相互调用。代码位于 Lineageos/packages/apps/Nfc/nci/jni,编译后生成 libnfc_nci_jni.so

与蓝牙类似,NFC在Android中也以服务的形式存在,Android Framework通过AIDL与服务通信。NFC Service代码位于 Lineageos/packages/apps/Nfc,对应NXP的芯片编译后生成 NfcNci.apk,而Broadcom的芯片生成 Nfc.apk

Android APP通过调用Android框架提供的API来使用NFC功能。

五、NFC Enable流程

上一节介绍了NFC在Android的总体结构,本节结合具体代码来跟踪一下当我们点击设置菜单里的NFC按钮后NFC Enable的具体流程。

首先找到Preferences中切换NFC这个开关。系统设置是一个软件包,代码位于 Lineageos/packages/apps/Settings。从Android项目中文件及目录的命名可以看出Android的命名是相当规范的,因此我们进入到这个目录后应该就能猜出它会通过 NfcEnabler.java 中的 NfcEnabler 类的相关方法来启用NFC。当然,我们也可以一步步把它找出来。

strings.xml 找到如下与设置界面一致的字符串:

    <string name="connected_devices_dashboard_title" msgid="2355264951438890709">"已连接的设备"</string>

搜索以下看哪些布局用到这个字符串,在 connected_devices.xml 中找到:

    <SwitchPreference
        android:key="toggle_nfc"
        android:title="@string/nfc_quick_toggle_title"
        android:icon="@drawable/ic_nfc"
        android:summary="@string/nfc_quick_toggle_summary"
        android:order="-5"/>

以上布局是如何被加载的这里不用关心,知道PreferenceScreen可以通过key找到这个组件就行啦。以toggle_nfc为关键字搜索java代码,可以发现 NfcPreferenceController.java 用到了它:

    public class NfcPreferenceController extends AbstractPreferenceController
            implements PreferenceControllerMixin, LifecycleObserver, OnResume, OnPause {

        public static final String KEY_TOGGLE_NFC = "toggle_nfc";
        public static final String KEY_ANDROID_BEAM_SETTINGS = "android_beam_settings";

        private NfcEnabler mNfcEnabler;
        private NfcAdapter mNfcAdapter;
        ......
        @Override
        public void displayPreference(PreferenceScreen screen) {
            if (!isAvailable()) {
                removePreference(screen, KEY_TOGGLE_NFC);
                removePreference(screen, KEY_ANDROID_BEAM_SETTINGS);
                mNfcEnabler = null;
                return;
            }
            mNfcPreference = (SwitchPreference) screen.findPreference(KEY_TOGGLE_NFC);
            mBeamPreference = (RestrictedPreference) screen.findPreference(
                    KEY_ANDROID_BEAM_SETTINGS);
            mNfcEnabler = new NfcEnabler(mContext, mNfcPreference, mBeamPreference);
            // Manually set dependencies for NFC when not toggleable.
            if (!isToggleableInAirplaneMode(mContext)) {
                mAirplaneModeObserver = new AirplaneModeObserver();
                updateNfcPreference();
            }
        }
        ......
    }

从上面的代码可以看出显示这个Fragment的时候new了一个NfcEnabler对象,正是通过它来进行NFC的开与关。下面截取 NfcEnabler.java 部分代码:

    /**
    * NfcEnabler is a helper to manage the Nfc on/off checkbox preference. It is
    * turns on/off Nfc and ensures the summary of the preference reflects the
    * current state.
    */
    public class NfcEnabler implements Preference.OnPreferenceChangeListener {
        private final Context mContext;
        private final SwitchPreference mSwitch;
        private final RestrictedPreference mAndroidBeam;
        private final NfcAdapter mNfcAdapter;
        private final IntentFilter mIntentFilter;
        private boolean mBeamDisallowedBySystem;

        private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGED.equals(action)) {
                    handleNfcStateChanged(intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE,
                            NfcAdapter.STATE_OFF));
                }
            }
        };
        ......
        public boolean onPreferenceChange(Preference preference, Object value) {
            // Turn NFC on/off

            final boolean desiredState = (Boolean) value;
            mSwitch.setChecked(desiredState);
            mSwitch.setEnabled(false);

            if (desiredState) {
                mNfcAdapter.enable();
            } else {
                mNfcAdapter.disable();
            }

            return false;
        }
        ......
    }

可以看到在这个Listener中创建了一个Brodcasteceiver,当我们点击NFC设置项那个SwitchPreference(相当于ListView的自定义item)时,它就会收到广播,并通过NfcAdapter来开关NFC。

前面我们知道,通过调用NfcAdapter.enable()方法来进行NFC硬件的开关。它具体又做了些什么事呢?我们来看看 Lineageos/frameworks/base/core/java/android/nfc/NfcAdapter.java

    @SystemApi
    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
    public boolean enable() {
        try {
            return sService.enable();
        } catch (RemoteException e) {
            attemptDeadServiceRecovery(e);
            return false;
        }
    }

可以看出这是一个系统API,也就是说我们编写的一般应用是不能调用这个API的。sService是一个static INfcAdapter的对象,INfcAdapterAIDL定义的接口,用于调用NfcService的方法。可以看出它执行了Service的enable()方法。代码位于 Lineageos/packages/apps/Nfc/NfcService.java,相关aidl也定义在这个目录。实现如下:

    @Override
    public boolean enable() throws RemoteException {
        NfcPermissions.enforceAdminPermissions(mContext);
        saveNfcOnSetting(true);
        new EnableDisableTask().execute(TASK_ENABLE);
        return true;
    }

NfcService作为系统服务,由NfcNci.apk提供,并在开机时启动由NfcApplication启动。下面我们来看看NfcService在这个异步任务里面又做了些什么。

class EnableDisableTask extends AsyncTask<Integer, Void, Void> {
        @Override
        protected Void doInBackground(Integer... params) {
            ......
            switch (params[0].intValue()) {
                case TASK_ENABLE:
                    enableInternal();
                    break;
                case TASK_DISABLE:
                    disableInternal();
                    break;
                case TASK_BOOT:
                    Log.d(TAG, "checking on firmware download");
                    if (mPrefs.getBoolean(PREF_NFC_ON, NFC_ON_DEFAULT)) {
                        Log.d(TAG, "NFC is on. Doing normal stuff");
                        enableInternal();
                    } else if (!isSecHal()) {
                        Log.d(TAG, "NFC is off.  Checking firmware version");
                        mDeviceHost.checkFirmware();
                    }
                    ......
            }
            ......
        }

        /**
         * Enable NFC adapter functions.
         * Does not toggle preferences.
         */
        boolean enableInternal() {
            if (mState == NfcAdapter.STATE_ON) {
                return true;
            }
            Log.i(TAG, "Enabling NFC");
            updateState(NfcAdapter.STATE_TURNING_ON);

            WatchDogThread watchDog = new WatchDogThread("enableInternal", INIT_WATCHDOG_MS);
            watchDog.start();
            try {
                mRoutingWakeLock.acquire();
                try {
                    if (!mDeviceHost.initialize()) {
                        Log.w(TAG, "Error enabling NFC");
                        updateState(NfcAdapter.STATE_OFF);
                        return false;
                    }
                } finally {
                    mRoutingWakeLock.release();
                }
            } finally {
                watchDog.cancel();
            }

            if (mIsHceCapable) {
                // Generate the initial card emulation routing table
                mCardEmulationManager.onNfcEnabled();
            }

            nci_version = getNciVersion();
            Log.d(TAG, "NCI_Version: " + nci_version);

            synchronized (NfcService.this) {
                mObjectMap.clear();
                mP2pLinkManager.enableDisable(mIsNdefPushEnabled, true);
                updateState(NfcAdapter.STATE_ON);
            }

            initSoundPool();

            mScreenState = mScreenStateHelper.checkScreenState();
            int screen_state_mask = (mNfcUnlockManager.isLockscreenPollingEnabled()) ?
                             (ScreenStateHelper.SCREEN_POLLING_TAG_MASK | mScreenState) : mScreenState;

            if(mNfcUnlockManager.isLockscreenPollingEnabled())
                applyRouting(false);

            mDeviceHost.doSetScreenState(screen_state_mask);

            /* Start polling loop */

            applyRouting(true);
            return true;
        }
        ......
    }

enableInternal方法里调用了mDeviceHost的initialize()方法。

mDeviceHost = new NativeNfcManager(mContext, this);

在nci和nxp目录下都有相应的 NativeNfcManager.java 实现了DeviceHost接口。从 Android.mk 中可以看出他们分属于两个不同的Package:NfcNciNfc。这里有两个包是因为以前Android平台的NFC HAL层没有一个统一的接口,NfcNci对应的是Broadcom公司NFC芯片的实现,而Nfc对应的是NXP公司的芯片。在Linageos 15.1中Mi 5s Plus采用的这款NXP的pn54x芯片,用的是NfcNci的代码实现,说明两家公司NCI的实现终于还是统一了。从手机/system/lib*/下的libnfc-nci.so、libnfc_nci_jni.so,以及/system/app/NfcNci.apk都可以看出的确是用的NfcNci这个Package,当然我们也可以从 Lineageos/device/xiaomi/msm8996-common/msm8996.mk 得到印证。其中包含的这部分代码:

# NFC
PRODUCT_PACKAGES += \
    [email protected] \
    [email protected] \
    com.android.nfc_extras \
    nfc_nci.msm8996 \
    NfcNci \
    Tag

我们来看看NativeNfcManager类的initialize()方法:

    private native boolean doInitialize();
    private native int getIsoDepMaxTransceiveLength();
    @Override
    public boolean initialize() {
        boolean ret = doInitialize();
        mIsoDepMaxTransceiveLength = getIsoDepMaxTransceiveLength();
        return ret;
    }

它调用了一个名为doInitialize的native方法。这个方法通过jniRegisterNativeMethods注册到了函数nfcManager_doInitialize,其实最终调用的是JNIEnv里面的RegisterNatives函数来完成动态注册,这里Android对它进行了一下封装。下面我们来看看nfcManager_doInitialize这个函数。

static jboolean nfcManager_doInitialize (JNIEnv* e, jobject o)
{
    ALOGV("%s: enter; ver=%s nfa=%s NCI_VERSION=0x%02X",
        __func__, nfca_version_string, nfa_version_string, NCI_VERSION);
    tNFA_STATUS stat = NFA_STATUS_OK;

    PowerSwitch & powerSwitch = PowerSwitch::getInstance ();

    if (sIsNfaEnabled)
    {
        ALOGV("%s: already enabled", __func__);
        goto TheEnd;
    }

    powerSwitch.initialize (PowerSwitch::FULL_POWER);

    {
        unsigned long num = 0;

        NfcAdaptation& theInstance = NfcAdaptation::GetInstance();
        theInstance.Initialize(); //start GKI, NCI task, NFC task

        {
            SyncEventGuard guard (sNfaEnableEvent);
            tHAL_NFC_ENTRY* halFuncEntries = theInstance.GetHalEntryFuncs ();

            NFA_Init (halFuncEntries);

            stat = NFA_Enable (nfaDeviceManagementCallback, nfaConnectionCallback);
            if (stat == NFA_STATUS_OK)
            {
                num = initializeGlobalAppLogLevel ();
                CE_SetTraceLevel (num);
                LLCP_SetTraceLevel (num);
                NFC_SetTraceLevel (num);
                RW_SetTraceLevel (num);
                NFA_SetTraceLevel (num);
                NFA_P2pSetTraceLevel (num);
                sNfaEnableEvent.wait(); //wait for NFA command to finish
            }
            EXTNS_Init (nfaDeviceManagementCallback, nfaConnectionCallback);
        }

        if (stat == NFA_STATUS_OK)
        {
            //sIsNfaEnabled indicates whether stack started successfully
            if (sIsNfaEnabled)
            {
                RoutingManager::getInstance().initialize(getNative(e, o));
                nativeNfcTag_registerNdefTypeHandler ();
                NfcTag::getInstance().initialize (getNative(e, o));
                PeerToPeer::getInstance().initialize ();
                PeerToPeer::getInstance().handleNfcOnOff (true);

                /////////////////////////////////////////////////////////////////////////////////
                // Add extra configuration here (work-arounds, etc.)

                if (gIsDtaEnabled == true)
                {
                    uint8_t configData = 0;
                    configData = 0x01;    /* Poll NFC-DEP : Highest Available Bit Rates */
                    NFA_SetConfig(NFC_PMID_BITR_NFC_DEP, sizeof(uint8_t), &configData);
                    configData = 0x0B;    /* Listen NFC-DEP : Waiting Time */
                    NFA_SetConfig(NFC_PMID_WT, sizeof(uint8_t), &configData);
                    configData = 0x0F;    /* Specific Parameters for NFC-DEP RF Interface */
                    NFA_SetConfig(NFC_PMID_NFC_DEP_OP, sizeof(uint8_t), &configData);
                }

                struct nfc_jni_native_data *nat = getNative(e, o);

                if ( nat )
                {
                    if (GetNumValue(NAME_POLLING_TECH_MASK, &num, sizeof(num)))
                        nat->tech_mask = num;
                    else
                        nat->tech_mask = DEFAULT_TECH_MASK;
                    ALOGV("%s: tag polling tech mask=0x%X", __func__, nat->tech_mask);
                }

                // if this value exists, set polling interval.
                if (GetNumValue(NAME_NFA_DM_DISC_DURATION_POLL, &num, sizeof(num)))
                    nat->discovery_duration = num;
                else
                    nat->discovery_duration = DEFAULT_DISCOVERY_DURATION;

                NFA_SetRfDiscoveryDuration(nat->discovery_duration);

                // get LF_T3T_MAX
                {
                    SyncEventGuard guard (sNfaGetConfigEvent);
                    tNFA_PMID configParam[1] = {NCI_PARAM_ID_LF_T3T_MAX};
                    stat = NFA_GetConfig(1, configParam);
                    if (stat == NFA_STATUS_OK)
                    {
                        sNfaGetConfigEvent.wait ();
                        if (sCurrentConfigLen >= 4 || sConfig[1] == NCI_PARAM_ID_LF_T3T_MAX) {
                            ALOGV("%s: lfT3tMax=%d", __func__, sConfig[3]);
                            sLfT3tMax = sConfig[3];
                        }
                    }
                }

                prevScreenState = NFA_SCREEN_STATE_OFF_LOCKED;

                // Do custom NFCA startup configuration.
                doStartupConfig();
                goto TheEnd;
            }
        }

        ALOGE("%s: fail nfa enable; error=0x%X", __func__, stat);

        if (sIsNfaEnabled)
        {
            EXTNS_Close ();
            stat = NFA_Disable (FALSE /* ungraceful */);
        }

        theInstance.Finalize();
    }

TheEnd:
    if (sIsNfaEnabled)
        PowerSwitch::getInstance ().setLevel (PowerSwitch::LOW_POWER);
    ALOGV("%s: exit", __func__);
    return sIsNfaEnabled ? JNI_TRUE : JNI_FALSE;
}

可以看到,它调用了NfcAdaptationInitialize()方法和NFA_SetConfig()等在libnfc-nci中定义的API函数,对硬件和GKI、NFA等子系统进行了初始化,最后启动Discovery。再往下就是HAL层调用,这里算是和硬件打上交道了。至此enable过程分析完成。

六、从NCI层入手

从上面NFC Service的相关分析也可以看出,安卓系统正是通过NCI层来与NFCC进行交互的。因此我们只要合理调用libnfc-nci.so中的函数,也能达到控制NFCC的目的,当然也应该可以实现设置UID的目的。这里不再对NCI层代码作详细分析,感兴趣的同学可以参考Bluetooth在Android的实现,他们是差不多的。网上关于Bluetooth分析的文章非常多,这里推荐一个CSDN博主风语比较全面的分析。

通过分析我们知道Nfc Service启动Rf Discovery时会调用libnfc-nci中的NFA_StartRfDiscovery()函数,这个函数会发送一个表示事件NFA_DM_API_START_RF_DISCOVERY_EVT的消息,经过消息分发后会执行nfa_dm_start_rf_discover()函数,在此函数中又会调用nfa_dm_set_rf_listen_mode_config()。在nfa_dm_set_rf_listen_mode_config()函数中设置了Listen的参数,但是没有指定NFCID1,将由NFCC自行决定(NCI协议规定为 0×80 开头的随机值)。下面截取该函数的部分代码:

static tNFA_STATUS nfa_dm_set_rf_listen_mode_config(
    tNFA_DM_DISC_TECH_PROTO_MASK tech_proto_mask) {
  uint8_t params[40], *p;
  uint8_t platform = 0;
  uint8_t sens_info = 0;
......
  p = params;
  /*
  ** for Listen A
  **
  ** Set ATQA 0x0C00 for T1T listen
  ** If the ATQA values are 0x0000, then the FW will use 0x0400
  ** which works for ISODEP, T2T and NFCDEP.
  */
  if (nfa_dm_cb.disc_cb.listen_RT[NFA_DM_DISC_LRT_NFC_A] ==
      NFA_DM_DISC_HOST_ID_DH) {
    UINT8_TO_STREAM(p, NFC_PMID_LA_BIT_FRAME_SDD);
    UINT8_TO_STREAM(p, NCI_PARAM_LEN_LA_BIT_FRAME_SDD);
    UINT8_TO_STREAM(p, 0x04);
    UINT8_TO_STREAM(p, NFC_PMID_LA_PLATFORM_CONFIG);
    UINT8_TO_STREAM(p, NCI_PARAM_LEN_LA_PLATFORM_CONFIG);
    UINT8_TO_STREAM(p, platform);
    UINT8_TO_STREAM(p, NFC_PMID_LA_SEL_INFO);
    UINT8_TO_STREAM(p, NCI_PARAM_LEN_LA_SEL_INFO);
    UINT8_TO_STREAM(p, sens_info);
  } 
......
  if (p > params) {
    nfa_dm_check_set_config((uint8_t)(p - params), params, false);
  }
  return NFA_STATUS_OK;
}

从以上代码可以看出在设置的参数中没有NFCID1,我们在UINT8_TO_STREAM(p, sens_info);之后加入设置NFCID1的代码:

    UINT8_TO_STREAM(p, NFC_PMID_LA_NFCID1);// parameter type is nfcid1
    UINT8_TO_STREAM(p, 0x04); // parameter length
    UINT8_TO_STREAM(p, 0x01); 
    UINT8_TO_STREAM(p, 0x02);
    UINT8_TO_STREAM(p, 0x03);
    UINT8_TO_STREAM(p, 0x04);

在LineageOS代码根目录使用mmm system/nfc即可编译这个模块。使用adb push将生成的 libnfc-nci.so 传送到手机的 /system/lib64/,通过kill命令杀死 com.android.nfc 进程,NFC Service将自动重启。通过读卡器读取手机模拟的NFC卡片UID为:01020304。实验成功。

将UID写死可不是我们想要的,既然通过上面的函数将UID写入到NFCC就会生效,那么我们自己写软件来调用这个函数设置UID可以不能?答案是可以的。下面我们将通过写程序来动态控制UID。

从上一节的分析我们可以看出NFA模块的初始化是比较复杂的,因此我们直接在程序中加载libnfc-nci.so来调用它提供的API是会崩溃的,除非我们也如同NFC Service那样进行以系列初始化工作。我们应该在初始化完成的环境中来调用API,所以我们需要注入到 com.android.nfc 进程中去。我在demo中用的注入工具是TinyInjector,当然我们的目的是仅仅是把动态库加载到目标进程中去,用xposed等框架也是可以的。寻找目标函数在进程空间的地址也是个麻烦事,我直接使用了iqiyi团队开源的xHook将目标函数地址替换为我的函数地址,然后在我的函数里调用目标函数,也算是一种曲线救国的方式。我选择调用nfa_dm_set_config来设置参数,这个函数会在NFA_SetConfig调用后作为消息处理函数被调用。设置UID后需要重启Listening来使配置生效,这里通过调用NFC_Deactivate函数将NFCC设置为IDLE状态再设置为DISCOVERY状态实现重启,通过其他如Stop/StartRf函数也是可以的。测试代码在这里

七、总结

为了给NFCC设置固定的UID,从而达到模拟门禁卡的目的。本文先尝试了网上广泛流传的修改配置文件的方式,在尝试未果后结合Android的源代码分析,实现了通过注入来设置UID的一种方式。该方法与修改配置文件的方法均需要root权限,同时修改配置文件的方法在新机器上还需要解锁system分区,而本方法则不需要。我们的目的是把so注入到目标进程中去,但是为了动态改变UID,我们还需要与动态库进行通信。Android上跨进程的java与native通信可以用grpc或者自己写socket通信。如果我们写成xposed模块,则可以使用xposed自带的注入,还可以在目标进程中建立Broadcast Receiver来接收控制APP的指令,在模块内直接通过jni即可调用我们native函数。

*本文作者:新希望鲜牛奶,本文属FreeBuf原创奖励计划,未经许可禁止转载。

那天,我问小明:告诉我你的账号密码,我帮你抢到票,愿意不?

小明:如果真能抢到,我还愿意加钱。

WX20190123-195648@2x.png

前几天,吴京一条微博成了当天的热搜。网友关注的不只是大明星、大导演竟然也会坐火车,而是那一句“只要能回家,坐哪都成”,更何况还有很多人至今也还在为抢到一张回家的票而出力、出钱。

20161228141533_5964_waifu2x_photo_noise1_scale_tta_1.png

春运的人山人海就是无数中国人对于回家的渴望,过年回家对于很多人来说可能是一年仅一次的与家人团聚,也是无数家庭一年之中最像一个家的时候。

只是,过去年兽是传说中的凶兽,现在,春运抢票是横在他们面前阻碍最大的年兽。

春运抢票已经成为很多人回家前的一场大战,需要准备“抢票日历”,来熟知回家的票是哪一天开抢;甚至可能你还需要准备多台电脑或者手机,使用多个账号号召亲戚朋友一起等待准点秒杀,即便如此,很多人连票都没看到就显示已售罄。

因为火车票的数量是一定的,但你不知道春运的大军中有多少人在和你一样为同一张票厮杀。于是,我们开始相信手动抢票比不过机器抢票,寄希望于各大平台的加速包、第三方抢票工具,希望这些能够助你成为抢到票的幸运儿。

隐私与回家车票的取舍

第三方抢票软件势头有多猛,有一组数据可以体会一下。中铁银通支付有限公司董事长罗晴说,目前12306系统日处理能力近2000万张,三分之一到一半都被第三方软件抢走,大部分系统处理能力变为刷票软件服务,很多数据处理造成无效浪费。这在一定程度上也说明,确实是有相当一部分人在依赖第三方抢票软件。

而就在春运之前,FreeBuf曾报道过一起12306数据泄露的事件,400多万条数据在暗网上被低价出售,后续事件发酵,一度被判定这些数据均是由第三方抢票软件泄露。所包含的数据不仅有账号主人本身的账号密码、身份证号、手机号等信息,还有账号上所添加的所有关联人的身份证号、姓名等重要信息。

当时报道这件事情,我所能想到最坏的结果就是密码及关联人身份证信息泄露的后果,但如果因为数据泄露,导致账号被他人登陆,无论是处于恶作剧还是何种心理,把辛辛苦苦抢到的票给退掉了,这可能是对参与这场春运的人伤害最大的吧。

因此,这其中又牵涉到关于隐私的取舍问题。我始终记得李彦宏曾经说的那句“中国人愿意用隐私换取便利”。放在春运抢票上,你愿意用隐私换取一张回家的票吗?

如果真的能够有一张回家的车票,我可能真的愿意执行这场交易。

W020180325511826684100.jpg

在这个时间节点,春运回家已经成为了这些人唯一的诉求,相比起所谓隐私泄露的危害性,家人可能是最实际也是最大的诱惑。

仔细揣摩一下,其实很多人对于隐私的概念依然很模糊。一般他们很少主动会去思考其背后所存在的危害性。拿抢票这件事情来说,我相信所有几乎所有使用第三方抢票平台的用户,内心只有一件事情——必须要抢到回家的票。隐私、账户泄露什么的都不足以成为当下之急,甚至即便主动被告知12306数据泄露事件,提醒使用第三方软件的风险,当你你没能抢到票的时候,似乎也只能寄希望于第三方抢票平台。

正如采访中一位朋友说到的“毕竟现在信息泄露的渠道太多了……”

抢票难是原罪

作为一个多次经历春运抢票的人,寄希望于12306官网及手机APP渠道抢票,很大几率会让你失望。尤其是12306图形验证码机制上线之后,这个几率可能还会继续增大。在百度百科上对于“12306验证码”这个词条的解释中摘录了这么一段话:

一家网站统计称,12306网站的购票验证码共有581种,按照要输入两个关键词的规则,排列组合多达336980种。一次性输入准确的比例为8%,两次输入准确的比例为27%,三次甚至4次以上输入准确的比例为65%。如果一次性输入成功的平均用时为5秒的话,按照热门车票“秒光”的情况计算,每输错一次验证码,就意味着当次购票成功率下降80%左右。

目前所有的抢票平台包括客户端抢票软件,基本都可以做到云识别验证码,平均识别准确率远高于人手动输入,而且速度够快。这在准点抢票中显然很实用,此外,即便准点抢票没有抢到票,利用第三方抢票工具也能够做到全天候刷票捡漏。视频采访中就有同事提到使用某抢票软件,捡漏刷票都抢到票了。

而以上说的两点这也是12306官方渠道做不到的,好消息是12306今年春运期间推出了一个候补购票的功能,的确能够实现第三方抢票软件的功能,坏消息是所支持的线路依然较少,至少笔者查询自己可以回家的线路中全都不支持。甚至采访中问及是否有了解候补购票的功能,多位朋友表示没有听说过。

WX20190131-070501@2x.png

不得不承认的是,第三方抢票软件的确有能提高抢票成功率,但也一定程度上造成了抢票难的这结果。有一个比较明显的特征,12306官网的订票助手默认自动刷票时间间隔是5s,而笔者使用过的一款抢票软件可以自定义刷票间隔,这也是大多数抢票软件都具备的功能(携程、飞猪等平台未知),软件也会显示提醒“修改抢票间隔会增加IP被封的几率”。

WX20190131-065725@2x.png

12306官方打击第三方刷票行为从来就没有停止过,近期中国铁路回应称,12306购票系统已经能够识别抢票软件的机器特征并实施限制,花钱购买的加速服务也不会像软件显示的成功率那样。所谓的机器特征,其中之一应该是同一个账户的刷票速度,刷票速度过快很容易被判定为机器刷票,不过对此第三方软件基本都加入了自动切换代理IP的功能,来应对IP被封的情况。

作为一个传播正能量、引导大家注意隐私保护的小编,能给出的官方建议是,尽量使用官方渠道去购票,不要登陆第三方抢票软件,也无需付费购买加速包,如果能够使用候补购票功能应该会大大增加购票成功率。

如果要使用第三方抢票软件,请务必保证12306账户密码与其他密码区分,且抢票结束后务必修改密码。

因为,我知道无论以什么理由去说服,第三方抢票软件依然会有很大的市场,即便当官网渠道能够实现第三方平台所有的功能,当你依然抢不到票的时候,第三方抢票软件依然是主要选择之一。有时候并非花钱去购买那1%的可能性,更多的买的是心里的安慰,一种期待回家过年的渴望。

至于隐私,等先过完年再说吧……

*本文作者:shidongqi,转载请注明来自FreeBuf.COM

前言

近期360互联网安全中心监测到一款网络劫持木马在众多网吧及大学的机房中大范围传播。该木马在2018年9月开始在国内传播,会利用篡改网络设置、劫持客户端网络数据、监控QQ聊天等方式窃取用户的隐私。

通过进一步追溯分析发现:在被攻击的网吧或学校机房环境中,均使用的一款名为“锐起无盘系统(去广告版本)”的软件。而正是该软件被利用,向用户电脑中植入带有劫持功能的“鑫哥木马”。根据我们掌握的数据统计,至少有60家网吧及9所大学受到影响,被劫持的网站列表超过9000个,同时还会获取用户QQ号及聊天记录文件等信息。木马最终通过劫持网站、跳转导航、游戏退弹等方式来牟取暴利。

木马分析

通过这个去广告版的无盘系统,会下发一个名为AD_xxxxxx_UID.exe的木马:

image.png

木马文件信息

木马通过篡改DNS及Hosts的形式实现网络劫持,其运行关系如下:

image.png

执行流程简图

image.png

游戏菜单界面图

安装过程

“锐起”服务端程序释放相关文件后,则会更新到客户机的客户端程序中执行,导致整个无盘网络系统的机器全部中招,客户端的游戏菜单程序启动后会读取MenuScreenConfig.xml文件中配置的Autorun启动项自动执行,启动顺序如下:TopTen\menu.datà RichtechGameTool.exeàProgramlog.exeàAD_xxxxxx_UID.exe,最终执行木马程序。

image.png

配置文件中的AutoRun项

为了达到掩人耳目的目的,木马程序还将自身复制到腾讯TGP、Adobe等常见游戏软件的目录下执行:

image.png

复制木马程序到Adobe、TGP目录代码片断

劫持网站

最终执行的木马会根据云端数据在本地生成一个脚本“Tencent.vbs”,运行该脚本后,会通过篡改DNS及Hosts文件来实现劫持。同时,木马还会回传用户信息至云端服务器数据库,回传的信息包括:机器名、MAC地址、内网IP、外网IP等。

image.png

木马从云端数据库中获取要劫持的内容

劫持后的落地页面,则会根据对应的UID来请求服务端数据库中对应的展示内容。如果该步骤的获取出错,则跳转到带有渠道号(k1828680)的2345导航页面。其所展示的页面中,甚至会用代码在浏览器控制台中打出“人工智能是其发展的核心技术”之类的字样:

image.png

浏览器控制台中显示的文字:

image.png

网页代码中的控制台输出代码

记录QQ信息

木马还会记录机器上登录QQ的信息,具体信息包括:IP地址、机器名、登录时间、下线时间、QQ号、QQ聊天记录文件、记录时间等。

image.png

木马记录QQ信息

木马后台数据

根据我们获取到的其后台统计数据,可以看到累计有7.5万台电脑被劫持,这些电脑上报了数据约220万条(截止2019年1月16日):

image.png

回传信息数据库

image.png

部分受影响网吧及学校信息

通过对数据库中的IP进行的统计,发现其中广东的IP占比高达45.02%,其后则是湖南6.78%、吉林4.98%、河南4.8%、广西4.52%。具体分布情况如下图所示:

image.png

感染设备所在地区分布图

溯源信息

根据对数据库中信息的分析,我们发现该木马团伙使用了一种多级代理模式在扩散木马,而存放劫持页面的域名,均以“镇江**网络科技有限公司”的名义进行了备案:

image.png

数据库中的代理人信息

image.png

存放劫持页面的域名备案信息

根据公开信息,我们可以查询到该公司主要人员及企业结构图:

image.png

镇江**网络科技有限公司的企业结构和主要负责人

总结

目前监测到该木马主要植入了用于劫持网站和监控QQ通讯的木马,后续攻击者可能会下发更多恶意程序:如盗号木马、勒索病毒、挖矿程序等进行获利。这类无盘系统一般包含多台计算机,一旦主机中招,整个网络都会受到影响。

360互联网安全中心提醒:

1.在网吧等公共环境上网,应该格外注意,尽量选择扫描二维码登录避免输入口令。

2.尽量避免在这类公共环境使用网银、操作涉及个人敏感信息的数据等。

3.网络管理人员可以通过检查DNS和HOST查看机器是否出现被篡改的情况,也可通过安装360安全卫士查杀此类木马。

MD5:

b41b87a494dcace1e80d2bb799e27779

ad911af95d591edbff0ffe95b2c9d2b5

9166dc84fb69f1d628e7ec8dbe1dd905

8cbe3c789dde4016fb23c7df3a8d33a0

Hosts/IP劫持列表:

http://www.ntxxz[.]cn/public/conf/dns_chuanqi.hosts

DNS:

47.97.123[.]210

*本文作者:360安全卫士,转载请注明来自FreeBuf.COM

各位Buffer早上好,今天是2019年1月31日星期四。今天的早餐铺内容有:FaceBook每月付费收集用户隐私信息;2018年黑客窃取加密货币价值达17亿美元;取缔全球最大DDoS服务网站后 Europol开始向151000名注册用户追责;警方关闭网络犯罪市场xDedic;AV-TEST公布最新Windows 10安全软件产品排名。

LBM-Chicken-Roll-Ups_1.jpg

FaceBook每月付费收集用户隐私信息

据Techcrunch报道,自2016年以来,FaceBook每月秘密付费给用户,要求用户安装名为“FaceBook Research”的应用程序,以便搜集用户在手机或网页上的活动,获取隐私信息。Facebook确认称,“FaceBook Research”这个项目主要是为了收集用户习惯,并且不打算停止。该计划主要面向13至35岁的用户,每月支付高达20美元的费用。用户下载该VPN之后,FaceBook会要求开通可访问网络流量的root权限,以便通过这种方式解密并分析用户行为。。同时还要求用户截图并上传他们的亚马逊历史订单页面。目前,苹果已经知晓了FaceBook这一行为,“FaceBook Research”可能会从APP Store下架。[来源:techcrunch]

2018年黑客窃取加密货币价值达17亿美元

反洗钱和区块链取证公司CipherTrace近日发布报告称,2018年,黑客(网络犯罪分子)通过欺诈、勒索、攻击以及恶意软件等多种途径,从交易所、用户以及投资者等平台或个人共窃取价值17亿美元的加密货币。其中,交易所的损失高达9.5亿美元。日本、韩国成为损失最大的国家。而众筹欺诈骗局成为最主要的损失原因。[来源:bleepingcomputer]

取缔全球最大DDoS服务网站后 Europol开始向151000名注册用户追责

2018年4月Europol取缔了最大的DDoS for-hire网站webstresser.org之后,该执法机构正寻求对其151,000名注册用户采取法律行动。该执法机构表示它正在和英国、荷兰当局开展合作,追查该网站的一些注册用户。该网站声称发起了400多万次DDoS攻击,其服务费用可以低至每月15欧元。Europol已经展开行动,至少有250名网络用户很快面临应有的法律制裁。[来源:cnbeta]

警方关闭网络犯罪市场xDedic

近日,美国和欧洲警方联合展开国际公开行动,关闭了一家网络犯罪市场xDedic,并在乌克兰逮捕了三名嫌疑人。这家黑市售卖计算机、服务器,还交易个人信息。据统计,通过xDedic交易的服务器超过17.6万。而受害者则遍布政府、医院、急救服务、呼叫中心、交通、法律、基金、学校等多个领域。此前,xDedic曾经关闭过一段时间,但又再次上线。而此次彻底被关停,其位于比利时和乌克兰的核心服务器等基础设施也被查封。[来源:thehackernews]

AV-TEST公布最新Windows 10安全软件产品排名

AV-TEST发布了最新的Windows 10下安全软件产品的评测报告,2018年12月的测试分为三个领域(保护,性能和可用性)的安全产品测试,结果表明此前排名较高的产品仍然处于领先地位,并且微软内置Windows杀毒软件仍然是一个明智的选择。其中,Windows Defender、Avast免费安全解决方案、卡巴斯基安全产品等得分较高,能够很好地保护Windows设备。[来源:cnbeta]

*转载请注明来自FreeBuf.COM

Sitadel:一款功能强大的Web 应用扫描器

Sitadel实际上是WAScan的升级版,不过是Python版本(>= 3.4)的,这样有助于研究人员根据自己的需要去进行自定义开发,并引入新的功能模块。

目前,Sitadel可实现扩展的功能如下:

前端框架检测;

内容分发网络检测;

定义扫描风险等级;

插件系统;

可使用Docker镜像进行构建和运行;

工具安装

$ git clone https://github.com/shenril/Sitadel.git
$ cd Sitadel
$ pip install .
$ python sitadel.py –help

功能介绍

1. 指纹识别

a)     服务器

b)     Web框架(CakePHP、CheeryPy……)

c)     前端框架(AngularJS、MeteorJS、VueJS……)

d)     Web应用程序防火墙(Waf)

e)     内容管理系统(CMS)

f)      操作系统(Linux、Unix……)

g)     编程语言(PHP、Ruby……)

h)     Cookie安全

i)      内容分发网络(CDN)

2. 攻击

(1)暴力破解

管理接口

常用后门

常用备份目录

常用备份文件

常用目录

常用文件

日志文件

(2)注入攻击

HTML注入

SQL注入

LDAP注入

XPath注入

跨站脚本(XSS)

远程文件披露(RFI)

PHP代码注入

(3)其他攻击

HTTPAllow方法

HTML对象

多重引用

Robots路径

WebDav

跨站追踪(XST)

PHPINFO

Listing

(4)漏洞利用

ShellShock

匿名密码(CVE-2007-1858)

SPDY(CVE-2012-4929)

Struts-Shock

参考命令

简单运行:

python sitadel http://website.com

以“高危”风险等级运行扫描,不支持重定向:

python sitadel http://website.com -r 2 --no-redirect

运行指定模块(查看运行日志):

python sitadel http://website.com -a admin backdoor -f header server –vvv

Docker运行:

docker build -t sitadel .
docker run sitadel http://example.com

Sitadel项目地址:【GitHub传送门

* 参考来源:kitploit,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM

近日,深信服安全团队基于SIP产品在国内发现一种新型的Mirai变种,该变种会进行门罗币挖矿并且通过SSH爆破来实现传播。

整个流程如下:

1.png

0×01 系统调用下载器

然后我们分析一下另一个程序DDG这个文件,这个文件就是一个下载器,下载母体。

2.png

该程序使用了大量的内联汇编,通过系统调用来实现下载的功能,容量体积更小,减少了被杀软引擎识别的风险。具体的系统调用可以在https://syscalls.kernelgrok.com/查询。

3.png

我们使用strace来观察一下这个程序的行为,连接了5.63.159.203下载母体文件dl

4.png5.png

0×02 多功能变种Mirai母体

该Mirai母体已被多家引擎识别。

6.png

跟踪一下母体的行为。

7.png

程序将通过系统调用unlink将自身给删除了,这里说明一点,母体会一直运行,所以遇到这种找不到文件,但是母体还在运行的程序,可以通过查找PID, 然后进入/proc/PID/ 目录下,找到exe,使用cat /proc/PID/exe >> mom_exe,可以拿到母体文件,如果直接将母体进程干掉,就拿不到这个文件了。本例有拿到下载器,所以可以通过下载器下载。

8.png9.png

检查是否存在重启信号设备watchdog(看门狗),如果检测到则修改权限,让watchdog失效

10.png

然后检测同行的比特币挖矿软件,通过使用md5来识别。

11.png12.png 将http, https, ftp的代理信息写入.bashrc文件中。

13.png14.png

下载挖矿软件到/tmp目录并且执行,挖矿软件的名称采用随机字符串的方式,长度不固定

15.png16.png 17.png18.png

可以看到创建了一个随机端口监听,等待连接,并且开启了一个ssh暴力破解攻击其他主机,实现传播,被攻击的为内网IP和随机生成IP。

19.png20.png

清除用户的计划任务

21.png

并且程序拥有着守护挖矿进程的能力,当把挖矿进程直接杀掉,母体会重新下载挖矿病毒并运行,所以这里是导致出现病毒文件再次出现,但是并没有运行的原因。

22.png23.png

拥有着一个基本所有功能的Mirai,功能太过复杂,使用超多的fork和syscall汇编内联代码。

增加分析人员的分析难度。 

0×03 挖矿程序

同样也被各大引擎识别了,这个是门罗币(XMR)挖矿,XMR由于匿名性被黑产热爱。

24.png

strace看一下程序的运行流程,连接矿池,发送任务,接受任务结果

25.png

首先通过gulf.moneroocean.stream这个地址登录矿工的信息,钱包等,然后连接矿池xmr.crypto-pool.fr(139.99.100.250)进行挖矿

26.png27.png

我们抓取了一些数据包,可以发现程序通信的挖矿数据

28.png

我们可以看到一次完整的挖矿通信的数据

29.png

0×04 分析结论

该程序经过测试,在执行病毒母体后,重启之后并不会再次出现感染情况。上次的重启重新下载了出现病毒文件初步估计是没有清理干净,清理顺序的问题。

挖矿信息:

矿工信息验证地址:gulf.moneroocean.stream

矿池地址:xmr.crypto-pool.fr

钱包地址:489nAwmiY4s8X95kvPDVvUaHsqmrs1NwsVKoBbqKftYzDAQJVUryJwJ7WMKansCeowe4vxg42p9VtbDKTPxkKp1pUXTnA5X

86V42mJ86Dg5uw9TYLWairZrjkhK1LkkiaAyDHeLTJhfEi1c9XbFPTHLCJWtM12Sv16P9aM6U64Ekc9drZ8or55Y3SqqiQK

IOC(MD5):

挖矿:04e28bfd6f7e58701a7654d53d279100

下载器:d530cab45f1234ce35c0b56689516f42

Mirai母体:9c699434f72e798ac42f72a82a30f997

0×05 解决方案

清除方式:

将母体的所有进程先全部使用kill -9 强行杀死

然后到/tmp目录下将挖矿文件删掉(如果母体文件dl还在,也将母体文件删除)

然后使用kill -9 将挖矿进程干掉。 

病毒检测查杀:

1、深信服EDR产品、SIP产品及防火墙等安全产品均具备病毒检测能力,部署相关产品用户可进行病毒检测。

30.png

病毒防御:

1、修补相关的web漏洞,防止被再次入侵。

2、更改账户密码,设置强密码,避免使用统一的密码,因为统一的密码会导致一台被攻破,多台遭殃。

3、病毒通过ssh传播,如果可以,可以将ssh的密码登录方式换成密钥登录。可防止ssh爆破传播。

全世界的安全研究专家们经常都需要对恶意软件来进行逆向工程分析,这样才能更加清楚地了解到网络攻击者的攻击方式以及真正意图。

*本文作者:千里目安全实验室,转载请注明来自FreeBuf.COM

近日研究人员发现网络犯罪分子开始利用一个2018年12月修复的ThinkPHP漏洞来进行僵尸网络的传播,传播的这两个僵尸网络是Mirai的变种Yowai和Gafgyt的变种Hakai。攻击者通过默认凭证字典攻击的方法来攻击使用该PHP框架创建的web站点,并获取这些路由器的控制权来发起DDOS攻击。研究人员发现这两个恶意软件类型导致1月11日到17日攻击和感染数据攀升。

Yowai

研究人员分析发现Yowai有一个与其他Mirai变种类似的配置表。配置表也是用相同的步骤来解密,并在感染入口向量中加入了其他已知的ThinkPHP漏洞利用。

Yowai会监听端口6来接收来自C2服务器的命令。在感染路由器后,会用字典攻击来尝试感染其他设备。受感染的路由器就会变成僵尸网络的一部分,僵尸网络的运营者就可以用受感染的设备发起DDOS攻击。

Yowai还使用了大量的漏洞利用来补充字典攻击,执行后会在用户的console上展示信息。分析发现Yowai还参考了一个竞争僵尸网络的kill列表。

 

图1. Console display on a Yowai感染的设备的console显示

image.png

表1. Yowai用于字典攻击的默认用户名和密码列表和参的竞争僵尸网络的kill列表

除了利用ThinkPHP漏洞外,研究人员发现Yowai样本还利用了CVE-2014-8361, a Linksys RCE, CVE-2018-10561, CCTV-DVR RCE等漏洞。

图2. ThinkPHP漏洞

Hakai

Hakai 是Gafgyt的变种僵尸网络,Gafgyt之前就是感染IoT设备并依赖路由器的漏洞进行传播。研究人员捕获和分析的Hakai样本利用了一些系统中尚未修复的漏洞,并加入了ThinkPHP, D-Link DSL-2750B router vuln, CVE-2015-2051, CVE-2014-8361, CVE-2017-17215的漏洞利用来进行传播并执行不同类型的DDOS攻击。

图3. Hakai扫描的有漏洞的路由器

图4. ThinkPHP漏洞利用

有趣的是,研究人员发现Hakai样本中含有直接从Mirai复制的代码,尤其是用于加密配置表的函数。但研究人员发现这些函数并不是可操作的,因此研究人员怀疑用于telnet字典攻击的代码被移除了,以使Hakai变种更加静默。

因为Mirai变种会杀掉竞争的僵尸网络,这对Hakai变种避免攻击使用默认凭证的IoT设备来说是非常有利的。相比暴力破解,单独使用自我传播的漏洞利用方法很难检测。

图5.部分代码来自于Mirai

图6. Hakai样本没有使用从Mirai复制的代码

结论 

ThinkPHP是一个免费的开源PHP框架,因其简单易用,深受开发者和企业欢迎。网络犯罪分子滥用Hakai和Yowai可以很容易地入侵web服务器攻击网站。随着越来越多的僵尸网络代码出现在互联网上,研究人员猜测味蕾会有更多的代码相似的竞争僵尸网络出现。网络犯罪分子也会继续开发基于Mirai的僵尸网络,因为大多数的IoT设备使用还是默认凭证。

IoT设备用户应该更新设备到最新的发布版本来修复漏洞利用。用户也应该经常更换设备密码以应对对设备的非授权访问。

本文是介绍如何设置和使用某些最重要的IOS应用渗透测试工具的系列课程中的第一篇。

对于这个系列文章来说,我们会假设用户使用Electra进行越狱。我具体使用的是iOS11.1.2,但是本系列中的大多数工具都可以在任何版本的iOS11上使用。

实现手机越狱

虽然我们可以在非越狱设备上进行某些渗透测试,不过,只有越狱之后,才能使用所有可用的工具实现全方位的测试。其实,越狱过程非常简单——您可以从这里下载11.0-11.1.2版本的Electra,或者这里下载11.2-11.3.1版本的Electra。

该软件的安装说明可参阅这里。

使用SSH连接设备

在安装Electra时,会顺便安装OpenSSH,这意味着我们可以通过ssh连接至设备。实际上,在进行渗透测试的过程中,几乎到处都要用到这个功能。

确定我们的iPhone手机的IP地址——为此,请打开Settings -> Wi-Fi选项。然后,单击所连接的WiFi旁边的“i”图标,并记下这个IP地址。

打开一个新的终端窗口,然后键入下列命令:

ssh [email protected]

(其中X是我们找到的IP地址)

当提示输入密码时,键入“alpine”——这是默认密码,为了安全起见,最好立即修改该密码(更多详情,请参考这里)。

完成上述操作后,会看到如下内容:

My-iPhone:~ root#

大功告成了!

破解应用程序

一旦你越狱成功了,我们就可以进行渗透测试了。通常情况下,第一件事情就是破解目标应用程序,并考察其文件和二进制代码。在iOS11上,我们可以使用一个名为“bfinject”的工具来完成该任务。

Bfinject是一款.dylib注入工具,适用于11.0-11.1.2版本的iOS系统。实际上,该工具不仅提供了破解功能,同时,它还提供了cycript(将在稍后讨论)。

安装bfinject时,可以从此处下载相应的tarball安装包。然后,需要将其复制到我们的设备:

在手机的终端中(即通过ssh连接手机后),使用下列命令来创建一个文件夹:

$ mkdir bfinject

然后,将这个tarball传输到手机中的bfinject文件夹中。实际上,为了达到这个目的,可用的方法有很多——例如,可以使用scp,不过对我来说,更喜欢使用Cyberduck,该工具可以从这里下载。在使用Cyberduck软件时,请转到左上角的“Open Connection”,选择SSH协议,并使用与前面相同的IP/密码进行登录(您可以将端口保留为22)。

现在,在Cyberduck或终端中,切换到设备的bfinject文件夹,并从Mac为其上传tarball安装包。对于使用Cyberduck的读者来说,先单击“Action”按钮,然后单击“Upload”按钮即可。

最后,执行下列命令:

$ tar xvf bfinject.tar

在运行bfinject之前,我们需要从Cydia下载“Core Utilities”。为此,请打开Cydia,并搜索该软件。选择该软件后,转到“Modify”,然后选择“Install”即可。

现在,我们已经为破解应用程序做好了准备。至于选择哪个软件吗?大家可以根据自己的喜好,从应用程序商店下载即可。下载相应的软件之后,请打开该应用程序,并使其位于手机的前台,同时,还要确保手机已经与计算机相连。在root终端中,导航至bfinject文件夹,并运行下列命令:

$ bash bfinject -P <AppName> -L decrypt

这时候,将在终端中看类似下面这样的内容:

1.png

几秒钟后,会在手机上收到破解完成的消息。当该软件询问是否想要使用netcat时,请选择“No”。现在,要获取破解之后的.ipa文件,需要在手机上找到该应用程序的目录。在Cyberduck或终端中,该目录位于/private/var/mobile/Containers/Data/Application。

顺便说一句,在手机中上,要想进入var目录,需要从root目录下面运行“cd ..”命令,因为我们使用ssh连接手机后,首先会进入root目录。

在Application目录中,可以看到许多具有随机名称的文件夹。其中每个文件夹都对应于该设备上的一个应用。为了帮助快速找到与目标应用程序相对应的目录,可以通过Cyberduck的“Modified”按钮对其进行排序,而最近安装的应用程序应该排在最前面。之后,请切换至目标应用程序对应目录下的Documents目录,这样就可以找到decrypted-app.ipa文件了。然后,在Cyberuck中选择“Action”->“Download as”选项,从而完成下载。之后,可以对这个文件进行重命名,具体名称您随意,不过,一定要把扩展名改为“.zip”,而不是原来的“.ipa”,这样,我们就可以轻松查看其内容了。

这样,就破解并下载好了我们的第一个应用程序!

class-dump

接下来要使用的class-dump软件,也是一个非常有价值的工具:它不仅可以转储应用程序类的运行时头文件,还能帮助我们理解应用程序的结构,选择我们想要的目标位置。

为了在Mac上安装该软件,请打开一个新终端,并运行下列命令:

$ brew install class-dump

然后,我们需要找到应用程序的可执行文件:对复制到计算机上的.ipa/.zip进行解压后,就能在得到的件夹中找到该文件,具体路径为 /Payload/AppName.app/AppName。

这时,我们可以运行下列命令:

./class-dump <AppName>.app/AppName > Dumped

如果出现错误消息,可以尝试安装与class-dump具有类似功能的其他软件,如这款软件,或这款软件。由于版本的原因,仍然可能会出错,这时请尝试将<AppName>.app/AppName改为 <AppName> 或 <AppName.app>。

现在,使用文本编辑器打开“Dumped”文件,这样就能找到该应用程序的所有运行时头文件了。

1.png

我通常从搜索单词“password”、“authentication”、“user”或“credentials”开始,来寻找让人感兴趣的类、方法或属性。

好了,本系列的第一篇文章就到此结束了!在第2篇文章中,我们将为读者介绍如何安装、探索和篡改cycript。在第3篇文章中,我们将介绍FRIDA/Objection工具的用法;在第4篇文章中,我们将介绍如何使用Hopper和lldb工具来调试二进制代码。

早在去年11月,我们就曾在一篇文章中提到过利用CHM(Compiled HTML Help,即“编译的HTML帮助文件”)传播恶意代码的行动,对其初步分析后,引起了我们威胁分析和情报团队的兴趣,分析结果显示,此次行动针对的是金融行业,尤其是是俄罗斯联邦和白俄罗斯共和国金融行业的工作人员。此次行动背后的行为者是Silence团伙,这是一个相对较新的威胁团伙,自2016年中期以来一直在运营。到目前为止,我们已确定的被攻击的企业包括:

· NBD Bank Russia:提供零售和商业服务的俄罗斯银行。

· Zapsibkombank(Zapadno-Sibirskiy Kommercheskiy Bank):西西伯利亚商业银行(WSCB),位于俄罗斯。

· FPB(Finprombank):同样位于俄罗斯。

· MSP银行(МСПБанк):专注于为中小企业提供融资的俄罗斯联邦国家银行。

· MT Bank(МТБанк):Meridian trade Bank,也是唯一一家位于白俄罗斯的实体银行。

1.png

图1. 攻击目标分布示意图

此次攻击行动的主要载体就是下图所示的CHM恶意文件。CHM是一种已经有点“过时”的文件格式,但它在过去也有被用于传播恶意代码的成功案例。在此次行动中,CHM除了运行本机OS二进制文件以收集与其目标相关的信息之外,还用于下载属于感染链的组件。

Silence团伙的传播策略

此次攻击利用鱼叉式网络钓鱼电子邮件进行传播,邮件由俄语编写,并带有一个名为“Contract_12112018.Z”(2018年11月12日合同)的压缩附件。

对附件进行解压后,得到一个名为“Contract_12112018”的文件,其内容是银行开户协议,一旦解压就意味着开启了感染过程的第一步。

2.png

图2. 附件中的CHM恶意文件和Contract_12112018.chm文件的内容

此邮件会伪造成是由俄罗斯各家银行的官方地址发送的,调查结果显示,大多发送地址都是俄罗斯联邦央行,邮件内容如下:

您好!

我是Skurtov Andrei Vladimirovich,

PJSC FinServisBank银行业务关系主管。

我们可就卢布和其余可兑换货币协商开立及维护代理账户。

希望您能申请。

附件是档案和合同,如感兴趣,请填写并发送给我。

提前谢谢你,静待您的答复。

 

敬上,

PJSC FinServisBank银行业务关系主管

Nizhny Novgorod region, Sarov, Silkin street, 13

恶意组件

在下图中,我们完整重建了Silence团伙使用的CHM感染链,分为三个主要阶段:

· 下载启动感染链所需的初始有效负载(VBScript)。

· 由受感染计算机上的初始负载执行的活动,并下载主要恶意软件组件。

· 信息收集和传递给C&C。

3.png

图3. CHM感染链

编译的HTML帮助文件(contract_12112018.chm)的文件结构类似于超文本页面,需要通过本地Microsoft Windows程序“hh.exe”打开。CHM文件包含一个名为start.htm的文件和恶意payload,一旦CHM文件打开,start.htm便会自动运行,并启动cmd.exe和mshta.exe,从IP为 146.0.72.139处下载恶意VBscript(称为“li”)。这是感染链的第一阶段。

下图详细显示了用于启动mshta、下载并运行恶意VBS文件的命令行:

4.jpg

图4.恶意CHM文件中嵌入的HTM文件的内容

感染链的第二阶段继续执行“li”文件中包含的指令,主要负责:

· 复制一份cmd.exe和PowerShell.exe将它们分别重命名为ejpejpff.com和ejpejpf.com,并将它们保存在%TEMP%文件夹中。

· 调用参数-nop -W hidden -non – interactive -c的ejpejpf.com (Powershell.exe的一个副本):

1.下载Base64编码的“flk”有效负载,并将其保存到%TEMP%文件夹中,格式为“ejpej .txt”,

2.解码并保存为“ejpejp.com”

3.执行“ejpejp.com”

5.jpg

图5.li文件的内容

感染链的第三个也是最后一个阶段,将继续执行“ejpejp.com”,负责:

· 在\ AppData \ Roaming \中将自身复制为conhost.exe,该文件名也是合法的Console Windows Host经常调用的文件名,这么做可能是为了逃避检测

· 将引用添加到HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run作为持久性方法运行

· 运行System Information Discovery

恶意文件的元数据描述为“MS DefenderApplicationController”。

我们已经确认该应用程序是Silence团伙使用的木马变种,该木马负责收集每个受害者电脑的信息,是通过以下4个Windows系统二进制文件进行收集的:

· system.exe:执行“System Information”以收集有关受害者计算机配置和操作系统的详细信息,例如:产品ID,硬件功能和安全信息。

· net.exe:“Net View”用于收集有关局域网的信息以及启动/停止IPv6协议服务。

· whoami.exe:用于获取用户的当前域和用户名。

· ipconfig.exe:用于收集TCP / IP网络配置设置。

所有这些信息都存储在“INFOCONTENT.TXT”文件中,文件保存在%ProgramData%中,并上传到IP 146.0.72.188托管的服务器,该服务器是Silence团伙用于此次行动的C2。 以下是使用ReaQta-Hive重建攻击的详细步骤。

6.png

图6.用ReaQta-Hive重构的恶意CHM文件的步骤

具体细节请点击此处

网络基础设施

如上所示,此次攻击行动通过运行本机的几个二进制文件来收集目标银行基础设施的情报。

通信过程使用两个IP地址进行:第一个是146.0.72.139,它是下载攻击链不同部分的直接通道;第二个是通过IP地址146.0.72.188与C2进行通信,将采集到的信息进行过滤。这两个ip都位于荷兰。

7.jpg

图7.恶意的网络请求处理

归因

同往常一样,归因从来都不是一件容易的事,我们想要分享导致我们认为Silence团伙是这次攻击背后的因素:

· 其操作方式和感染载体(CHM)是Silence最新操作的典型特征

· CHM的内部结构是Silence团伙攻击的常见结构

· 下载的二进制文件与Silence使用的二进制文件相匹配(Truebot的变体)

· 鱼叉式网络钓鱼运动中使用的语言也匹配得上

· 目标位于东欧和俄罗斯

· 目标类型(金融机构)与Silence团伙通常选择的目标相匹配

· 在之前Silence团伙的攻击中发现的TTP与此处分析的攻击相匹配

· 这些因素使我们得出结论,Silence团伙(或附属团体)很可能是此次攻击背后的原因。

结论

我们收集的情报显示,这次攻击行动只是冰山一角,其攻击目标仍是主要在俄罗斯境内运作的金融机构。从感染过程、攻击链和操作结构来看,Silence团伙虽然还很年轻,但已经成为一个越来越有组织、越来越危险的银行恶意软件的传播源。

我们的分析是使用ReaQta-Hive进行的:端点可见性和威胁搜索能力是每个旨在降低网络攻击风险的威胁组织的需求。这类威胁显示了攻击者的速度有多快、适应性有多强,而检测、包含和响应的结构化流程对于防止对业务连续性的破坏和中断至关重要。

Mitre Att&ck

· T1193 : Spearphishing Attachment

· T1223 : Compiled HTML File

· T1105 : Remote File Copy

· T1043 : Commonly Used Port

· T1170 : Mshta

· T1036 : Masquerading

· T1059 : Command-Line Interface

· T1086 : Powershell

· T1064 : Scripting

· T1140 : Deobfuscate/Decode Files or Information

· T1060 : Registry Run Keys / Startup Folder

· T1082 : System Information Discovery

IOCs

SHA1 CHM files

· 20055FC3F1DB35B279F15D398914CABA11E5AD9D

· D83D27BC15E960DD50EAD02F70BD442593E92427

· 2250174B8998A787332C198FC94DB4615504D771

· 9D4BBE09A09187756533EE6F5A6C2258F6238773

· D167B13988AA0B277426489F343A484334A394D0

· 26A8CFB5F03EAC0807DD4FD80E80DBD39A7FD8A6

SHA1 Dropped files

· 290321C1A00F93CDC55B1A22DA629B3FCF192101

· 2CD620CEA310B0EDB68E4BB27301B2563191287B

· E5CB1BE1A22A7BF5816ED16C5644119B51B07837

IPs

· 146.0.72.139

· 146.0.72.188

近日PEAR 官方发布推文, 有个go-pear.phar安装包出现一个安全问题。

1.jpg

这意味着,如果你在过去6个月内从其官方网站下载了PHP PEAR包管理器(PHP PEAR package manager),则你的服务器很可能已经受到了攻击。

如果你在近六个月下载过这个 go-pear.phar 文件,你可以去 GitHub https://github.com/pear/pearweb_phars复制一份相同版本的副本文件,比较两文件的哈希值。如果不一样,你的文件很可能就是被篡改的。

上周,PEAR的维护人员在发现有人用核心PEAR文件系统中的篡改版本替换了原来的PHP PEAR包管理器(go-pear.phar),于是他们关闭了PEAR的官方网站。目前官方未公布服务重建的预计完成时间,不过维护人员目前正在进行调查,以确定攻击的范围以及攻击者最初是如何设法对服务器进行攻击的。

尽管PEAR开发人员仍在分析恶意程序包,根据2019年1月19日发布的一份安全声明,我们可以知道,被恶意代码污染的安装文件的下载时间已经至少有半年了。

PEAR是PHP扩展与应用库(the PHP Extension and Application Repository)的缩写。它是一个PHP扩展及应用的一个代码仓库,简单地说,PEAR之于PHP就像是CPAN(Comprehensive Perl Archive Network)之于Perl。PHP扩展和应用程序存储库(PEAR)是一个社区驱动的框架和分发系统,它使任何人都可以搜索和下载用PHP编程语言编写的免费库。这些开源库(更好地称为包)允许开发人员在其项目和网站中轻松添加其他功能,包括身份验证,缓存,加密,Web服务等等。PEAR的基本目标是发展成为PHP扩展和库代码的知识库,而这个项目最有雄心的目标则是试图定义一种标准,这种标准将帮助开发者编写可移植、可重用的代码。

当你为Unix/Linux/BSD系统下载PHP软件时,PEAR下载管理器(go-pear.phar)已预先安装,而Windows和Mac OS X用户需要手动安装该组件。

现在可以在Github上下载一个新的干净版本,即1.10.10版本的pearweb_pharshttps://github.com/pear/pearweb_phars/releases/tag/v1.10.10,Github也重新发布了干净的go-pear版本。 

受污染的go-pear.phar版本为1.10.9,现在每个受污染的phar文件都含有独立的GPG签名。但目前只发现pear.php.net服务器上的副本受到了影响,因此影响了go-pear的GitHub副本。

开发人员进一步通知说,只有pear.php.net服务器上的副本受到了影响,据他们所知, go-pear.phar的GitHub副本并没有受到影响。

由于PEAR没有公布任何细节,目前仍不清楚谁是这次袭击的幕后黑手。

所有在过去六个月内从官方网站下载安装文件go-pear.phar的PHP / PEAR用户,都应该是受到了攻击,我们建议快速下载并安装Github版本。

分析进展和安全注意事项

在分析了包管理器的受污染版本之后,维护团队发现恶意模块是通过Perl在IP 104.131.154.154服务器生成反向shell,通受感染的服务器,攻击者可以完全控制包管理器,比如安装恶意应用程序、运行恶意代码和窃取敏感数据。

德国网络安全组织DCSO也分析了这个受污染的代码,该组织表示,服务器IP地址104.131.154.154指向一个网络域名bestlinuxgames[.]com,这个可能是攻击者使用的一个受到攻击的主机。另外PEAR团队在一系列推文中也表示:

除了发现这个IP外,目前还没有发现其他漏洞,install-pear-nozlib.phar也没问题,GitHub上的go-pear.phar文件也没有问题,这些文件可以用作对于任何可疑md5sum副本的比较。所以,如果你是在2018年12月20号之后下载的go-pear.phar,且在系统上安装了PEAR包,那么你应该与安全的副本做个比较,特别是如果系统中有“sh”和“perl”可用时。如果你是在2018年12月20日之前下载了go-pear.phar,则不用担心。

另外,本次的污染不会影响PEAR安装程序包本身,它只会影响你最初安装PEAR安装程序时使用的go-pear.phar可执行文件,这意味着使用pear命令安装各种PEAR包是不受此次影响的。