安全面试准备

 

自己在准备安全岗位面试时的一些安全基础知识

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:需要满足的条件如下:

  1. 源程序必须存在栈溢出漏洞,以便于攻击者可以控制程序流程。
  2. 服务器端的进程在崩溃之后会重新启动,并且重新启动的进程的地址与先前的地址一样(这也就是说即使程序有ASLR保护,但是其只是在程序最初启动的时候有效果)。目前nginx, MySQL, Apache, OpenSSH等服务器应用都是符合这种特性的。

主要要找三类gadget:

  1. Stop gadget:可以让攻击者判断程序仍然在正常运行而没有崩掉的程序跳转地址
  2. _init_csu最后的连续pop gadget:这里不光是pop了该地址上写着的这些寄存器,而且可以通过适当错位得到pop rdi和pop rsi的gadget,测试即是后面加6个p64(0)后再加一个p64(stop_gadget)看是否能正常返回
  3. 找到puts_plt的地址(puts只会被\x00截断)
  4. 打印程序段

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文件中的几个段:

  1. 重定位表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