• Welcome to the world's largest Chinese hacker forum

    Welcome to the world's largest Chinese hacker forum, our forum registration is open! You can now register for technical communication with us, this is a free and open to the world of the BBS, we founded the purpose for the study of network security, please don't release business of black/grey, or on the BBS posts, to seek help hacker if violations, we will permanently frozen your IP and account, thank you for your cooperation. Hacker attack and defense cracking or network Security

    business please click here: Creation Security  From CNHACKTEAM

Lenovo ThinkPad 0day Bug


This Wind

Recommended Posts

 
#!/usr/bin/python

'''
#############################################################################
  THINKPWN SCANNER
  This program is used to scan UEFI drivers extracted from firmware image 
  for ThinkPwn vulnerability in vendor/model agnostic way.
  For more information about this vulenrability check the following links:
    https://github.com/Cr4sh/ThinkPwn
    http://blog.cr4.sh/2016/06/exploring-and-exploiting-lenovo.html
  AUTHORS: 
    @d_olex (aka Cr4sh) -- initial Vivisect based version of the program;
    @trufae (aka pankake) -- radare2 based version (this one); 
  To check the binary for ThinkPwn vulnerability we have to find a vulnerable 
  System Management Mode (SMM) callback that usually has the following look:    
                =------------------------------=
                | push rbx                     |
                | sub rsp, 0x20                |
                | mov rax, qword [rdx + 0x20]  |
                | mov rbx, rdx                 |
                | test rax, rax                |
                | je 0xa5c                     |
                =------------------------------=
                        f t
             .----------' '----------------.
             |                             |
             |                             |
     =-------------------------------=     |
     | mov rcx, qword [rax]          |     |
     | lea r8, [rdx + 0x18]          |     |
     | mov rdx, qword [rip + 0x5f4]  |     |
     | call qword [rax + 8]          |     |
     | and qword [rbx + 0x20], 0     |     |
     =-------------------------------=     |
         v                                 |
         '---------------.     .-----------'
                         |     |
                         |     |
                     =--------------------=
                     | xor eax, eax       |
                     | add rsp, 0x20      |
                     | pop rbx            |
                     | ret                |
                     =--------------------=
  And decompiled C code of this function:
    EFI_STATUS __fastcall sub_AD3AFA54(
        EFI_HANDLE SmmImageHandle, VOID *CommunicationBuffer, UINTN *SourceSize)
    {
        VOID *v3; // rax@1
        VOID *v4; // rbx@1
        // get some structure pointer from EFI_SMM_COMMUNICATE_HEADER.Data
        v3 = *(VOID **)(CommunicationBuffer + 0x20);
        v4 = CommunicationBuffer;
        if (v3)
        {
            /*
              Vulnarability is here:
              this code calls some function by address from obtained v3 structure field.
            */
            *(v3 + 0x8)(*(VOID **)v3, &dword_AD002290, CommunicationBuffer + 0x18);
            // set zero value to indicate successful operation
            *(VOID **)(v4 + 0x20) = 0;
        }
        
        return 0;
    }
  To match the vulnerable function shown above program uses a simple binary heuristics
  that checks number of basic blocks, instructions, global variable usage, etc. 
  See match_func() subroutine for more details.
  USAGE:
    1) Install radare2 and r2pipe for Python:
       https://radare.org/
       https://pypi.python.org/pypi/r2pipe
    2) Unpack UEFI firmware image from your computer using UEFIExtract, it's a part 
       of UEFITool (https://github.com/LongSoft/UEFITool):
       # UEFIExtract firmware_image.bin all
    3) Run scan_thinkpwn.py with path to the extracted firmware image contents as argument:
       # python scan_thinkpwn.py firmware_image.bin.dump
    4) At the end of the scan you will see the list of vulnerable SMM callbacks and UEFI
       drivers where they're located.
  Example of program output on vulnerable firmware from ThinkPad T450s:
    http://www.everfall.com/paste/id.php?cztv0fmo03gv
#############################################################################
'''

import os, sys, errno
from threading import Thread
from Queue import Queue

import r2pipe

# Do not load r2 plugins to speedup startup times
os.environ['R2_NOPLUGINS'] = '1'

# you might want to change these paramenetrs to tune the heuristics
BB_COUNT = 3
MAX_INSN = 10
MIN_INSN = 3
GUID_LEN = 0x10

# scan only EFI drivers that contains these GUIDs
GUID_LIST = \
[
    # SMM base protocol GUID
    '\x4D\x95\x90\x13\x95\xDA\x27\x42\x93\x28\x72\x82\xC2\x17\xDA\xA8',

    # SMM communication protocol GUID
    '\xE2\xD8\x8E\xC6\xC6\x9D\xBD\x4C\x9D\x94\xDB\x65\xAC\xC5\xC3\x32',

    # SMM communicate header GUID
    '\x6C\xE3\x28\xF3\xB6\x23\x95\x4A\x85\x4B\x32\xE1\x95\x34\xCD\x75'
]

WORKERS = 4

q, results = Queue(), []

def has_guid(file_path, guid_list, find_any = False):

    with open(file_path, 'rb') as fd:

        data, guid_found = fd.read(), []
        
        # lookup for one or all of the specified GUIDs inside file contents
        for guid in guid_list:

            if data.find(guid) != -1:

                if find_any: return True
                if not guid in guid_found: guid_found.append(guid)

        return len(guid_found) == len(guid_list)

def is_valid_file(file_path):

    with open(file_path, 'rb') as fd:

        # check for DOS header signature
        if fd.read(2) != 'MZ': return False

    # check if image contains needed GUIDs
    return has_guid(file_path, GUID_LIST, find_any = True)

def insn_uses_global(op):

    if op['type'] == 'mov':

        # get global variable information if MOV instruction is using it
        return ( op['esil'].find('rip,+,[8]') != -1, op['esil'].find('=[') != -1 )

    # not a MOV instruction    
    return (0, 0)

class BasicBlock(object):

    def __init__(self, r2, addr, size, insn_num):

        self.addr, self.size = addr, size
        self.insn_num = insn_num
        
        self.calls_total, self.calls_matched = 0, 0
        self.glob_reads, self.glob_writes = 0, 0
        
        # disassemble basic block
        r2ops = r2.cmdj('aoj %d @ 0x%x' % (insn_num, addr))

        # update instructions information
        for op in r2ops:
        
            # check for the CALL instruction
            self.check_call(op)

            # check for the MOV instruction with global variable as operand
            self.check_glob(op)

    def check_call(self, op):
        
        if op['type'] == 'call':

            # regular fucntion call
            self.calls_total += 1

        elif op['type'] == 'ucall' and op['opcode'].find('[') != -1:

            # call function by pointer
            self.calls_total += 1
            self.calls_matched += 1

    def check_glob(self, op):

        # check if instruction reads or writes some global variable
        r, w = insn_uses_global(op)
        if r: self.glob_reads += 1
        if w: self.glob_writes += 1

def match_func(r2, addr):

    bb_all = []

    # obtain list of basic blocks for given function
    bb_list = r2.cmdj('afbj %s' % addr)
    if len(bb_list) != BB_COUNT: return False
    
    for bb in bb_list:

        insn_num = bb['ninstr']
    
        # check basic block for proper amount of instruction
        if insn_num > MAX_INSN or insn_num < MIN_INSN:
            
            return False

        # analyze basic block
        bb = BasicBlock(r2, bb['addr'], bb['size'], insn_num)
        bb_all.append(bb)

    #
    # check calls and global variables usage for each basic block
    #
    if bb_all[0].calls_total != 0 or bb_all[0].calls_matched != 0: return False
    if bb_all[0].glob_reads  != 0 or bb_all[0].glob_writes   != 0: return False

    if bb_all[1].calls_total != 1 or bb_all[1].calls_matched != 1: return False
    if bb_all[1].glob_reads  != 1 or bb_all[1].glob_writes   != 0: return False
    
    if bb_all[2].calls_total != 0 or bb_all[2].calls_matched != 0: return False
    if bb_all[2].glob_reads  != 0 or bb_all[2].glob_writes   != 0: return False
    
    # vulnerable function was matched!
    return True

class Watcher:
    ''' This class solves two problems with multithreaded
    programs in Python, (1) a signal might be delivered
    to any thread (which is just a malfeature) and (2) if
    the thread that gets the signal is waiting, the signal
    is ignored (which is a bug). '''

    def __init__(self):
        ''' Creates a child thread, which returns.  The parent
        thread waits for a KeyboardInterrupt and then kills
        the child thread. '''

        self.child = os.fork()

        if self.child == 0: return
        else: self.watch()

    def watch(self):

        try:

            os.wait()

        except KeyboardInterrupt:

            print('\nEXIT')

            self.kill()

        sys.exit(errno.ECANCELED)

    def kill(self):

        try: os.kill(self.child, signal.SIGKILL)
        except OSError: pass

def scan_file(file_path):

    ret = []

    print('Scanning \"%s\"...' % file_path)

    # start radare instance
    r2 = r2pipe.open(file_path)

    # perform initial analysis
    r2.cmd('aa;aad')

    # enumerate available functions
    for addr in r2.cmdj('aflqj'):

        # check for vulnerable function
        if match_func(r2, addr):

            print('VULNERABLE FUNCTION: %s' % addr)

            ret.append(addr)

    # close radare instance
    r2.quit()

    return ret

def worker():

    global q, results

    while True:

        file_path = q.get()

        # scan single file
        procs = scan_file(file_path)

        if len(procs) > 0: 

            # save scan results
            results.append(( file_path, procs ))
        
        q.task_done()

def scan_dir(dir_path):

    for file_name in os.listdir(dir_path):

        file_path = os.path.join(dir_path, file_name)        

        if os.path.isfile(file_path) and is_valid_file(file_path):

            # queue scanning of the single file
            q.put(file_path)

        elif os.path.isdir(file_path):

            scan_dir(file_path)

def main():

    global q, results

    if len(sys.argv) < 2:

        print('USAGE: scan_thinkpwn.py <unpacked_firmware_dir>')
        return -1

    # ctrl+C handling stuff
    if sys.platform != 'win32': Watcher()

    # run worker threads
    for i in range(WORKERS):

         t = Thread(target = worker)
         t.daemon = True
         t.start()

    # scan files in target directory
    scan_dir(sys.argv[1])
    q.join()

    print('**************************************')
    print('SCAN RESULTS:')

    # print scan results
    for file_path, matched in results:

        print('\n' + file_path + '\n')

        for addr in matched:

            print(' * %s' % addr)

    print('')

    return 0

if __name__ == '__main__':

    exit(main())

#
# EoF
#

 

原文链接:https://github.com/Cr4sh/ThinkPwn

有关此项目的更多信息,请阅读以下文章:

http://blog.cr4.sh/2016/06/exploring-and-exploiting-lenovo.html


此代码利用Lenovo固件的SystemSmmRuntimeRt UEFI驱动程序(GUID为7C79AC8C-5E6C-4E3D-BA6F-C260EE7C172E)中的0day特权升级漏洞(或后门?)。所有ThinkPad系列笔记本电脑都存在漏洞,我检查过的最旧的笔记本电脑是X220,而最新的笔记本电脑是T450(目前提供最新固件版本)。通过运行任意系统管理模式代码,攻击者可以在Windows 10 Enterprise上禁用闪存写保护并感染平台固件,禁用安全启动,绕过虚拟安全模式(Credential Guard等),并做其他恶作剧。

########################################

从30.06.2016更新:

Lenovo从8系列芯片组的英特尔参考代码中复制粘贴了SystemSmmRuntimeRt UEFI驱动程序的易受攻击的代码。此确切的代码在公共场所不可用,但某些英特尔主板的开源固件也正在共享它。例如,在这里您可以看到Intel Quark BSP的SmmRuntimeManagementCallback()函数-完全相同的易受攻击的代码。

https://kernel.googlesource.com/pub/scm/linux/kernel/git/jejb/Quark_EDKII/+/master/QuarkSocPkg/QuarkNorthCluster/Smm/Dxe/SmmRuntime/SmmRuntime.c#639

来自公共存储库的EDK2源从未存在此漏洞-与易受攻击的版本相比,它的QuarkSocPkg版本进行了重大修改:

https://github.com/tianocore/edk2/commits/master/QuarkSocPkg

这意味着最初的漏洞已由英特尔在2014年中修复。不幸的是,没有任何公开的批评,因此,仍不清楚英特尔或联想实际上是否知道此漏洞。其他OEM / IBV供应商的固件中当前存在具有此漏洞的旧英特尔代码的可能性很高。

########################################

从01.07.2016更新:

联想发布了有关此漏洞的公告,他们声称从第三方IBV(独立BIOS供应商)收到了英特尔编写的易受攻击的代码:

https://support.lenovo.com/my/zh/solutions/LEN-8324

现在我们可以肯定地说其他OEM的产品也存在此漏洞。

########################################

从02.07.2016更新:

我的一名追随者确认他的HP Pavilion笔记本电脑中存在易受攻击的代码:

https://twitter.com/al3xtjames/status/749063556486791168

########################################

2016年5月7日更新:

Alex James在技嘉(Z68-UD3H,Z77X-UD5H,Z87MX-D3H,Z97-D3H等)的主板上发现了易受攻击的代码:

https://twitter.com/al3xtjames/status/750163415159582720 
https://twitter.com/al3xtjames/status/750183816266940417

一个有趣的事实-固件中易受攻击的UEFI驱动程序具有与HP和联想不同的GUID值:A56897A1-A77F-4600-84DB-22B0A801FA9A

########################################

从06.07.2016更新:

日本研究人员173210在富士通LIFEBOOK A574 / H的固件中发现了易受攻击的代码,其他富士通计算机也可能受到了影响:

https://twitter.com/173210/status/750565904111562752 
https://twitter.com/173210/status/750569389741731840

########################################

2016年7月7日更新:

Kasey Smith认为Dell Latitude E6430也容易受到攻击,这意味着Dell的其他计算机也可能会受到影响:

https://twitter.com/Ziginox/status/750778513012043776

########################################

2016年8月7日更新:

联想通过产品型号信息更新了其咨询服务,这些产品是IdeaPad和ThinkPad系列产品中很多易受攻击的机器:

https://support.lenovo.com/my/zh/solutions/LEN-8324

另外,似乎在基于Skylake的Lenovo计算机中不存在此漏洞。

########################################

2016年8月8日更新:

英特尔的服务器主板似乎也很脆弱,英特尔发布了自己的ThinkPwn(aka SmmRuntime)漏洞公告: 

https://security-center.intel.com/advisory.aspx?intelid=INTEL-SA-00056&languageid=zh-CN

修复程序将于9月19日发布。 

########################################

从09.08.2016更新:

惠普发布了ThinkPwn通报,其中列出了易受攻击的计算机型号:

http://h20564.www2.hp.com/hpsc/doc/public/display?docId=emr_na-c05230715

补丁程序尚不可用。

########################################


脆弱的SMM回调代码:

    EFI_STATUS __fastcall sub_AD3AFA54(
        EFI_HANDLE SmmImageHandle,VOID * CommunicationBuffer,UINTN * SourceSize)
    {
        无效* v3; // rax @ 1
        无效* v4; // rbx @ 1

        //从EFI_SMM_COMMUNICATE_HEADER.Data获取一些结构指针
        v3 = *(VOID **)(CommunicationBuffer + 0x20);
        v4 = CommunicationBuffer;
        如果(v3)
        {
            / *
              易损性在这里:
              此代码从获得的v3结构字段中按地址调用某些函数。
            * /
            *(v3 + 0x8)(*(VOID **)v3,&dword_AD002290,CommunicationBuffer + 0x18);

            //设置零值以指示操作成功
            *(无效**)(v4 + 0x20)= 0;
        }
        
        返回0;
    }


此漏洞的概念证明利用被设计为从UEFI Shell运行的UEFI应用程序。也可以从运行的操作系统中利用它,但是您必须实现自己的EFI_BASE_PROTOCOL.Communicate()函数。

这个精确的PoC只为Lenovo计算机设计,很幸运,它可以在其他任何地方使用。如果它将无法利用任何其他供应商机器上的漏洞,则并不意味着该机器不会受到攻击,这可能只是漏洞利用所不支持。

要使用Visual Studio编译器在Windows上从源代码构建漏洞,必须执行以下步骤:

  1.将ThinkPwn项目目录复制到EDK2源代码目录中。

  2.运行Visual Studio 2008命令提示符,并运行cd到EDK2目录。

  3.执行Edk2Setup.bat --pull配置构建环境并下载所需的二进制文件。

  4.编辑AppPkg / AppPkg.dsc文件,并将ThinkPwn / ThinkPwn.dsc的路径添加到[Components]部分的末尾。

  5. cd到ThinkPwn项目目录并运行build命令。

  6.编译后,将在Build / AppPkg / DEBUG_VS2008x86 / X64 / ThinkPwn / ThinkPwn / OUTPUT / ThinkPwn.efi中创建生成的PE映像文件。


在您自己的硬件上测试漏洞利用:

  1.使用ThinkPwn.efi和UEFI Shell(https://github.com/tianocore/tianocore.github.io/wiki/Efi-shell)二进制文件准备FAT32格式的USB闪存盘。

  2.引导到UEFI Shell中并执行ThinkPwn.efi应用程序。


用法示例:

FS1:\> ThinkPwn.efi

SMM访问协议位于0xaa5f8b00
可用的SMRAM区域:
 * 0xad000000:0xad3fffff
SMM基本协议位于0xaa989340
SMM通信呼叫的缓冲区分配在0xacbfb018
获取FvFile(7C79AC8C-5E6C-4E3D-BA6F-C260EE7C172E)图像句柄...
 *手柄= 0xa4aee798
   Communicate()返回状态0x0000000e,数据大小为0x1000
 *手柄= 0xa4aee298
   Communicate()返回状态0x00000000,数据大小为0x1000
SmmHandler()已执行,开发成功!


此存储库还包含一个Python程序,该程序允许扫描任何计算机供应商/型号的固件映像,以查找具有ThinkPwn漏洞的UEFI SMM驱动程序。有关更多信息,请检查其源代码:

https://github.com/Cr4sh/ThinkPwn/blob/master/scan_thinkpwn.py


撰写人:
Dmytro Oleksiuk(又名Cr4sh)

 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now