pwn学习日记Day2 基础知识积累

时间:2024-05-21 18:04:26

知识杂项

shell-storm.org的shellcode数据库
使用pwntools库把shellcode作为输入传递给程序,尝试使用io.interactive()与程序进行交互,发现可以执行shell命令。
shellcode:是一段可以执行特定功能的神秘代码。
shell-storm:上还有可以执行其他功能如关机,进程炸弹,读取/etc/passwd等的shellcode。
EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。
EBX 是"基地址"(base)寄存器, 在内存寻址时存放基地址。
ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
EDX 则总是被用来放整数除法产生的余数。
ESI/EDI分别叫做"源/目标索引寄存器"(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串。
EBP是"基址指针"(BASE POINTER), 它最经常被用作高级语言函数调用的"框架指针"(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码:
  
  push ebp ;保存当前ebp
  mov ebp,esp ;EBP设为当前堆栈指针
  sub esp, xxx ;预留xxx字节给函数临时变量.
  ...
  
这样一来,EBP 构成了该函数的一个框架, 在EBP上方分别是原来的EBP, 返回地址和参数. EBP下方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可。
ret指令用栈中的数据,修改IP的内容,从而实现近转移。
retf指令用栈的数据,修改CS和IP的内容,从而实现远转移。
ESP 专门用作堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。
int setvbuf(FILE stream, char buf, int type, unsigned size);
  参数:stream :指向流的指针 ;
  buf : 期望缓冲区的地址;
  type : 期望缓冲区的类型:
  _IOFBF(满缓冲):当缓冲区为空时,从流读入数据。或者当缓冲区满时,向流写入数 据。
  _IOLBF(行缓冲):每次从流中读入一行数据或向流中写入一行数据。
  _IONBF(无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。
  size : 缓冲区内字节的数量。
void malloc(unsigned int num_bytes)
malloc函数是一种分配长度为num_bytes字节的内存块的函数,可以向系统申请分配指定size个字节的内存空间。
动态内存分配,当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态的分配内存。
void
表示未确定类型的指针。C,C++规定,void* 类型可以通过类型转换强制转换为任何其它类型的指针。
exit(0):正常运行程序并退出程序;
exit(1):非正常运行导致退出程序;
return():返回函数,若在主函数中,则会退出函数并返回一值。

shellcode原理

每条汇编指令都对应着长短不一的一串16进制数,这些16进制数串叫做opcode。opcode是由最多6个域组成的,和汇编指令存在对应关系的机器码。或者说可以认为汇编指令是opcode的“别名”。
易于人类阅读的汇编语言指令,如xor ecx, ecx等,实际上就是被汇编器根据opcode与汇编指令的替换规则替换成16进制数串,再与其他数据经过组合处理,最后变成01字符串被CPU识别并执行的。
IDA之类的反汇编器也是使用替换规则将16进制串处理成汇编代码的。所以我们可以直接构造合法的16进制串组成的opcode串,即shellcode,使系统得以识别并执行,完成我们想要的功能。

系统调用

查阅intel开发者手册我们可以知道,int指令的功能是调用系统中断,所以int 80h就是调用128号中断。在32位的linux系统中,该中断被用于呼叫系统调用程序system_call()。
我们知道,出于对硬件和操作系统内核的保护,应用程序的代码一般在保护模式下运行。在这个模式下我们使用的程序和写的代码是没办法访问内核空间的。但是我们显然可以通过调用read(), write()之类的函数从键盘读取输入,把输出保存在硬盘里的文件中。那么read(), write()之类的函数是怎么突破保护模式的管制,成功访问到本该由内核管理的这些硬件呢?答案就在于int 80h这个中断调用。
不同的内核态操作通过给寄存器设置不同的值,再调用同样的 指令int 80h,就可以通知内核完成不同的功能。
read(), write(), system()之类的需要内核“帮忙”的函数,就是围绕这条指令加上一些额外参数处理,异常处理等代码封装而成的。
32位linux系统的内核一共提供了0~337号共计338种系统调用用以实现不同的功能。

shellcode的变形

在很多情况下,我们多试几个shellcode,总能找到符合能用的。但是在有些情况下,为了成功将shellcode写入被攻击的程序的内存空间中,我们需要对原有的shellcode进行修改变形以避免shellcode中混杂有\x00, \x0A等特殊字符,或是绕过其他限制。有时候甚至需要自己写一段shellcode。(后续Day3)