自己在准备安全岗位面试时的一些安全基础知识
C++逆向
- [x]
XNUCA题目
- [x]
https://www.anquanke.com/post/id/87194
Heapstorm2
tukan.farm
how2heap
python
可/不可变参数
在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。
python 函数的参数传递(注意函数的默认参数,也是被视为传入的参数,是能被函数修改的!):
- 不可变类型:类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
- 可变类型:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响
匿名函数
lambda [arg1 [,arg2,.....argn]]:expression
sum = lambda a1, a2: a1+a2 # 定义函数
print(sum(1,2))
import
一个模块只会被导入一次,不管你执行了多少次import。这样可以防止导入模块被一遍又一遍地执行。
python的包
包是一个分层次的文件目录结构,它定义了一个由模块及子包,和子包下的子包等组成的 Python 的应用环境。
简单来说,包就是文件夹,但该文件夹下必须存在 init.py 文件, 该文件的内容可以为空。__init__.py 用于标识当前文件夹是一个包。
input和raw_input
input([prompt]) 函数和 raw_input([prompt]) 函数基本类似,但是 input 可以接收一个Python表达式作为输入,并将运算结果返回。
迭代
如果给定一个list或tuple或dict或其他数据结构,我们可以通过for
循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
列表生成式
[expr(i) for i in <iter>]
生成器和函数
区别在于函数中的return ret换成yield ret
生成器第一次被调用时从定义开头运行,其后每次被调用时从yield的下一句开始运行
高阶函数
传入函数作为参数的函数,python自带map、filter和sorted等高阶函数
python类中的私有变量
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318650247930b1b21d7d3c64fe38c4b5a80d4469ad7000
python的动态继承
静态语言 vs 动态语言
对于静态语言(例如Java)来说,如果需要传入Animal
类型,则传入的对象必须是Animal
类型或者它的子类,否则,将无法调用run()
方法。
对于Python这样的动态语言来说,则不一定需要传入Animal
类型。我们只需要保证传入的对象有一个run()
方法就可以了:
python序列化
使用pickle序列化
>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'
# 反之使用loads来从一个bytes对象中恢复出原来的对象
使用json序列化与pickle一致
多进程/线程
多进程(无线程池)
from multiprocessing import Process
import os
# 子进程要执行的代码
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',))
print('Child process will start.')
p.start()
p.join()
print('Child process end.')
多进程(有线程池)
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')
子进程(外部进程)
import subprocess
print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)
多线程
import time, threading
# 假定这是你的银行存款:
balance = 0
lock = threading.Lock()
def change_it(n):
# 先存后取,结果应该为0:
global balance
balance = balance + n
balance = balance - n
def run_thread(n):
for i in range(100000):
lock.acquire()
try:
change_it(n)
finally:
lock.release()
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)
可以使用Queue、Pipes来进行进程间通信
正则表达式
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143193331387014ccd1040c814dee8b2164bb4f064cff000
一些简单的沙盒逃逸
http://www.lsowl.xyz/2018/06/11/python%E6%B2%99%E7%9B%92%E9%80%83%E9%80%B8/#more
装饰器
import functools
def log(func):
@functools.wraps(func) # 用来修复返回的wrapper的__name__为原来函数now的__name__
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log
def now():
print('2015-3-25')
# 不带参数的装饰器,即相当于执行now=log(now)
import functools
def log(text):
def decorator(func):
@functools.wraps(func) # 用来修复返回的wrapper的__name__为原来函数now的__name__
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute')
def now():
print('2015-3-25')
# 自带1个传入参数的装饰器,相当于执行了now = log('execute')(now)
密码学、计算机组成、编译原理、操作系统、算法
C中的volatile
主要是用来在并行计算时,强制要求编译器不优化与定义为volatile的变量相关的语句(避免由于优化导致失去了由于并行带来的影响)
安卓APP逆向
jadx-gui
jdk
解包
jbe修改字节码,需要对java字节码较熟悉
javaassist修改字节码,使用IDEA编写代码
jar命令重打包
SROP、BROP
ROP中的tip:这里需要说明执行strcmp的时候,rdx会被设置为将要被比较的字符串的长度,所以我们可以找到strcmp函数,从而来控制rdx。
在Ubuntu 16.04上测试时好像没有在传参时改变rdx的值,但是发现rdx会等于rsi指向的字符串中第一个不同的字符的值(或0表示二者一致),也就是在已知/可控rdi的字符串而且可控rsi的字符串时,我们可以通过strcmp返回来改变rdx的范围由0~0xff
SROP:自己写一个signal注册好,在程序执行到signalhandler的时候gdb attach上去看之后的程序执行可以发现程序首先handler返回,返回到了libc的__restore_rt函数中,这个函数将rax设置为15后调用syscall,即发起了一次sys_rt_sigreturn,而往后的栈上则是返回用的寄存器的值及其他变量了
BROP:需要满足的条件如下:
- 源程序必须存在栈溢出漏洞,以便于攻击者可以控制程序流程。
- 服务器端的进程在崩溃之后会重新启动,并且重新启动的进程的地址与先前的地址一样(这也就是说即使程序有ASLR保护,但是其只是在程序最初启动的时候有效果)。目前nginx, MySQL, Apache, OpenSSH等服务器应用都是符合这种特性的。
主要要找三类gadget:
- Stop gadget:可以让攻击者判断程序仍然在正常运行而没有崩掉的程序跳转地址
- _init_csu最后的连续pop gadget:这里不光是pop了该地址上写着的这些寄存器,而且可以通过适当错位得到pop rdi和pop rsi的gadget,测试即是后面加6个p64(0)后再加一个p64(stop_gadget)看是否能正常返回
- 找到puts_plt的地址(puts只会被\x00截断)
- 打印程序段
fake linkmap
checksec
nx、aslr、pie
Fortify:将printf变成printf_chk、strcpy变成strcpy_chk
RelRO:GOT只读(partial为可写)
angr简单运用及相关理解
dl-runtime-resolve利用(How the ELF ruin the Christmas)
64位的情况:
首先伪造Elf64_Rela (24 bytes):
buf2 += p64(r_offset) # Elf64_Rela
buf2 += p64(r_info)
buf2 += p64(r_addend)
# 其中r_offset设为addr_xxx_got均可
# r_info=(((addr_sym - addr_dynsym) / 0x18) << 0x20) | 0x7
# r_addend=0
然后伪造Elf64_Sym (24 bytes):
buf2 += p32(st_name) # Elf64_Sym
buf2 += p32(0x00000012)
buf2 += p64(0)
buf2 += p64(0)
# 其中 st_name=addr_symstr - addr_dynstr
然后在addr_symstr处写上需要调用的函数名即可。
但是在64位下,dl_runtime_resolve会在_dl_fixup
处挂掉,原因是r_info的值过大导致SIGSEGV
回到glibc的源码继续看,有:
const struct r_found_version *version = NULL;
if (l->l_info[VERSYMIDX(DT_VERSYM)] != NULL) // [r10+0x1c8] != 0
{
const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
version = &l->l_versions[ndx];
if (version->hash == 0)
version = NULL;
}
因此需要获得got表第1项(第0项为ELF的Elf64_Dyn地址,第2项为_dl_runtime_resolve
自己的地址),即link_map的地址并将link_map+0x1c8处写为0,就可以顺利通过检查并执行定义函数名在addr_symstr处的函数了
https://www.usenix.org/conference/usenixsecurity15/technical-sessions/presentation/di-frederico
实际上涉及到了ELF文件中的几个段:
- 重定位表Relocation table:.rel.plt,三句汇编代码jmp;push;call;
TLS
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;//这里为canary
uintptr_t pointer_guard;//这里为pointer guard
unsigned long int vgetcpu_cache[2];
/* Bit 0: X86_FEATURE_1_IBT.
Bit 1: X86_FEATURE_1_SHSTK.
*/
unsigned int feature_1;
int __glibc_unused1;
/* Reservation of some values for the TM ABI. */
void *__private_tm[4];
/* GCC split stack support. */
void *__private_ss;
/* The lowest address of shadow stack, */
unsigned long long int ssp_base;
/* Must be kept even if it is no longer used by glibc since programs,
like AddressSanitizer, depend on the size of tcbhead_t. */
__128bits __glibc_unused2[8][4] __attribute__ ((aligned (32)));
void *__padding[8];
} tcbhead_t;
在单线程情况下,线程的tls会放在ld.so的r-xp段后;多线程情况下tls放在子线程栈的栈底(地址最高处)
https://chao-tic.github.io/blog/2018/12/25/tls