CSAPP实验记录(2)--------- Bomb

news2024/10/2 22:16:33

实验简介

本实验需要拆除一个“二进制炸弹”,“二进制炸弹”是一个可执行目标程序。运行时,它会提示用户键入6个不同的字符串。如果其中任何一个错误,炸弹就会“爆炸”。必须通过逆向工程和汇编语言知识,推导出六个字符串分别是什么。从而拆除炸弹。

开始实验

下载实验包后,会解压得到一个bomb.c源码和bomb.o可执行目标文件。bomb.c源码如下:

int main(int argc, char *argv[])
{
    char *input;

    /* Note to self: remember to port this bomb to Windows and put a 
     * fantastic GUI on it. */

    /* When run with no arguments, the bomb reads its input lines 
     * from standard input. */
    if (argc == 1) {  
	infile = stdin;
    } 

    /* When run with one argument <file>, the bomb reads from <file> 
     * until EOF, and then switches to standard input. Thus, as you 
     * defuse each phase, you can add its defusing string to <file> and
     * avoid having to retype it. */
    else if (argc == 2) {
	if (!(infile = fopen(argv[1], "r"))) {
	    printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
	    exit(8);
	}
    }

    /* You can't call the bomb with more than 1 command line argument. */
    else {
	printf("Usage: %s [<input_file>]\n", argv[0]);
	exit(8);
    }

    /* Do all sorts of secret stuff that makes the bomb harder to defuse. */
    initialize_bomb();

    printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
    printf("which to blow yourself up. Have a nice day!\n");

    /* Hmm...  Six phases must be more secure than one phase! */
    input = read_line();             /* Get input                   */
    phase_1(input);                  /* Run the phase               */
    phase_defused();                 /* Drat!  They figured it out!
				      * Let me know how they did it. */
    printf("Phase 1 defused. How about the next one?\n");

    /* The second phase is harder.  No one will ever figure out
     * how to defuse this... */
    input = read_line();
    phase_2(input);
    phase_defused();
    printf("That's number 2.  Keep going!\n");

    /* I guess this is too easy so far.  Some more complex code will
     * confuse people. */
    input = read_line();
    phase_3(input);
    phase_defused();
    printf("Halfway there!\n");

    /* Oh yeah?  Well, how good is your math?  Try on this saucy problem! */
    input = read_line();
    phase_4(input);
    phase_defused();
    printf("So you got that one.  Try this one.\n");
    
    /* Round and 'round in memory we go, where we stop, the bomb blows! */
    input = read_line();
    phase_5(input);
    phase_defused();
    printf("Good work!  On to the next...\n");

    /* This phase will never be used, since no one will get past the
     * earlier ones.  But just in case, make this one extra hard. */
    input = read_line();
    phase_6(input);
    phase_defused();

    /* Wow, they got it!  But isn't something... missing?  Perhaps
     * something they overlooked?  Mua ha ha ha ha! */
    
    return 0;
}

可以看到代码中有许多没有给出定义(其实是定义在spport.h头文件中,这个头文件没有给出)的函数,也就是说我们仅仅通过bomb.c代码是不能得知我们要输入的六个字符串的,不过好在题目给出了代码编译出的可执行目标文件bomb.o,这样我们就有机会通过逆向工程确定bomb.c的源码了。
接下来运行可执行目标文件bomb.o,先随便输入个abc试一试效果:

在这里插入图片描述
果然会爆炸。。
看来现在可以对bomb.o的汇编代码进行分析了,使用objdump工具,将bomb.o的反汇编代码导入到txt文件中,这里我用
下面是主函数的反汇编代码:

0000000000400da0 <main>:
  400da0:	53                   	push   %rbx
  400da1:	83 ff 01             	cmp    $0x1,%edi
  400da4:	75 10                	jne    400db6 <main+0x16>
  400da6:	48 8b 05 9b 29 20 00 	mov    0x20299b(%rip),%rax        # 603748 <stdin@@GLIBC_2.2.5>
  400dad:	48 89 05 b4 29 20 00 	mov    %rax,0x2029b4(%rip)        # 603768 <infile>
  400db4:	eb 63                	jmp    400e19 <main+0x79>
  400db6:	48 89 f3             	mov    %rsi,%rbx
  400db9:	83 ff 02             	cmp    $0x2,%edi
  400dbc:	75 3a                	jne    400df8 <main+0x58>
  400dbe:	48 8b 7e 08          	mov    0x8(%rsi),%rdi
  400dc2:	be b4 22 40 00       	mov    $0x4022b4,%esi
  400dc7:	e8 44 fe ff ff       	callq  400c10 <fopen@plt>
  400dcc:	48 89 05 95 29 20 00 	mov    %rax,0x202995(%rip)        # 603768 <infile>
  400dd3:	48 85 c0             	test   %rax,%rax
  400dd6:	75 41                	jne    400e19 <main+0x79>
  400dd8:	48 8b 4b 08          	mov    0x8(%rbx),%rcx
  400ddc:	48 8b 13             	mov    (%rbx),%rdx
  400ddf:	be b6 22 40 00       	mov    $0x4022b6,%esi
  400de4:	bf 01 00 00 00       	mov    $0x1,%edi
  400de9:	e8 12 fe ff ff       	callq  400c00 <__printf_chk@plt>
  400dee:	bf 08 00 00 00       	mov    $0x8,%edi
  400df3:	e8 28 fe ff ff       	callq  400c20 <exit@plt>
  400df8:	48 8b 16             	mov    (%rsi),%rdx
  400dfb:	be d3 22 40 00       	mov    $0x4022d3,%esi
  400e00:	bf 01 00 00 00       	mov    $0x1,%edi
  400e05:	b8 00 00 00 00       	mov    $0x0,%eax
  400e0a:	e8 f1 fd ff ff       	callq  400c00 <__printf_chk@plt>
  400e0f:	bf 08 00 00 00       	mov    $0x8,%edi
  400e14:	e8 07 fe ff ff       	callq  400c20 <exit@plt>
  400e19:	e8 84 05 00 00       	callq  4013a2 <initialize_bomb>
  400e1e:	bf 38 23 40 00       	mov    $0x402338,%edi
  400e23:	e8 e8 fc ff ff       	callq  400b10 <puts@plt>
  400e28:	bf 78 23 40 00       	mov    $0x402378,%edi
  400e2d:	e8 de fc ff ff       	callq  400b10 <puts@plt>
  400e32:	e8 67 06 00 00       	callq  40149e <read_line>
  400e37:	48 89 c7             	mov    %rax,%rdi
  400e3a:	e8 a1 00 00 00       	callq  400ee0 <phase_1>
  400e3f:	e8 80 07 00 00       	callq  4015c4 <phase_defused>
  400e44:	bf a8 23 40 00       	mov    $0x4023a8,%edi
  400e49:	e8 c2 fc ff ff       	callq  400b10 <puts@plt>
  400e4e:	e8 4b 06 00 00       	callq  40149e <read_line>
  400e53:	48 89 c7             	mov    %rax,%rdi
  400e56:	e8 a1 00 00 00       	callq  400efc <phase_2>
  400e5b:	e8 64 07 00 00       	callq  4015c4 <phase_defused>
  400e60:	bf ed 22 40 00       	mov    $0x4022ed,%edi
  400e65:	e8 a6 fc ff ff       	callq  400b10 <puts@plt>
  400e6a:	e8 2f 06 00 00       	callq  40149e <read_line>
  400e6f:	48 89 c7             	mov    %rax,%rdi
  400e72:	e8 cc 00 00 00       	callq  400f43 <phase_3>
  400e77:	e8 48 07 00 00       	callq  4015c4 <phase_defused>
  400e7c:	bf 0b 23 40 00       	mov    $0x40230b,%edi
  400e81:	e8 8a fc ff ff       	callq  400b10 <puts@plt>
  400e86:	e8 13 06 00 00       	callq  40149e <read_line>
  400e8b:	48 89 c7             	mov    %rax,%rdi
  400e8e:	e8 79 01 00 00       	callq  40100c <phase_4>
  400e93:	e8 2c 07 00 00       	callq  4015c4 <phase_defused>
  400e98:	bf d8 23 40 00       	mov    $0x4023d8,%edi
  400e9d:	e8 6e fc ff ff       	callq  400b10 <puts@plt>
  400ea2:	e8 f7 05 00 00       	callq  40149e <read_line>
  400ea7:	48 89 c7             	mov    %rax,%rdi
  400eaa:	e8 b3 01 00 00       	callq  401062 <phase_5>
  400eaf:	e8 10 07 00 00       	callq  4015c4 <phase_defused>
  400eb4:	bf 1a 23 40 00       	mov    $0x40231a,%edi
  400eb9:	e8 52 fc ff ff       	callq  400b10 <puts@plt>
  400ebe:	e8 db 05 00 00       	callq  40149e <read_line>
  400ec3:	48 89 c7             	mov    %rax,%rdi
  400ec6:	e8 29 02 00 00       	callq  4010f4 <phase_6>
  400ecb:	e8 f4 06 00 00       	callq  4015c4 <phase_defused>
  400ed0:	b8 00 00 00 00       	mov    $0x0,%eax
  400ed5:	5b                   	pop    %rbx
  400ed6:	c3                   	retq   
  400ed7:	90                   	nop
  400ed8:	90                   	nop
  400ed9:	90                   	nop
  400eda:	90                   	nop
  400edb:	90                   	nop
  400edc:	90                   	nop
  400edd:	90                   	nop
  400ede:	90                   	nop
  400edf:	90                   	nop

第一个炸弹

在这里插入图片描述

从bomb.c的代码片段可以看出,每道题目都是由一个函数开始的,我们在反汇编代码中找到phase_1的代码:

0000000000400ee0 <phase_1>:
  400ee0:	48 83 ec 08          	sub    $0x8,%rsp
  400ee4:	be 00 24 40 00       	mov    $0x402400,%esi
  400ee9:	e8 4a 04 00 00       	callq  401338 <strings_not_equal>
  400eee:	85 c0                	test   %eax,%eax
  400ef0:	74 05                	je     400ef7 <phase_1+0x17>
  400ef2:	e8 43 05 00 00       	callq  40143a <explode_bomb>
  400ef7:	48 83 c4 08          	add    $0x8,%rsp
  400efb:	c3                   	retq   

可以看到在第二行,程序将一个好像是地址的变量赋给了%esi,然后调用了<strings_not_equal>函数,从字面上可以看出这个函数好像是判断字符串是否相等。
容易想到,mov $0x402400,%esi这句指令是在为调用<strings_not_equal>构造参数,而此时寄存器%rdi的值并没有变,所以可以想到<strings_not_equal>函数有两个参数,一个是位于 0x402400 位置的数据,另一个就是 <phase_1> 本身的参数,也就是我们输入的字符串。
从<strings_not_equal>返回后,执行test指令,如果返回值不为0,那么就会执行<explode_bomb>,这个显然是让炸弹爆炸的函数,我们不能让程序执行到这里。
然后找到 0x402400:发现其位于可执行目标文件的.rodata节:

在这里插入图片描述
在这里插入图片描述
也就是说,第一个炸弹的逻辑是,输入的字符串与程序中内置的格式串比对,相同就通过测试。观察0x402400处的字节序列,可以发现答案就Border relations with Canada have never been better.,这句话最后的句号坑了我很长时间,因为.rodata节里的不可见字符全都会显示成"."。总的来说第一个炸弹十分简单。

第二个炸弹

找到phase_2的反汇编代码部分:

0000000000400efc <phase_2>:
  400efc:	55                   	push   %rbp                            //被调用者保存寄存器
  400efd:	53                   	push   %rbx                            //被调用者保存寄存器
  400efe:	48 83 ec 28          	sub    $0x28,%rsp
  400f02:	48 89 e6             	mov    %rsp,%rsi
  400f05:	e8 52 05 00 00       	callq  40145c <read_six_numbers>
  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
  400f0e:	74 20                	je     400f30 <phase_2+0x34>
  400f10:	e8 25 05 00 00       	callq  40143a <explode_bomb>
  400f15:	eb 19                	jmp    400f30 <phase_2+0x34>
  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
  400f1a:	01 c0                	add    %eax,%eax
  400f1c:	39 03                	cmp    %eax,(%rbx)
  400f1e:	74 05                	je     400f25 <phase_2+0x29>
  400f20:	e8 15 05 00 00       	callq  40143a <explode_bomb>
  400f25:	48 83 c3 04          	add    $0x4,%rbx
  400f29:	48 39 eb             	cmp    %rbp,%rbx
  400f2c:	75 e9                	jne    400f17 <phase_2+0x1b>
  400f2e:	eb 0c                	jmp    400f3c <phase_2+0x40>
  400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx
  400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp
  400f3a:	eb db                	jmp    400f17 <phase_2+0x1b>
  400f3c:	48 83 c4 28          	add    $0x28,%rsp
  400f40:	5b                   	pop    %rbx
  400f41:	5d                   	pop    %rbp
  400f42:	c3                   	retq  

可以看到有一个叫<read_six_numbers>的函数,字面意思是要读取6个数字的输入。而且注意到400f02: 48 89 e6 mov %rsp,%rsi,它似乎需要栈指针作为第二个参数,和第一个炸弹一样,此时%rdi同样没有改变,说明<read_six_numbers>函数的第一个参数也是我们输入的字符串。

通过观察400f0e一行,可以得知调用<read_six_numbers>后,如果栈指针处的值为1,那么将跳转到400f30(核心代码所在处),否则直接执行炸弹。那么我们可以猜想如果我们以正确的格式输入六个数字的话,栈指针此时一定是等于0x1的。我们可以找到<read_six_numbers>的位置,看看这个函数到底做了什么。

000000000040145c <read_six_numbers>:
  40145c:	48 83 ec 18          	sub    $0x18,%rsp
  401460:	48 89 f2             	mov    %rsi,%rdx
  401463:	48 8d 4e 04          	lea    0x4(%rsi),%rcx
  401467:	48 8d 46 14          	lea    0x14(%rsi),%rax
  40146b:	48 89 44 24 08       	mov    %rax,0x8(%rsp)
  401470:	48 8d 46 10          	lea    0x10(%rsi),%rax
  401474:	48 89 04 24          	mov    %rax,(%rsp)
  401478:	4c 8d 4e 0c          	lea    0xc(%rsi),%r9
  40147c:	4c 8d 46 08          	lea    0x8(%rsi),%r8
  401480:	be c3 25 40 00       	mov    $0x4025c3,%esi
  401485:	b8 00 00 00 00       	mov    $0x0,%eax
  40148a:	e8 61 f7 ff ff       	callq  400bf0 <__isoc99_sscanf@plt>
  40148f:	83 f8 05             	cmp    $0x5,%eax
  401492:	7f 05                	jg     401499 <read_six_numbers+0x3d>
  401494:	e8 a1 ff ff ff       	callq  40143a <explode_bomb>
  401499:	48 83 c4 18          	add    $0x18,%rsp
  40149d:	c3                   	retq   

可以看到整个函数的前半部分都在进行参数的构造,只是为了能够调用__isoc99_sscanf@plt,而之前我们推导过,这个函数的第二个参数%rsi其实是栈指针,也就是说这些操作%rsi的指令其实都是对栈指针的操作。这里的<__isoc99_sscanf>函数是动态链接进来的C标准库函数,在可执行目标文件里是看不到它的信息的,所以我们只能上网查这个函数的功能了。经过查资料,我大概可以知道这个函数在<read_six_numbers>里的作用是,将输入的六个整数保存在 (%rsp),(%rsp+0x4),(%rsp+0x8),(%rsp+0xc),(%rsp+0x10),(%rsp+0x14),这六个位置。也就是栈上的一片地址连续的空间中。

然后就是从位置400f30处开始的核心代码了,可以看到指令定义了两个指针,%rbx和%rbp,一个指向 0x4(%rsp),一个指向 0x18(%rsp),这中间就是刚才我们输入的六个整数保存的位置。从:

  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
  400f1a:	01 c0                	add    %eax,%eax
  400f1c:	39 03                	cmp    %eax,(%rbx)
  400f1e:	74 05                	je     400f25 <phase_2+0x29>
  400f20:	e8 15 05 00 00       	callq  40143a <explode_bomb>

这几句可以看出,程序不断将 %rbx 与 -0x4(%rbx) 对比,400f1a 一句表明当且仅当 (%rbx) 是 -0x4(%rbx)的二倍时,炸弹才不会爆炸。

  400f25:	48 83 c3 04          	add    $0x4,%rbx
  400f29:	48 39 eb             	cmp    %rbp,%rbx
  400f2c:	75 e9                	jne    400f17 <phase_2+0x1b>

这三句又说明,%rbx只有和 %rbp 相等时,才能结束程序。否则将%rbx的值加4(一个int的大小),之后跳回400f17。这明显是一个循环语句。刚才我们了解到 %rbp = 0x18(rbx),也就是说当%rbx = %rbp时,程序刚好扫描到我们输入的六个数字,而且每个数字都应该是前一个的二倍,第一个数字等于 (%rsp) = 0x1,那么这个炸弹就被成功解开了。答案是:
1 2 4 8 16 32

第三个炸弹

截至最终更新时,作者已经解开了四个炸弹,但这个实验总归还是比较繁琐,还剩下两个没有解开,而且像这样详细地复盘也比较费时间,剩下的之后再补 😃 。

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

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

相关文章

Linux权限管理

修改权限-chmod 通过chmod指令&#xff0c;可以修改文件或者目录的权限 第一种方式 - 变更权限 u:所有者 g:所有组 o:其他人 a:所有人 chmod urwx,grx,ox 文件目录名 chmod ow …

条件随机场CRF(持续更新ing...)

诸神缄默不语-个人CSDN博文目录 本文是作者学习CRF后的笔记。 最近更新时间&#xff1a;2022.11.15 最早更新时间&#xff1a;2022.11.15 条件随机场CRF是适宜于顺序预测任务的判别模型&#xff0c;可用于命名实体识别、词性标注等。 文章目录1. linear-chain CRF2. CRF目标…

使用TensorRT量化ResNet50网络(PTQ)

深度学习正在彻底改变行业提供产品和服务的方式。 这些服务包括用于计算机视觉的对象检测、分类和分割&#xff0c;以及用于基于语言的应用程序的文本提取、分类和摘要。 这些应用程序必须实时运行。 大多数模型都采用浮点 32 位算法进行训练&#xff0c;以利用更大的动态范…

python代码规范工具

文章目录一&#xff1a;Pycharm 创建文件自动头部二&#xff1a;代码门禁三 : 附录一&#xff1a;Pycharm 创建文件自动头部 Pycham —> Preferences —> 编辑器 —> 文件和代码模版 —> 文件 —> Python Script —> 填写头部 #!/usr/bin/env python # -*- c…

详解clickhouse分区目录的合并过程

数据存储底层分布 目录名类型说明202103_1_10_2目录分区目录一个或多个&#xff0c;由于分区LSM生成的detached目录通过DETACH语句卸载后的表分区存放位置format_version.txt文本文件纯文本&#xff0c;记录存储的格式 columns.txt&#xff1a;该文件是一个文本文件&#xff0c…

vue 生成二维码的两种方式

方式一&#xff1a;qrcode&#xff08;无 icon 图标&#xff09; npm i qrcodejs2 --save 完整代码 <template><div class"flex-box"><div>qrcode&#xff08;无 icon 图标&#xff09;</div><div class"qr-code" ref"…

【面试题】BFC的理解

1. BFC Block format context(BFC)&#xff0c;块级格式上下文。BFC是一个独立的布局环境&#xff0c;BFC内部的元素的渲染不会影响到边界以外的元素。 2. BFC的布局规则 BFC内部的块会在垂直方向上一个接一个的放置&#xff1b;垂直方向上的距离由margin决定&#xff0c;在…

vision transformer 剪枝论文汇总

Vision Transformer Pruning 这篇论文的核心思想很简单&#xff0c;就是剪维度&#xff0c;也就是说剪的是这个d。 具体方法就是通过一个gate&#xff0c;如图中的dimension pruning&#xff0c;输出0或者1&#xff0c;来判断不同维度的重要性&#xff0c;但是由于0和1&#xf…

最新CleanMyMac X4.12.1中文版Mac系统优化清理工具

CleanMyMac X v4.12.1是COCO玛奇朵搜集到的一款mac电脑系统清理工具&#xff0c;删除系统缓存文件 , 多余的应用程序语言包 , PowerPc软件运行库等。 CleanMyMac是一个强大的应用程序&#xff0c;清洁&#xff0c;优化和保护您的Mac多年的使用。运行即时系统清理&#xff0c;卸…

html+css实现容器显示两行文本超出部分以省略号显示

Bootstrap 给予响应式、移动设备优先的流式栅格系统提供了十分丰富的样式类&#xff0c;基于这些了可以做出很多好看的效果&#xff0c;虽让提供了常用基本样式类&#xff0c;但有一些比较特殊的需求 比如bootstrap 提供了 text-truncate 样式类&#xff0c;使用这个类可以轻松…

只开源36小时!GitHub标星139K从Java开发到软件架构师进阶笔记

什么是架构? 关于架构&#xff0c;我以前一直以为&#xff0c;只有真正从0到1&#xff0c;经历各种技术选型后搭建出来的一个系统框架&#xff0c;才算是真正的架构。 在程序员的现实世界里&#xff0c;不想当架构师的程序员不是个好程序员&#xff0c;即使你未曾主动想去当…

Mac M1 安装 brew

安装顺序&#xff1a; 1. 安装 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 执行完后&#xff0c;发现brew &#xff0c;还是命令找不到。 2. 还要配置&#xff1a; 复制该内容到 vim ~/.zprofile&#x…

ElasticSearch——手写一个ElasticSearch分词器(附源码)

1. 分词器插件 ElasticSearch提供了对文本内容进行分词的插件系统&#xff0c;对于不同的语言的文字分词器&#xff0c;规则一般是不一样的&#xff0c;而ElasticSearch提供的插件机制可以很好的集成各语种的分词器。 Elasticsearch 本身并不支持中文分词&#xff0c;但好在它…

Python——BeautifulSoup库

下面例题基于同一个网页讲解&#xff0c;进入网页后右键查看页面源代码对应着看 文章目录前言一、BeautifulSoup库简介什么是BeautifulSoup库&#xff1f;BeautifulSoup库解析器网页爬虫解析网页的基本方法二、BeautifulSoup库的安装安装测试三、BeautifulSoup库的基本元素获取…

APS排产在线材线束行业的应用

经过数年的发展&#xff0c;我国线束行业从无到有、从小到大&#xff0c;已经具备了一定的规模&#xff0c;其生产的线束产品能够满足市场上绝大多数的需求。世界上&#xff0c;任何事物的发展都不可能一帆风顺&#xff0c;总是在矛盾中纠正自身不足&#xff0c;从而不断前进。…

关于CUDA+Torch+TorchVision+Python环境配置问题

背景知识 1、GPU的并行计算能力&#xff0c;在过去几年里恰当地满足了深度学习的需求。在训练强化学习模型时&#xff0c;为了提供更好地算力和训练时间&#xff0c;因此需要使用GPU。 2、CUDA&#xff1a;是Nvidia推出的只能用于自家GPU的并行计算框架。只有安装这个框架才能…

机器学习笔记之条件随机场(五)条件随机场需要解决的任务介绍

机器学习笔记之条件随机场——条件随机场需要解决的任务介绍引言回顾&#xff1a;条件随机场条件随机场要解决的任务引言 上一节介绍了条件随机场的建模对象——条件概率P(I∣O)\mathcal P(\mathcal I \mid \mathcal O)P(I∣O)参数形式和向量形式的表示。本节将针对条件随机场…

java多线程基础技术

1.1 进程与多线程 1、进程 程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加载至 CPU&#xff0c;数据加载至内存。在 指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的 当一个程…

Mysql 数据库开发简介与选择

文章目录 前言一、为什么要使用数据库 数据库的概念为什么要使用数据库二、程序员为什么要学习数据库三、数据库的选择 主流数据库简介使用MySQL的优势版本选择四、Windows 平台下安装与配置MySQL 启动MySQL 服务控制台登录MySQL命令五、Linux 平台下安装与配置MySQL总结前言 …

【附源码】计算机毕业设计JAVA宠物收养管理

【附源码】计算机毕业设计JAVA宠物收养管理 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA mybati…