Pwn学习

记录了学习pwn以来的一些容易忘的笔记

canary

canary绕过:

泄露canary: canary以\x00结尾,需要覆盖掉\x00,泄露出canary,然后再执行溢出控制运行过程。

劫持:__stack_chk_failed函数:

已知 Canary 失败的处理逻辑会进入到 __stack_chk_failed 函数,修改GOT表。

故意触发canary(SSP leak):

__stack_chk_failed函数中会输出argv[0],可构造足够长的字符串覆盖掉argv[0]为我们想要的地址。(详见附录)

附录:

https://veritas501.space/2017/04/28/%E8%AE%BAcanary%E7%9A%84%E5%87%A0%E7%A7%8D%E7%8E%A9%E6%B3%95/

格式化字符串

基本知识:

n$:获取格式化字符串中的指定参数

hh:一个字节 h:双字节

%p:十六进制地址格式打印变量的值

%n:不输出,把已成功输出的字符个数写入对应参数所指的变量

%%:输出一个%

泄露任意地址内存:

用:[tag]%p%p%p%p%p%p... 来测试需要输入的格式化字符串(恶意)是输出函数的第几个参数。

覆盖内存:

...[overwrite addr]....%[overwrite offset]$n

找到地址,然后确定该地址是第几个参数。

覆盖小数字:前面放padding,要覆盖的地址放在后面。

覆盖大数字:使用%hhn截断,按每个字节写入

Hijack GOT:

确定函数A的GOT表地址,确定函数B的内存地址,将函数B的内存地址写入到函数A的GOT表地址处。

Pwntools的fmtstr_payload函数,例如:fmtstr_payload(7, {puts_got: system_addr}) 的意思就是,我的格式化字符串的偏移是 7,我希望在 puts_got 地址处写入 system_addr 这个数据。


Hijack Retaddr:

直接修改为想要跳转的地址。

栈溢出

基本知识:

1、局部变量和全局变量都是先定义的在低地址,后定义的在高地址。

2、ELF编译时加了PIE选项后为so文件

3、plt表存got表项地址,got表存的是真正的函数地址(在未调用过一次函数前,got表存的是plt表下一条指令地址,调用过一次之后才是真正函数地址)。plt表在代码段,got表在数据段,plt表可以跳到got表地址。

4、readelf -r ./filename查看got表

5、改表内容一般GOT表,改返回地址改为PLT表地址或者GOT表地址都可以。

6、libc中找”/bin/sh”:libc.search(‘/bin/sh’).next()

7、enter和leave一组,call和ret一组。

call: push rip; jmp target;

ret: pop rip

enter: push rbp; mov rbp,rsp

leave: mov rsp,rbp; pop rbp

8、gets函数遇到回车才结束,回车被自动转换成’\0’存到字符串。read函数无0截断。

9、gdb默认关闭ASLR,要开启需要:set disable-randomization off


ret2text:

直接溢出改返回地址

ret2shellcode:

vmmap查看段执行权限,例如bss段。把shellcode放到bss段中,改返回地址跳转到shellcode。

shellcode = asm(shellcraft.sh())

注意64位要修改:context.bits=64 ; context.arch='amd64'

ret2syscall:

利用如下系统调用来获取shell。

execve("/bin/sh",NULL,NULL)

32位系统:eax为系统调用号0xb,ebx指向/bin/sh地址,ecx、edx为0。

用ROPgadget找gadget:

ROPgadget --binary filename --only ‘pop|ret’ |grep ‘ebx’

找/bin/sh地址:

ROPgadget --binary filename --string ‘/bin/sh’

找int 0x80:

ROPgadget --binary filename --only ‘int’

以上为32位,64位的不同之处除了使用的寄存器之外,64位并不使用int 0x80,而是使用syscall,两者机器码并不相同。

32位execve调用号是0xb,64位是0x3b,两者都是存在eax(rax)寄存器中

ret2libc:

  • 泄露 __libc_start_main 地址
  • 获取 libc 版本
  • 获取 system 地址与 /bin/sh 的地址
  • 再次执行源程序
  • 触发栈溢出执行 system(‘/bin/sh’)

Ret2libc3要注意开始时把esp进行了and操作,最低4位清0

ret2csu:

利用__libc_csu_init函数中的gadgets,可以控制rbx,rbp,r12,r13,r14,r15的数据,其中可以通过r13,r14,r15控制rdx,rsi,edi的数据,通过rbx和r12的配合控制跳转到想要的函数。注意是call qword ptr [r12+rbx*8],会先取地址内容再跳转,所以不能填plt表而应该是got表。

控制rbx+1=rbp,则不会跳转,进行顺序执行。但是要注意会一直执行到retn,所以要构造padding长度为0x38用来pop。

libc_csu_init的尾部可以通过偏移,还能控制rbp和rsi寄存器。

ret2dl_resolve:

参考:http://pwn4.fun/2016/11/09/Return-to-dl-resolve/;

http://rk700.github.io/2015/08/09/return-to-dl-resolve/(这个没看,有空看看)

Stage1:普通ROP

Stage2:控制返回地址为plt[0],然后执行push GOT1,jmp GOT[2],调用_dl_runtime_resolve(linkmap,reloc_arg),相当于人为跳过了PLT表项开始时那部分操作(就是调用函数到PLT时有三部分操作嘛,JMP、PUSH、JMP,然后这个的做法就是跳过了第一个JMP,手动PUSH偏移,然后第三个JMP就是跳到PLT[0])

Stage3:实际上Stage2中push的偏移,就是目标函数在.rel.plt节中的偏移。这个stage中,伪造了write在.rel.plt节中对应的表项。这个表项有两个值,第一个值对应的是函数的GOT表地址,第二个值r->info(不是很懂,但是它右移8位得到的叫索引,这个索引对应的是.dynsym中函数所在的索引,例如write的值是0x607,右移8位得到索引就是0x6,刚好对应它在.dynsym中索引是6。而它的7代表着它是R_386_JUMP_SLOT这个type)

Stage4:这个stage中,伪造了函数在.dynsym表中的项。在stage3中伪造了2个值,其中可以通过第二个值找到函数在.dynsym中的索引,再通过这个索引对应的表项A,可以找到函数在.dynstr表中的项B。表项A长度为0x10,低地址的4字节(st_name)存的是B和.dynstr表头的偏移。

Stage5:这个stage和stage4基本差不多,这个stage是在stage4的基础上伪造了.dynstr表项。借此改变st_name,引导到自己写的函数名中,本例是”write\x00”

Stage6:和stage5同理,实战。把字符串改为”system\x00”,即可解析成system函数。

附录:

roputils工具:https://github.com/inaz2/roputils

基本知识:

  1. 当调用free函数的时候__free_hook不为NULL会优先调用__free_hook函数劫持free函数。当GOT表不能修改时,可考虑修改__free_hook指向的内容为想要的函数(如system),要被释放的堆地址为参数,达到free(addr)=system(addr)的效果。修改__malloc_hook后,可考虑通过连续释放同一堆块(double free)进行触发,程序会报错,但会调用backtrace_and_map函数之后调用malloc函数。

  2. 堆基础学习资料:https://www.cnblogs.com/alisecurity/p/5486458.html,https://www.cnblogs.com/alisecurity/p/5520847.html

  3. chunk加入unsorted bin的情况:

①释放small chunk时,如果能进行合并,就将能合并的从small bin中移除,将新的chunk加入到unsorted bin中。(large bin同理)

②如果large bin最大chunk size大于用户请求的size,则拆分为两块,前者给用户,后者加入到unsorted bin。

  1. fast bin:LIFO ; small bin: FIFO fast bin使用单链表,其他循环双链表

  2. mmap得到的地址是跟libc有关的,可以在关闭ASLR的时候求偏移。然后再利用这个偏移,可以在开启ASLR的时候求得libc基址。

  3. 向后合并:把相邻的低地址的chunk合并

  4. 32位系统中,用户的请求在16bytes到64bytes会被分配到fastbin中;64位系统中,用户的请求在32bytes到128bytes会被分配到fastbin中

  5. 伪造fake chunk时,要注意伪造next chunk的prev_size,否则会报错。(House of einherjar里面有详细提到)

  6. 当申请的堆块大于当前的top chunk size且小于用mmap分配的阈值时,系统会将原来的top chunk 放到**unsorted bin**中,同时分配新的较大的top chunk出来。

  7. #define MALLOC_ALIGNMENT (2 * SIZE_SZ) #define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)

  8. tcache_entry中,next域直接指向user data,而不是像fastbin那样指向chunk开头

  9. calloc不会分配tcache中的堆

在博客上写

UAF:

堆被释放,但指针未被置null

Fastbin double free:

通过构造出A->B->A的fastbin,申请A,从而可以修改A的fd域,第二次再申请A的时候就可以修改想要的地址。要注意对size的检查,需要符合当前bin的size

House of spirit:

伪造块,free掉再申请,从而实现分配到的堆块是我们伪造的堆块。需要注意size符合要求,块地址对齐, ISMMAP 不能为1

Chunk overlap:

  1. free掉一个unsorted chunk A,这样它下一个chunk B(物理高地址相邻)的prev_size就是这个A的size
  2. 把A的size改小,然后分配至少两次(假设分配两次,把原来的A拆分得到C和D),由于改小了,因此分配时并不会影响到B的prev_size和size的prev_in_use位,当然这里要注意构造下fake chunk防止分配C时被check
  3. free C,free B(在free B的时候,因为prev_size没有改过,还是A的size,因此相当于把A和B合并起来了,得到E)
  4. E进入unsorted bin,但显然D也在E中,就有UAF,之后就常规利用了

Unsorted bin attack:

不满足fastsize的chunk被free后进入unsorted bin,当从unsorted bin中取出一个chunk p时,会把p->bk->fd置为一个很大的数(main_arena中的一个地址),因此如果能改bk就能把某个地址的值设为很大,一般可以改global_max_fast

Small bin attack(House of Lore):

利用条件:①可以控制在small bin中的chunk的bk指针 ②可以控制指定位置的fd、bk指针

Small bin的check检查了victim->bk->fd==victim,victim为待unlink的堆,把bk改为想要的位置fake chunk,同时那个位置的fd指针改为victim,即可绕过第一次check,分配到victim。然后fake chunk的bk要指向一个可控制的地方,让该处的fd为fake chunk,即可绕过第二次check,分配到fake chunk。

if (in_smallbin_range(nb)) {
        // 获取 small bin 的索引
        idx = smallbin_index(nb);
        // 获取对应 small bin 中的 chunk 指针
        bin = bin_at(av, idx);
        // 先执行 victim= last(bin),获取 small bin 的最后一个 chunk
        // 如果 victim = bin ,那说明该 bin 为空。
        // 如果不相等,那么会有两种情况
        if ((victim = last(bin)) != bin) {
            // 第一种情况,small bin 还没有初始化。
            if (victim == 0) /* initialization check */
                // 执行初始化,将 fast bins 中的 chunk 进行合并
                malloc_consolidate(av);
            // 第二种情况,small bin 中存在空闲的 chunk
            else {
                // 获取 small bin 中倒数第二个 chunk 。
                bck = victim->bk;
                // 检查 bck->fd 是不是 victim,防止伪造
                if (__glibc_unlikely(bck->fd != victim)) {
                    errstr = "malloc(): smallbin double linked list corrupted";
                    goto errout;
                }
                // 设置 victim 对应的 inuse 位
                set_inuse_bit_at_offset(victim, nb);
                // 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来
                bin->bk = bck;
                bck->fd = bin;
                // 如果不是 main_arena,设置对应的标志
                if (av != &main_arena) set_non_main_arena(victim);
                // 细致的检查
                check_malloced_chunk(av, victim, nb);
                // 将申请到的 chunk 转化为对应的 mem 状态
                void *p = chunk2mem(victim);
                // 如果设置了 perturb_type , 则将获取到的chunk初始化为 perturb_type ^ 0xff
                alloc_perturb(p, bytes);
                return p;
            }
        }
    }

Large bin attack:(还不太懂)

参考:

https://veritas501.space/2018/04/11/Largebin%20%E5%AD%A6%E4%B9%A0/

控制一个unsorted chunk和一个large chunk,unsorted chunk控制bk,large chunk控制bk和bk_nextsize。(下面说的是unsorted chunk>large chunk)

先把unsorted chunk的bk改为想控制的地址附近,要让size域符合你想控制的大小(也是接下来你申请的大小),而这个unsorted chunk不能完美符合你接下来申请的大小,使得unsorted chunk回到对应large bin。

接着因为这个unsorted chunk要回到large bin,就要发生从unsorted bin中unlink,此时因为下图会把控制的地方的fd域改为一个libc地址。

img

然后把这个unsorted chunk加入到large bin中,会触发下图插入到large bin的代码。会把控制的地方的bk_nextsize改为堆地址。

img

之后还有一些代码,如下图,又是任意地址写堆地址。这里的bck->fd要让它改到size域,单字节,使得它满足你想要的大小。

img

最后分配得到的就是你在unsorted chunk的bk域里伪造的那个堆。

House of force:

利用条件:①可以控制top chunk的size域 ②可以申请任意大小的堆

设fake chunk地址为A,当前top chunk地址为B。这时偏移为A-B。接下来要计算申请的堆大小req。根据glibc代码,需要满足:req+SIZE_SZ+MALLOC_ALIGN_MASK=偏移=A-B。

这时req=A-B-SIZE_SZ-MALLOC_ALIGN_MASK,然后malloc(req)。这时top chunk就指向了你想要的地址A。再申请就会返回地址为A的chunk。

House of Einherjar:

利用条件:①可以控制1个非fast chunk A的prev size域 ②可以让A的prev_in_use域为0

原理是利用**unlink**。把A的prev_in_use域置0,在free A的时候会触发后向合并,把A和A-prev size的fake chunk合并。其中的check和unlink时的check一样:

if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      
  malloc_printerr (check_action, "corrupted double-linked list", P, AV);  

需要把fake chunk的fd和bk改为满足以上条件。

PS:unlink中对size和prev_size有如下验证:

if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      
      malloc_printerr ("corrupted size vs. prev_size");

所以只需P+size,就是glibc认为的下一个chunk的prev_size域,等于P的size域即可。

House of Orange:

利用条件:当程序中没有free函数时使用(需要能改top chunk size?感觉不一定,但资料这么写)

当top chunk不能满足当前分配的需求时,会用brk拓展堆,之前的top chunk就会被放入unsorted bin中

注意事项:①伪造的top chunk size必须要对齐到内存页(即后3位) ②size要大于MINSIZE ③size要小于之后申请的chunk size+MINSIZE ④size的prev_in_use位为1

House of Roman:

利用条件:①无法leak ②存在UAF漏洞 ③可以生成fast chunk和unsorted chunk

先通过fast bin attack,使得fast chunk的fd域存有main_arena相关的地址,通过改低字节,申请到__malloc_hook-0x23作为fake chunk。然后通过unsorted bin attack,使得unsorted chunk的bk域存有main_arena相关地址,通过改低字节,把__malloc_hook的值改为一个和main_arena相关的地址。最后利用前面fast bin attack得到的fake chunk,改低字节,把main_arena相关改成one_gadget。最后double free触发one_gadget,选取rsp+0x50那个成功率较高。

Tcache Attack:

tcache poisoning:

和fast bin attack类似,改fd,但没有了size的限制

tcache dup:

2.27:double free,但没有任何检查,可以直接double free

2.29:对double free加入检查,如果发现在当前tcache中已有待free的堆,则报错;但如果同一个堆在两个tcache中存在不会报错

IO_FILE利用

基础知识:

  1. 程序对每个文件流生成一个FILE结构。对所有FILE结构用链表连接,链表头用_IO_list_all表示。标准IO库,程序启动时自动打开stdin、stdout、stderr三个文件流,这三个流位于libc.so的数据段。而用fopen创建的文件流分配在堆内存上。

  2. c struct _IO_FILE_plus { _IO_FILE file; IO_jump_t *vtable; }

32位下vtable偏移为0x94,64位偏移位0xd8

  1. ```c void * funcs[] = { 1 NULL, // "extra word" 2 NULL, // DUMMY 3 exit, // finish 4 NULL, // overflow 5 NULL, // underflow 6 NULL, // uflow 7 NULL, // pbackfail

    8 NULL, // xsputn #printf 9 NULL, // xsgetn 10 NULL, // seekoff 11 NULL, // seekpos 12 NULL, // setbuf 13 NULL, // sync 14 NULL, // doallocate 15 NULL, // read 16 NULL, // write 17 NULL, // seek 18 pwn, // close 19 NULL, // stat 20 NULL, // showmanyc 21 NULL, // imbue }; ```

  2. fread调用:IO_file_xsgetn

fwrite调用:IO_file_xsputn,IO_file_overflow,_IO_file_write

fopen调用

fclose调用:IO_file_finish

printf/puts调用:IO_file_xsputn,IO_file_overflow,_IO_file_write

  1. vtable 中的函数调用时会把对应的_IO_FILE_plus 指针作为第一个参数传递.

  2. 程序调用 exit 后,会遍历 _IO_list_all ,调用 _IO_2_1_stdout_ 下的 vatable_setbuf 函数。

  3. 当 glibc 检测到内存错误时,会依次调用这样的函数路径:malloc_printerr ->

libc_message->__GI_abort -> _IO_flush_all_lockp -> _IO_OVERFLOW

IO_FILE_to_leak

_IO_2_1_stdout_的flag为0xfbad1800,然后后面加p64(0)*3+'\x00',改小_IO_write_base,从而可以多输出一点信息,其中包括libc地址,可以找到_IO_file_jumps

内核Pwn

环境配置

环境配置参考:

http://blog.eonew.cn/archives/1162

https://23r3f.github.io/2019/05/15/kernel%20pwn%E5%88%9D%E6%8E%A2/

内核编译:

make bzImage

busybox编译:

 make install

得到_install文件夹,手动添加一些系统文件夹和init初始化脚本

如果需要加可执行文件(ELF),也是放在_install目录下

打包:

find . | cpio -o --format=newc > ./rootfs.img 

qemu启动:

把编译好的内核bzImage和busybox打包好的文件系统放到同一文件夹下,之后执行shell命令(为方便直接放到.sh文件中)

sh:

#!/bin/sh
qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd  ./rootfs.img \
-append "root=/dev/ram rw oops=panic panic=1 kalsr" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-monitor /dev/null \
-smp cores=2,threads=1 \
-cpu kvm64,+smep \
#-S 启动gdb调试
#-gdb tcp:1234 等待gdb调试

命令

dmesg:开机信息( /proc/sys/kernel/dmesg_restrict 为1则不能输出)

/proc/kallsyms :保存地址的文件,可以获取commit_creds和prepare_kernel_creds的地址( /proc/sys/kernel/kptr_restrict 为1则不能输出)

执行 commit_creds(prepare_kernel_cred(0)) 即可获得 root 权限

gdb调试glibc

sudo apt-get install glibc-source 

sudo apt-get install libc6-dbg 

sudo tar -xf /usr/src/glibc/glibc-2.23.tar.xz

即可

在gdb提示符下输入以下内容:

pwndbg> directory /usr/src/glibc/glibc-2.23/malloc/

pwndbg> b _int_malloc(要断的函数)

如果出现Function "_int_malloc" not defined.,别慌,把程序运行起来再断就好。

实用小知识:

  1. libc中存储了main函数environ指针的地址,通过该指针可以找到main函数的返回地址存的地方A(在栈上),即通过environ泄漏栈地址:

&environ是受libc_base影响的地址。A=environ-0x1e*8

  1. 关闭系统ASLR:echo 0 > /proc/sys/kernel/randomize_va_space

开启gdb PIE:set disable-randomization off

ASLR取值解释:

0:所有随机化关闭

1:除堆以外,所有随机化开启(包括程序代码、栈、动态库地址)。堆地址虽然也随程序代码而动态变化,但是堆是紧跟在程序部分后面的,随程序代码基址变化。

2:所有随机化开启,堆地址也随机,不能通过程序代码基址推出。

  1. 出题,开启防护的方法:https://www.cnblogs.com/Spider-spiders/p/8798628.html

移除.symtab 符号表 以及 .strtab 符号字符串表

  • strip --remove-section=.symtab file_in
  • strip --remove-section=.strtab file_in

  • 在main函数前会调用.init段代码和.init_array段的函数数组中每一个函数指针。同样的,main函数结束后也会调用.fini段代码和.fini._arrary段的函数数组中的每一个函数指针。

  • 给程序传参数,process(argv=[])

  • patchelf使用方法:

改ld.so时,patchelf --set-interpreter ld.so elfname;

改libc时,patchelf --set-rpath libc elfname