bpf,ebpf一些原理以及逆向基于libbpf-bootstrap编写的bpf文件

admin 2022年4月27日01:03:44程序逆向评论18 views12480字阅读41分36秒阅读模式

0x00 bpf

BPF 的全称是 Berkeley Packet Filter,是一个用于过滤(filter)网络报文(packet)的架构。(例如tcpdump),目前称为Cbpf(Classical bpf)。BPF 在数据包过滤上引入了两大革新:

  • 一个新的虚拟机 (VM) 设计,可以有效地工作在基于寄存器结构的 CPU 之上;
  • 应用程序使用缓存只复制与过滤数据包相关的数据,不会复制数据包的所有信息。这样可以最大程度地减少BPF 处理的数据;

由于这些巨大的改进,所有的 Unix 系统都选择采用 BPF 作为网络数据包过滤技术,直到今天,许多 Unix 内核的派生系统中(包括 Linux 内核)仍使用该实现。

0x01 ebpf

eBPF演进为一个通用执行引擎,可基于此开发性能分析工具、软件定义网络等诸多场景,原来的 BPF 就被称为经典 BPF,缩写cBPF(classic BPF),cBPF现在已经基本废弃。现在,Linux 内核只运行eBPF。

eBPF全称extended BPF,Linux Kernel 3.15 中引入的全新设计, 是对既有BPF架构进行了全面扩展,一方面,支持了更多领域的应用,比如:内核追踪(Kernel Tracing)、应用性能调优/监控、流控(Traffic Control)等;另一方面,在接口的设计以及易用性上,也有了较大的改进。

eBPF支持在用户态将C语言编写的一小段“内核代码”注入到内核中运行,注入时要先用llvm编译得到使用BPF指令集的 ELF 文件,然后从ELF文件中解析出可以注入内核的部分,最后用 bpf_load_program() 方法完成注入。 用户态程序和注入到内核中的程序通过共用一个位于内核的 eBPF MAP实现通信。为了防止注入的代码导致内核崩溃,eBPF 会对注入的代码进行严格检查,拒绝不合格的代码的注入。

  1. eBPF prog load的严格的verify机制
  2. eBPF访问内核资源需借助各种eBPF 的helper func,helper func函数能在最坏的情况下保证安全
  3. 现在,Linux 内核只运行 eBPF,内核会将加载的BPF字节码 透明地转换成 eBPF 再执行

image.png

eBPF程序执行过程

  • 编译:将eBPF程序转成BPF bytecode
  • 加载:特权进程通过pbf系统调用将BPF bytecode提交给内核(pbf系统在eBPF诞生后,成为了内核的一个顶级子系统)
  • 验证:在执行前进行安全性校验,如无限循环、不能导致内核崩溃、可完成等,保证eBPF程序操作的安全性
  • 内核态执行:通过kprobo、uprobe、perf_event等方式调用

*用户态程序与内核态程序交互*

BPF映射

image.png

ebpf的应用

  • 动态追踪:bcc、bpftrace
  • 观测监控:Pixie、Hubble、kubectl-trace
  • 网络:Cilium、Katran
  • 安全:Falco、Tracee

0x02 libbpf-bootstrap

libbpf-bootstrap就是这样一个 BPF 游乐场,它已经尽可能地为初学者配置好了环境,帮助他们可以直接步入到 BPF 程序的书写。它综合了 BPF 社区多年来的最佳实践,并且提供了一个现代化的、便捷的工作流。libbpf-bootstrap 依赖于 libbpf 并且使用了一个很简单的 Makefile。对于需要更高级设置的用户,它也是一个好的起点。即使这个 Makefile不会被直接使用到,也可以很轻易地迁移到别的构建系统上。

libbpf-bootstrap 为生成基于 libbpf 的 bpf 程序提供了模板,开发者可以很方便的使用该模板生成自定义的 bpf 程序。这里说的模板有两层含义:

  1. 源码中使用 libbpf 接口的使用模板;
  2. 源码文件命名的模板;

安装:

https://mp.weixin.qq.com/s/nJtvgtXuSGzGQHIEA-R9Pw

基于 libbpf-bootstrap 编写自己的bpf文件,并使用 uprobe 跟踪应用程序中的函数:

https://zhuanlan.zhihu.com/p/467647354

libbpf-bootstrap 为编写 bpf 程序提供了模板——源文件命名规则和接口的使用方法。这里以uprobe跟踪应用程序中的函数为例进行展开

一些函数定义:https://github.com/libbpf/libbpf/blob/master/src/libbpf.h

模板


源文件名称

libbpf-bootstrap 中的 Makefile 和 CMakeList.txt 规定了源文件名的规则:

  1. 生成bpf字节码的 bpf 文件以 .bpf.c 结尾;
  2. 加载bpf字节码的 c 文件以 .c 结尾;
  3. 上述两个类型文件名的前缀必须相同

鉴于 libbpf-bootstrap 中已经提供了 uprobe 的例子,我们这里以 selfuprobe 进行命名—— selfuprobe.bpf.c 和 selfuprobe.c


接口用法

bpftool会根据bpf字节码文件(xxx.bpf.o)生成对应的 skeleton 文件——xxx.skel.h。这个文件中包含了关键的函数和结构体。比如,这个文件中包含了xxx.bpf.o文件的内容。还有,xxx_bpf__open_and_load()函数,该函数用于加载bpf字节码到内核中。

具体用法请查看以下 selfuprobe的实现。


selfuprobe目标及代码

目标:跟踪 /home/syj/test 程序中的 foo(int a, intb) 函数,将该函数的执行时间显示到终端。

example/c/selfuprobe.bpf.c

#include <linux/bpf.h>
#include <linux/ptrace.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

char LICENSE[] SEC("license") = "GPL";   //bpf程序必须遵守GPL协议

unsigned long long func_entry_time = 0;
unsigned long long func_exit_time = 0;

SEC("uprobe/func-exec-time")
int BPF_KPROBE(uprobe)
{
    func_entry_time = bpf_ktime_get_ns();
    return 0;
}

SEC("uretprobe/func-exec-time")//SEC(uprobe/func-exec-time) 和 SEC(uretprobe/func-exec-time) 。其中的 uprobe/和 uretprobe/ 是关键,libbpf 根据这两个关键字确定在此段中定义的函数为 uprobe/uretprobe 相关;
int BPF_KRETPROBE(uretprobe)
{
    func_exit_time = bpf_ktime_get_ns();

    bpf_printk("function execute time:%lu\n", func_exit_time - func_entry_time);

    return 0;
}

BPF_KPROBE(uprobe) 和 BPF_KRETPROBE(uretprobe)分别定义了进入函数(uprobe)退出函数(uretprobe)时执行的动作。括号中的名字会生成 struct bpf_program
和 struct bpf_link类型的结构体成员名,在 selfuprobe.skel.h 文件中有体现

bpf,ebpf一些原理以及逆向基于libbpf-bootstrap编写的bpf文件
example/c/selfuprobe.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "selfuprobe.skel.h"

static int libbpf_output(enum libbpf_print_level level, const char *format, va_list args)
{
    return vfprintf(stderr, format, args);
}

static void bump_memlock_limit(void)
{
    int32_t ret = 0;
    struct rlimit rl = {
        .rlim_cur = RLIM_INFINITY,
        .rlim_max = RLIM_INFINITY,
    };

    ret = setrlimit(RLIMIT_MEMLOCK, &rl);
    if (0 != ret)
    {
        printf("failed to increase RLIMIT_MEMLOCK limit\n");
        exit(1);
    }
}

int main(int argc, char *argv[])
{
    int32_t ret = 0;
    struct selfuprobe_bpf *skel = NULL;
    long func_offset = 0x1149;

    libbpf_set_print(libbpf_output);
   //设置 libbpf 的日志输出函数,libbpf 执行过程中的日志会通过libbpf_output函数进行输出

    bump_memlock_limit();

    skel = selfuprobe_bpf__open_and_load();
   //用于加载 bpf 字节码文件(selfuprobe.bpf.o)到内核中
    if (NULL == skel)
    {
        printf("failed to open and load bpf skeleton\n");
        return -1;
    }

    skel->links.uprobe = bpf_program__attach_uprobe(skel->progs.uprobe, false, -1, "/home/sdc/test", func_offset);
    ret = libbpf_get_error(skel->links.uprobe);
    if (0 != ret)
    {
        printf("failed to attach uprobe:%d\n", ret);
        goto exit;
    }

    skel->links.uretprobe = bpf_program__attach_uprobe(skel->progs.uretprobe, true, -1, "/home/sdc/test", func_offset);
    //bpf_program__attach_uprobe() 用于绑定 uprobe 和 uretprobe 追踪目标的信息。
    //此例中,追踪的目标文件名称为 /home/sdc/test,偏移量为 0x1149
        ret = libbpf_get_error(skel->links.uretprobe);
    if (0 != ret)
    {
        printf("failed to attach uretprobe:%d\n", ret);
        goto exit;
    }

    printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
               "to see output of the BPF programs.\n");

    while (1)
    {
        printf("I am alive ...\n");
        sleep(1);
    }

    return 0;

exit:
    selfuprobe_bpf__destroy(skel);

    return -1;
}

selfuprobe.skel.h

这个文件中包含了与自定义名称相关的一些函数,用于在 xxx.c 文件中进行调用

image.png

0x03 逆向基于libbpf-bootstrap编写的bpf文件

fpbe

提取bpf字节码

image.png

fpbe_bpf *__cdecl fpbe_bpf__open()
{
  return fpbe_bpf__open_opts(0LL);
}
fpbe_bpf *__cdecl fpbe_bpf__open_opts(const bpf_object_open_opts *opts)
{
  fpbe_bpf *obj; // [rsp+18h] [rbp-8h]

  obj = (fpbe_bpf *)calloc(1LL, 32LL);
  if ( !obj )
    return 0LL;
  if ( !fpbe_bpf__create_skeleton(obj) && !bpf_object__open_skeleton(obj->skeleton, opts) )
    return obj;
  fpbe_bpf__destroy(obj);
  return 0LL;
}
int __cdecl fpbe_bpf__create_skeleton(fpbe_bpf *obj)
{
  bpf_object_skeleton *s; // [rsp+18h] [rbp-8h]

  s = (bpf_object_skeleton *)calloc(1LL, 72LL);
  if ( !s )
    return -1;
  obj->skeleton = s;
  s->sz = 72LL;
  s->name = "fpbe_bpf";
  s->obj = &obj->obj;
  s->prog_cnt = 1;
  s->prog_skel_sz = 24;
  s->progs = (bpf_prog_skeleton *)calloc(s->prog_cnt, s->prog_skel_sz);
  if ( s->progs )
  {
    s->progs->name = "uprobe";
    s->progs->prog = &obj->progs.uprobe;
    s->progs->link = &obj->links.uprobe;
    s->data_sz = 1648LL;
    s->data = &bytecode;
    return 0;
  }
  else
  {
    bpf_object__destroy_skeleton(s);
    return -1;
  }
}

image.png

image.png

反汇编bpf字节码

方法一:llvm-objdump

使用llvm-objdump -d xxx.bpf.o来得到IR

bpf,ebpf一些原理以及逆向基于libbpf-bootstrap编写的bpf文件
方法二:IDA Plugins ebpf_processor

https://github.com/cylance/eBPF_processor

ebpf.py放置到procs目录下即可

导入没报错就可以了

image.png

image.png

uprobe_func:0000000000000008 uprobe:
uprobe_func:0000000000000008                 ldxdw          r2, [r1+0x68]
uprobe_func:0000000000000010                 lsh            r2, 0x20
uprobe_func:0000000000000018                 rsh            r2, 0x20
uprobe_func:0000000000000020                 ldxdw          r3, [r1+0x70]
uprobe_func:0000000000000028                 lsh            r3, 0x20
uprobe_func:0000000000000030                 rsh            r3, 0x20
uprobe_func:0000000000000038                 mov            r4, r3
uprobe_func:0000000000000040                 mul            r4, 0x6DC0
uprobe_func:0000000000000048                 mov            r5, r2
uprobe_func:0000000000000050                 mul            r5, 0xFB88
uprobe_func:0000000000000058                 add            r5, r4
uprobe_func:0000000000000060                 ldxdw          r4, [r1+0x60]
uprobe_func:0000000000000068                 lsh            r4, 0x20
uprobe_func:0000000000000070                 rsh            r4, 0x20
uprobe_func:0000000000000078                 mov            r0, r4
uprobe_func:0000000000000080                 mul            r0, 0x71FB
uprobe_func:0000000000000088                 add            r5, r0
uprobe_func:0000000000000090                 ldxdw          r1, [r1+0x58]
uprobe_func:0000000000000098                 mov            r0, 0
uprobe_func:00000000000000A0                 stxb           [r10-8], r0
uprobe_func:00000000000000A8                 stxdw          [r10-0x10], r0
uprobe_func:00000000000000B0                 stxdw          [r10-0x18], r0
uprobe_func:00000000000000B8                 lsh            r1, 0x20
uprobe_func:00000000000000C0                 rsh            r1, 0x20
uprobe_func:00000000000000C8                 mov            r0, r1
uprobe_func:00000000000000D0                 mul            r0, 0xCC8E
uprobe_func:00000000000000D8                 add            r5, r0
uprobe_func:00000000000000E0                 mov            r6, 1
uprobe_func:00000000000000E8                 lddw           r0, 0xBE18A1735995
uprobe_func:00000000000000F8                 jne            r5, r0, LBB0_5
uprobe_func:0000000000000100                 mov            r5, r3
uprobe_func:0000000000000108                 mul            r5, 0xF1BF
uprobe_func:0000000000000110                 mov            r0, r2
uprobe_func:0000000000000118                 mul            r0, 0x6AE5
uprobe_func:0000000000000120                 add            r0, r5
uprobe_func:0000000000000128                 mov            r5, r4
uprobe_func:0000000000000130                 mul            r5, 0xADD3
uprobe_func:0000000000000138                 add            r0, r5
uprobe_func:0000000000000140                 mov            r5, r1
uprobe_func:0000000000000148                 mul            r5, 0x9284
uprobe_func:0000000000000150                 add            r0, r5
uprobe_func:0000000000000158                 lddw           r5, 0xA556E5540340
uprobe_func:0000000000000168                 jne            r0, r5, LBB0_5
uprobe_func:0000000000000170                 mov            r5, r3
uprobe_func:0000000000000178                 mul            r5, 0xDD85
uprobe_func:0000000000000180                 mov            r0, r2
uprobe_func:0000000000000188                 mul            r0, 0x8028
uprobe_func:0000000000000190                 add            r0, r5
uprobe_func:0000000000000198                 mov            r5, r4
uprobe_func:00000000000001A0                 mul            r5, 0x652D
uprobe_func:00000000000001A8                 add            r0, r5
uprobe_func:00000000000001B0                 mov            r5, r1
uprobe_func:00000000000001B8                 mul            r5, 0xE712
uprobe_func:00000000000001C0                 add            r0, r5
uprobe_func:00000000000001C8                 lddw           r5, 0xA6F374484DA3
uprobe_func:00000000000001D8                 jne            r0, r5, LBB0_5
uprobe_func:00000000000001E0                 mov            r5, r3
uprobe_func:00000000000001E8                 mul            r5, 0x822C
uprobe_func:00000000000001F0                 mov            r0, r2
uprobe_func:00000000000001F8                 mul            r0, 0xCA43
uprobe_func:0000000000000200                 add            r0, r5
uprobe_func:0000000000000208                 mov            r5, r4
uprobe_func:0000000000000210                 mul            r5, 0x7C8E
uprobe_func:0000000000000218                 add            r0, r5
uprobe_func:0000000000000220                 mov            r5, r1
uprobe_func:0000000000000228                 mul            r5, 0xF23A
uprobe_func:0000000000000230                 add            r0, r5
uprobe_func:0000000000000238                 lddw           r5, 0xB99C485A7277
uprobe_func:0000000000000248                 jne            r0, r5, LBB0_5
uprobe_func:0000000000000250                 stxw           [r10-0xC], r1
uprobe_func:0000000000000258                 stxw           [r10-0x10], r4
uprobe_func:0000000000000260                 stxw           [r10-0x14], r2
uprobe_func:0000000000000268                 stxw           [r10-0x18], r3
uprobe_func:0000000000000270                 lddw           r1, 0xA7D73257B465443
uprobe_func:0000000000000280                 stxdw          [r10-0x28], r1
uprobe_func:0000000000000288                 lddw           r1, 0x4648203A47414C46
uprobe_func:0000000000000298                 stxdw          [r10-0x30], r1
uprobe_func:00000000000002A0                 lddw           r1, 0x2052554F59202145
uprobe_func:00000000000002B0                 stxdw          [r10-0x38], r1
uprobe_func:00000000000002B8                 lddw           r1, 0x4E4F44204C4C4557
uprobe_func:00000000000002C8                 stxdw          [r10-0x40], r1
uprobe_func:00000000000002D0                 mov            r6, 0
uprobe_func:00000000000002D8                 stxb           [r10-0x20], r6
uprobe_func:00000000000002E0                 mov            r1, r10
uprobe_func:00000000000002E8                 add            r1, -0x40
uprobe_func:00000000000002F0                 mov            r3, r10
uprobe_func:00000000000002F8                 add            r3, -0x18
uprobe_func:0000000000000300                 mov            r2, 0x21
uprobe_func:0000000000000308                 call           6
uprobe_func:0000000000000310
uprobe_func:0000000000000310 LBB0_5:                                 ; CODE XREF: uprobe+F0j
uprobe_func:0000000000000310                                         ; uprobe+160j ...
uprobe_func:0000000000000310                 mov            r0, r6
uprobe_func:0000000000000318                 ret
uprobe_func:0000000000000318 ; End of function uprobe
uprobe_func:0000000000000318
uprobe_func:0000000000000318 ; end of 'uprobe_func'
uprobe_func:0000000000000318

方法三:bpftool dump出ir和流程图

Usage: ./bpftool [OPTIONS] OBJECT { COMMAND | help }
       ./bpftool batch file FILE
       ./bpftool version

       OBJECT := { prog | map | link | cgroup | perf | net | feature | btf | gen | struct_ops | iter }
       OPTIONS := { {-j|--json} [{-p|--pretty}] | {-f|--bpffs} |
                {-m|--mapcompat} | {-n|--nomount} }

libbpf-bootstrap/tools/bpftool

bpftool命令使用:https://blog.csdn.net/Longyu_wlz/article/details/109931993

首先运行并调试程序,将断点下在fpbe_bpf__open_and_load将字节码载入内核之后

然后执行./bpftool prog show

image.png

知道id为175

sudo ./bpftool prog dump xlated id 175

得到IR

image.png

这种IR可以通过重编译之后利用IDA来进行反编译

ir = """  0: (79) r2 = *(u64 *)(r1 +104)
   1: (67) r2 <<= 32
   2: (77) r2 >>= 32
   3: (79) r3 = *(u64 *)(r1 +112)
   4: (67) r3 <<= 32
   5: (77) r3 >>= 32
   6: (bf) r4 = r3
   7: (27) r4 *= 28096
   8: (bf) r5 = r2
   9: (27) r5 *= 64392
  10: (0f) r5 += r4
  ...
"""
for line in ir.splitlines():
    addr_end = line.find(":")
    addr = line[2:addr_end]
    print('_' + addr, end=': ')
    ins_start = line.find(") ")
    if line.find("goto") != -1:
        arr = line.split(' ')
        print(arr[4] + " (" + arr[5] + arr[6] + arr[7] + '){' + 'goto _' + str(eval(str(addr) + '+1+' + arr[9][3:])) + ';}')
    else:
        print(line[ins_start + 2:] + ';')
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

typedef uint64_t u64;
typedef uint8_t u8;
typedef uint32_t u32;

int main()
{
    u64 flag[4] = {1,2,3,4};
    u64 r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10;
    _0: r2 = *(u64 *)flag[2];
    _1: r2 <<= 32;
    _2: r2 >>= 32;
    _3: r3 = *(u64 *)flag[3];
    _4: r3 <<= 32;
    _5: r3 >>= 32;
    _6: r4 = r3;
    _7: r4 *= 28096;
    _8: r5 = r2;
    _9: r5 *= 64392;
    _10: r5 += r4;
    _11: r4 = *(u64 *)flag[1];
    _12: r4 <<= 32;
    _13: r4 >>= 32;
    _14: r0 = r4;
    _15: r0 *= 29179;
    _16: r5 += r0;
    _17: r1 = *(u64 *)flag[0];
    _18: r0 = 0;
    _19: *(u8 *)(r10 -8) = r0;
    _20: *(u64 *)(r10 -16) = r0;
    _21: *(u64 *)(r10 -24) = r0;
    _22: r1 <<= 32;
    _23: r1 >>= 32;
    _24: r0 = r1;
    _25: r0 *= 52366;
    _26: r5 += r0;
    _27: r6 = 1;
    _28: r0 = 0xbe18a1735995;
    _30: if (r5 != r0){goto _97;}
    _31: r5 = r3;
    _32: r5 *= 61887;
    _33: r0 = r2;
    _34: r0 *= 27365;
    _35: r0 += r5;
    _36: r5 = r4;
    _37: r5 *= 44499;
    _38: r0 += r5;
    _39: r5 = r1;
    _40: r5 *= 37508;
    _41: r0 += r5;
    _42: r5 = 0xa556e5540340;
    _44: if (r0!=r5){goto _97;}
    _45: r5 = r3;
    _46: r5 *= 56709;
    _47: r0 = r2;
    _48: r0 *= 32808;
    _49: r0 += r5;
    _50: r5 = r4;
    _51: r5 *= 25901;
    _52: r0 += r5;
    _53: r5 = r1;
    _54: r5 *= 59154;
    _55: r0 += r5;
    _56: r5 = 0xa6f374484da3;
    _58: if (r0!=r5){goto _97;}
    _59: r5 = r3;
    _60: r5 *= 33324;
    _61: r0 = r2;
    _62: r0 *= 51779;
    _63: r0 += r5;
    _64: r5 = r4;
    _65: r5 *= 31886;
    _66: r0 += r5;
    _67: r5 = r1;
    _68: r5 *= 62010;
    _69: r0 += r5;
    _70: r5 = 0xb99c485a7277;
    _72: if (r0!=r5){goto _97;}
    _73: *(u32 *)(r10 -12) = r1;
    _74: *(u32 *)(r10 -16) = r4;
    _75: *(u32 *)(r10 -20) = r2;
    _76: *(u32 *)(r10 -24) = r3;
    _77: r1 = 0xa7d73257b465443;
    _79: *(u64 *)(r10 -40) = r1;
    _80: r1 = 0x4648203a47414c46;
    _82: *(u64 *)(r10 -48) = r1;
    _83: r1 = 0x2052554f59202145;
    _85: *(u64 *)(r10 -56) = r1;
    _86: r1 = 0x4e4f44204c4c4557;
    _88: *(u64 *)(r10 -64) = r1;
    _89: r6 = 0;
    _90: *(u8 *)(r10 -32) = r6;
    _91: r1 = r10;
    _92: r1 += -64;
    _93: r3 = r10;
    _94: r3 += -24;
    _95: r2 = 33;
    _96: printf((char*)r1, r3);
    _97: r0 = r6;
    _98: exit(0);
    return 0;
}

image.png

得到流程图

sudo ./bpftool prog dump xlated id 175 visual &> output.out
dot -Tpng output.out -o visual-digraph.png

image.png

0x04 反编译bpf

ghidra9.2.0+ebpf-for-ghidra
https://github.com/Nalen98/eBPF-for-Ghidra
image.png

FROM:tttang . com

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月27日01:03:44
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  bpf,ebpf一些原理以及逆向基于libbpf-bootstrap编写的bpf文件 http://cn-sec.com/archives/944148.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: