目录
关键代码解读:
getxs()
getbuf()
test()
核心思路
具体操作1
具体操作2
前段时间忙于强网杯、英语4级和一些其他支线,有点摸不清头绪了,特别是qwb只有一个输出,太过坐牢,决定这个安全项目做完后就继续投身web的修炼,向C语言暂时说拜拜!!!
前置知识:
C语言函数调用栈
贴出源码:
/* bufbomb.c
*
* Bomb program that is solved using a buffer overflow attack
*
* program for CS:APP problem 3.38
*
* used for CS 202 HW 8 part 2
*
* compile using
* gcc -g -O2 -Os -o bufbomb bufbomb.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
/* Like gets, except that characters are typed as pairs of hex digits.
Nondigit characters are ignored. Stops when encounters newline */
char* getxs(char* dest)
{
int c;
int even = 1; /* Have read even number of digits */
int otherd = 0; /* Other hex digit of pair */
char* sp = dest;
while ((c = getchar()) != EOF && c != '\n') {
if (isxdigit(c)) {
int val;
if ('0' <= c && c <= '9')
val = c - '0';
else if ('A' <= c && c <= 'F')
val = c - 'A' + 10;
else
val = c - 'a' + 10;
if (even) {
otherd = val;
even = 0;
}
else {
*sp++ = otherd * 16 + val;
even = 1;
}
}
}
*sp++ = '\0';
return dest;
}
int getbuf()
{
char buf[16];
getxs(buf);
return 1;
}
void test()
{
int val;
printf("Type Hex string:");
val = getbuf();
printf("getbuf returned 0x%x\n", val);
}
int main()
{
int buf[16];
/* This little hack is an attempt to get the stack to be in a
stable position
*/
int offset = (((int)buf) & 0xFFF);
int* space = (int*)malloc(offset);
*space = 0; /* So that don't get complaint of unused variable */
test();
return 0;
}
关键代码解读:
getxs()
这段代码定义了一个函数 getxs
,它接受一个参数 dest
,类型为 char*
。函数的作用是从标准输入中读取一串十六进制数字,并将其转换为对应的字符序列,最后将结果存储在 dest
指向的内存空间中。
代码中使用了一个 while
循环来逐个读取输入的字符,直到遇到文件结束符(EOF)或换行符('\n')为止。在循环体内部,首先判断当前字符是否是一个十六进制数字,这由 isxdigit
函数来判断。
如果是十六进制数字,根据其值计算出对应的十进制数。如果当前已经读取了偶数个数字,则将该数字保存在 otherd
变量中,并将 even
标志位设置为 0,表示下一个数字是一个新的十六进制数字的开始。如果当前已经读取了奇数个数字,则将 otherd
乘以 16 并加上当前数字的值,将结果存储在 sp
指向的内存空间中,sp指向地址增加一个字节,并将 even
标志位设置为 1。
循环结束后,将字符串的终止符 '\0' 存储在sp 指向的内存空间中,并返回指向 dest
的指针作为函数的返回值。
总而言之,这段代码实现了将输入的十六进制数字转换为字符序列的功能。
比如想输入deadbeef,则要输入efbeadde(小端存储,不解释)
getbuf()
先是定义一个16个字节长度的字符型数组,讲数组首地址传入getxs函数,最后返回一个1。
这里就存在一些栈溢出的手段,暂按不表。
test()
定义了一个整形变量val,打印"Type Hex string",将val赋值为getbuf()的返回值(这里为1),最后将val的16进制型打印出。如果我们放任不管的话,无论输入什么值,因为无法改变getbuf()的返回值,所以回显一定如下。
但实验要求是让我们输出getbuf returned 0xdeadbeef,我们该怎么入手呢?
核心思路
以我浅薄的C语言能力应该无法修改getbuf的返回值为deadbeef,所以想到可以利用栈溢出的方式跳过val的赋值语句,直接运行第二个printf函数,而val的值也通过溢出覆盖为期望的deadbeef。
而buf[16]和getxs函数的配合恰好可以满足我们搞破坏的需求。
一行输入的内容超过了16字节,getxs并不会停止读入,此时的读入已经超出了数组范围(数组访问越界),从而实现覆写改变高地址处内容。
具体修改什么内容呢?
主要有两个部分:getbuf()函数的返回地址(从而直接跳转到第二个printf,不执行赋值操作)和val的初始值
具体操作1
设个断点开始调试,进入test函数
看寄存器状态,得知EBP地址为0019FDB4
看一眼 EBP地址里存的内容为0019FEE8(原原EBP地址,即test函数栈帧的原EBP地址——main函数的EBP地址,这里其实不太严谨,但为了后续方便表述所以用了两个原)
再看内存中val的地址,为0019FDAC
(内容是cccccccc说明val变量还未初始化)
看汇编代码得知val赋值语句和第二个printf的地址分别为004119D7,004119DA
到此为止我们可以先用表格做个总结
具体操作2
跟进调试,进入getbuf函数,栈帧切换为getbuf
看一眼寄存器,发现新EBP地址为0019FCD4
在内存里查看新BEP地址里存的值,发现为原EBP地址(即test函数栈帧EBP地址)0019FDB4
看一下0019FDB8和0019FDBC里存的值,发现分别是val赋值语句的函数返回地址(004119D7)和原原EBP地址(0019FEE8)。
这里凡是相关EBP地址的内容我们都不能改,不然会报错,但是函数返回值可以任由我们操纵。
我们可以把0019FDB8的值由004119D7改为004119DA,这样就可以跳过赋值语句直接printf。
最后再看一眼buf数组的首地址为0019FCC0,因为是16个char,所以占四字节
我们可以用表格模拟当前栈的分布情况
最终payload:
12345678 12345678 12345678 12345678 12345678 B4FD1900 DA194100 E8FE1900 12345678 12345678 12345678 12345679 12345680 12345681 12345682 12345683 12345684 12345685 12345686 12345687 12345688 12345689 12345690 12345691 12345692 12345693 12345694 12345695 12345696 12345697 12345698 12345699 12345700 12345701 12345702 12345703 12345704 12345705 12345706 12345707 12345708 12345709 12345710 12345711 12345712 12345713 12345714 12345715 12345716 12345717 12345718 12345719 12345720 12345721 12345722 12345723 12345724 12345725 12345726 efbeadde
没学过几天C语言的我都能做出,相信优秀的你们一定也没问题!!!!
再见,安全项目QWQ