signal SIGSEGV: address access protected in __run_exit_handlers

I’m trying to do a packer project for my school, it is made in C and ASM, and for the moment it is supposed to target only ELF files.
I have managed to do my section injection, correctly align my new section and update the segments and section offsets/addresses (see code below). If I inspect the executable everything looks correct.

When the packed program is launched, it executes the payload (simply write ....WOODY....), then the program correctly, and finally segfault when leaving the program.

I tried on several Linux distros, (on Unbutu no problem, on Debian and Kali it crashes).

$> lldb -- ./woody
(lldb) target create "./woody"
Current executable set to './woody' (x86_64).
(lldb) r
Process 3312 launched: '/home/maxence/Desktop/woody-woodpacker/woody' (x86_64)
....WOODY....
Hello, World!
Process 3312 stopped
* thread #1, name = 'woody', stop reason = signal SIGSEGV: address access protected (fault address: 0x7fffffffe2ea)
    frame #0: 0x00007fffffffe2ea
->  0x7fffffffe2ea: popq   %rax
(lldb) bt
* thread #1, name = 'woody', stop reason = signal SIGSEGV: address access protected (fault address: 0x7fffffffe2ea)
  * frame #0: 0x00007fffffffe2ea
    frame #1: 0x00007ffff7c45495 libc.so.6`__run_exit_handlers(status=0, listp=0x00007ffff7e1a838, run_list_atexit=true, run_dtors=true) at exit.c:113:8
    frame #2: 0x00007ffff7c45610 libc.so.6`__GI_exit(status=<unavailable>) at exit.c:143:3
    frame #3: 0x00007ffff7c29d97 libc.so.6`__libc_start_call_main(main=(woody`main), argc=-7394, argv=0x00007fffffffdf18) at libc_start_call_main.h:74:3
    frame #4: 0x00007ffff7c29e40 libc.so.6`__libc_start_main_impl(main=(woody`main), argc=-7394, argv=0x00007fffffffdf18, init=0x00007ffff7ffd040, fini=<unavailable>, rtld_fini=<unavailable>, stack_end=0x00007fffffffdf08) at libc-start.c:392:3
    frame #5: 0x0000555555555085 woody`_start + 37

Debian:

On debian only the payload executes

$> lldb -- ./woody
(lldb) target create "./woody"
Current executable set to './woody' (x86_64).
(lldb) r
Process 4374 launched: '/home/bitnami/woody-woodpacker/woody' (x86_64)
....WOODY....
Process 4374 stopped
* thread #1, name = 'woody', stop reason = signal SIGSEGV: invalid address (fault address: 0x49)
    frame #0: 0x0000555555555054 woody`_start + 4
woody`_start:
->  0x555555555054 <+4>:  rcrl   0x48(%rsi)
    0x555555555057 <+7>:  movl   %esp, %edx
    0x555555555059 <+9>:  andq   $-0x10, %rsp
    0x55555555505d <+13>: pushq  %rax
(lldb) 

Here is a breakdown of my process:

  1. search for the last program header with a PT_LOAD type
  2. search for the last section of the previously found segment
  3. create a new section, define its attributes (type, flags, alignment, …), calculate its offset and address, copy the payload.
  4. update the size of the segment containing our new section
  5. update the offsets of the sections behind our new section
  6. finally update the entry point and write the offset to the old one in the payload (replacing the temporary jump addr)

Once done I move on to the writing process, making sure to add the necessary padding between the sections to maintain the alignments.

I specify that the payload is in assembly and that for the moment it only writes a message (the final payload is supposed to decrypt .text section before jumping to it).

I checked the alignments of the sections, the addresses, the offsets, the sizes of the sections and segments, the paddings, I tested several payloads down to the smallest possible with a simple jump or more complex, inspected instruction by instruction, inspected the binary byte by byte, nothing worked, I couldn’t find anything. Honestly I’m out of ideas and I need some fresh air.

Here is my code for the section insertion:

typedef struct s_elf_file {
    union u_elf_e_ident
    {
        struct {
            uint32_t    ei_magic;
            uint8_t     ei_class;
            uint8_t     ei_data;
            uint8_t     ei_version;
            uint8_t     ei_osabi;
            uint8_t     ei_abi_version;
            char        ei_pad[7];
        };

        uint8_t     raw[16];
    } e_ident;

    uint16_t    e_type;
    uint16_t    e_machine;
    uint32_t    e_version;
    uint64_t    e_entry;
    uint64_t    e_phoff;
    uint64_t    e_shoff;
    uint32_t    e_flags;
    uint16_t    e_ehsize;
    uint16_t    e_phentsize;
    uint16_t    e_phnum;
    uint16_t    e_shentsize;
    uint16_t    e_shnum;
    uint16_t    e_shstrndx;
    char        *e_type_name;
    t_elf_program_header    *program_headers;
    t_elf_section_table     *section_tables;
}   t_elf_file;

typedef struct s_elf_section_table {
    uint32_t    sh_name_offset;
    uint32_t    sh_type;
    uint64_t    sh_flags;
    uint64_t    sh_address;
    uint64_t    sh_offset;
    uint64_t    sh_size;
    uint32_t    sh_link;
    uint32_t    sh_info;
    uint64_t    sh_addralign;
    uint64_t    sh_entsize;
    uint8_t     *data;
    char        *sh_name;
}   t_elf_section_table;

typedef struct s_elf_program_header
{
    uint32_t    p_type;
    uint32_t    p_flags;
    uint64_t    p_offset;
    uint64_t    p_vaddr;
    uint64_t    p_paddr;
    uint64_t    p_filesz;
    uint64_t    p_memsz;
    uint64_t    p_align;
}   t_elf_program_header;
size_t calculate_padding(size_t size, size_t alignment) {
    size_t padding = (alignment - (size % alignment)) % alignment;
    return (padding);
}

size_t calculate_padded_size(size_t size, size_t alignment) {
    size_t padding = (alignment - (size % alignment)) % alignment;
    return (size + padding);
}

int     efl_find_last_prog_header(t_elf_file *elf)
{
    int index = 0;

    for (int i = 0; i < elf->e_phnum; i++)
    {
        if (elf->program_headers[i].p_type == PT_LOAD)
        {
            index = i;
        }
    }
    return (index);
}

int     efl_find_last_section_header(t_elf_file *elf, int progindex)
{
    int index = 0;

    t_elf_program_header *prog = (t_elf_program_header *)elf->program_headers + progindex;
    for (int j = 1; j < elf->e_shnum; j++)
    {
        if (elf->section_tables[j].sh_offset >= prog->p_offset &&
            elf->section_tables[j].sh_offset + elf->section_tables[j].sh_size <= prog->p_offset + prog->p_filesz)
        {
            index = j;
        }
    }
    return (index);
}

uint8_t *prepare_payload(t_elf_section_table *new_section_headers, t_packer *packer)
{
    uint8_t *payload = (uint8_t *)malloc(packer->payload_64_size);
    if (!payload)
        return (NULL);

    ft_memcpy(payload, packer->payload_64, packer->payload_64_size);
    ft_memcpy(payload + packer->payload_64_size - WD_PAYLOAD_OFF_KEY, key_aes, WD_AES_KEY_SIZE);
    return (payload);
}

int set_new_elf_section_string_table(t_elf_file *elf, t_elf_section_table *new_section)
{
    char *section_name = WB_SECTION_NAME;
    size_t section_name_len = ft_strlen(section_name);

    int section_string_table_index = elf->e_shstrndx;
    int old_size = elf->section_tables[section_string_table_index].sh_size;

    size_t new_string_table_size = elf->section_tables[section_string_table_index].sh_size + section_name_len + 1;
    elf->section_tables[section_string_table_index].data = ft_realloc(elf->section_tables[section_string_table_index].data, new_string_table_size);
    if (elf->section_tables[section_string_table_index].data == NULL) {
        return (-1);
    }

    ft_memcpy(elf->section_tables[section_string_table_index].data + old_size, section_name, section_name_len + 1);
    new_section->sh_name_offset = old_size;
    new_section->sh_name = WB_SECTION_NAME;
    elf->section_tables[section_string_table_index].sh_size = new_string_table_size;
    return (0);
}

int create_new_elf_section(t_elf_file *elf, t_packer *packer, int last_load_prog, int last_section_in_prog)
{
    t_elf_section_table *new_section_headers;
    elf->e_shnum += 1;

    size_t new_section_headers_size = sizeof(t_elf_section_table) * elf->e_shnum;
    elf->section_tables = ft_realloc(elf->section_tables, new_section_headers_size);
    if (!elf->section_tables)
        return (-1);

    t_elf_section_table *new_section = ft_calloc(1, sizeof(t_elf_section_table));
    if (!new_section)
        return (-1);

    new_section->sh_type = SHT_PROGBITS;
    new_section->sh_flags = SHF_EXECINSTR | SHF_ALLOC;
    new_section->sh_addralign = 16;

    uint64_t current_offset_padding = calculate_padding(elf->section_tables[last_section_in_prog].sh_offset + elf->section_tables[last_section_in_prog].sh_size, elf->section_tables[last_section_in_prog].sh_addralign);
    uint64_t current_offset = elf->section_tables[last_section_in_prog].sh_offset + elf->section_tables[last_section_in_prog].sh_size + current_offset_padding;
    uint64_t current_vaddr_padding = calculate_padding(elf->section_tables[last_section_in_prog].sh_address + elf->section_tables[last_section_in_prog].sh_size, elf->section_tables[last_section_in_prog].sh_addralign);
    uint64_t current_vaddr = elf->section_tables[last_section_in_prog].sh_address + elf->section_tables[last_section_in_prog].sh_size + current_vaddr_padding;
    
    new_section->sh_offset = current_offset;
    new_section->sh_address = current_vaddr;
    packer->new_section_size = current_offset_padding;

    packer->loader_offset = new_section->sh_address;

    if ((new_section->data = prepare_payload(new_section, packer)) == NULL)
    {
        free(new_section);
        return (-1);
    }

    new_section->sh_size = calculate_padded_size(packer->payload_64_size, new_section->sh_addralign);
    
    packer->new_section_size += new_section->sh_size;

    size_t remaining_after_section_headers_data_size = sizeof(t_elf_section_table) * (elf->e_shnum - last_section_in_prog - 1 - 1);
    size_t remaining_after_section_headers_count = sizeof(char *) * (elf->e_shnum - last_section_in_prog - 1 - 1);

    memmove(elf->section_tables + last_section_in_prog + 2, elf->section_tables + last_section_in_prog + 1, remaining_after_section_headers_data_size);

    last_section_in_prog += 1;

    if (elf->e_shstrndx != 0)
    {
        if (elf->e_shstrndx >= last_section_in_prog) {
            elf->e_shstrndx += 1;
        }

        if (set_new_elf_section_string_table(elf, new_section) == -1)
        {
            free(new_section);
            return (-1);
        }
    }
    else
    {
        dprintf(2, "No section string table foundn");
    }

    ft_memcpy(&elf->section_tables[last_section_in_prog], new_section, sizeof(t_elf_section_table));

    for (int i = 0; i < elf->e_shnum; i++) {
        char *section_name = elf->section_tables[i].sh_name;
        if (strcmp(section_name, ".symtab") == 0) {
            elf->section_tables[i].sh_link += 1;
        }
    }

    free(new_section);
    return (0);
}

void    update_program_header(t_elf_file *elf, t_packer *packer, int last_loadable, int last_loadable_section)
{
    elf->program_headers[last_loadable].p_memsz += packer->new_section_size;
    elf->program_headers[last_loadable].p_filesz += packer->new_section_size;

    elf->program_headers[last_loadable].p_flags |= PF_X | PF_W | PF_R;
}

void    update_section_addr(t_elf_file *elf, t_packer *packer, int last_loadable)
{
    for (int i = last_loadable; i < elf->e_shnum - 1; i++) {
        if (elf->section_tables[i].sh_type == SHT_NOBITS) {
            elf->section_tables[i + 1].sh_offset = elf->section_tables[i].sh_offset;
            continue;
        }
        uint64_t offset_padding = calculate_padded_size(
            elf->section_tables[i].sh_offset + elf->section_tables[i].sh_size, 
            elf->section_tables[i + 1].sh_addralign
        ) - (elf->section_tables[i].sh_offset + elf->section_tables[i].sh_size);

        elf->section_tables[i + 1].sh_offset = elf->section_tables[i].sh_offset + elf->section_tables[i].sh_size + offset_padding;
        elf->section_tables[i + 1].sh_address = elf->section_tables[i].sh_address + elf->section_tables[i].sh_size + offset_padding;
    }

    int section_count = elf->e_shnum;
    elf->e_shoff = elf->section_tables[section_count - 1].sh_offset + elf->section_tables[section_count - 1].sh_size;
}

void    update_entry_point(t_elf_file *elf, t_packer *packer, int last_loadable)
{
    uint64_t last_entry_point = elf->e_entry;
    elf->e_entry = elf->section_tables[last_loadable].sh_address;

    uint64_t jmp_instruction_address = elf->e_entry + packer->payload_64_size - WD_PAYLOAD_RETURN_ADDR;
    uint64_t next_instruction_address = jmp_instruction_address;
    int32_t offset = (int32_t)(last_entry_point - next_instruction_address);

    ft_memcpy(elf->section_tables[last_loadable].data + packer->payload_64_size - WD_PAYLOAD_RETURN_ADDR, &offset, sizeof(offset));
}

int elf_insert_section(t_elf_file *elf)
{
    t_packer    packer;
    packer.payload_64_size = WB_PAYLOAD_SIZE;
    packer.payload_64 = (uint8_t *)wd_playload_64;
    
    int progi = efl_find_last_prog_header(elf);
    int sectioni = efl_find_last_section_header(elf, progi);
    if (create_new_elf_section(elf, &packer, progi, sectioni) == -1)
        return (-1);

    sectioni += 1;
    update_program_header(elf, &packer, progi, sectioni);
    update_section_addr(elf, &packer, sectioni);
    update_entry_point(elf, &packer, sectioni);
    return (0);
}

And here is my payload:

global _payload_64

[BITS 64]

segment .text align=16

_payload_64:
    push rax
    push rdx
    push rsi
    push rdi
    jmp .print_start_msg
.displayed_str:
    db "....WOODY....", 0x0a, 0
.print_start_msg:
    mov rax, 0x1
    mov rdi, 1
    lea rsi, [rel .displayed_str]
    mov rdx, 15
    syscall

    pop rdi
    pop rsi
    pop rdx
    pop rax
    jmp 0x01020304

info_start:
key: dq "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ; reserved space for the key

And finaly here is the writing process:

int write_to_file(int fd, void *data, size_t size) {
    ssize_t written = write(fd, data, size);
    if (written == -1) {
        return (-1);
    }
    offset += written;
    return (0);
}

void add_zero_padding(int fd, size_t end_offset) {
    char c = 0;

    while(offset < end_offset) {
        write_to_file(fd, &c, sizeof(c));
    }
}

int packer(t_elf_file *elf)
{

...

size_t elf_header_size = sizeof(t_elf_file) - sizeof(char *) - sizeof(t_elf_program_header *) - sizeof(t_elf_section_table *);
    size_t elf_section_header_size = sizeof(t_elf_section_table) - sizeof(char *) - sizeof(uint8_t *);

    write_to_file(fd, elf, elf_header_size);
    add_zero_padding(fd, elf->e_phoff);

    for (int i = 0; i < elf->e_phnum; i++) {
        write_to_file(fd, &elf->program_headers[i], sizeof(t_elf_program_header));
    }

    for (int i = 0; i < elf->e_shnum; i++) {
        if (elf->section_tables[i].sh_type != SHT_NOBITS)
        {
            add_zero_padding(fd, elf->section_tables[i].sh_offset);
            write_to_file(fd, elf->section_tables[i].data, elf->section_tables[i].sh_size);
        }
    }

    add_zero_padding(fd, elf->e_shoff);

    for (int i = 0; i < elf->e_shnum; i++) {
        write_to_file(fd, &elf->section_tables[i], elf_section_header_size);
    }

...

}

If it can help here is some info (my custom section is .i'm a teapot, it’s a private jock ;))

ELF Header:
  Magic:   7F 45 4C 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN
  Version:                           0x1
  Entry point:                       0x4010
  Start of program headers:          64 (bytes into file)
  Start of section headers:          14139 (bytes into file)
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         32
  Section header string table index: 31

Section Headers:
  [Nr] Name               Type               Address            Offset             Size               Align
  [ 0]                    NULL               000000000000000000 000000000000000000 000000000000000000 000000000000000000
  [ 1] .interp            PROGBITS           0x0000000000000318 0x0000000000000318 0x000000000000001c 0x0000000000000001
  [ 2] .note.gnu.property NOTE               0x0000000000000338 0x0000000000000338 0x0000000000000030 0x0000000000000008
  [ 3] .note.gnu.build-id NOTE               0x0000000000000368 0x0000000000000368 0x0000000000000024 0x0000000000000004
  [ 4] .note.ABI-tag      NOTE               0x000000000000038c 0x000000000000038c 0x0000000000000020 0x0000000000000004
  [ 5] .gnu.hash          0x6ffffff6         0x00000000000003b0 0x00000000000003b0 0x0000000000000024 0x0000000000000008
  [ 6] .dynsym            DYNSYM             0x00000000000003d8 0x00000000000003d8 0x00000000000000a8 0x0000000000000008
  [ 7] .dynstr            STRTAB             0x0000000000000480 0x0000000000000480 0x000000000000008d 0x0000000000000001
  [ 8] .gnu.version       0x6fffffff         0x000000000000050e 0x000000000000050e 0x000000000000000e 0x0000000000000002
  [ 9] .gnu.version_r     0x6ffffffe         0x0000000000000520 0x0000000000000520 0x0000000000000030 0x0000000000000008
  [10] .rela.dyn          RELA               0x0000000000000550 0x0000000000000550 0x00000000000000c0 0x0000000000000008
  [11] .rela.plt          RELA               0x0000000000000610 0x0000000000000610 0x0000000000000018 0x0000000000000008
  [12] .init              PROGBITS           0x0000000000001000 0x0000000000001000 0x000000000000001b 0x0000000000000004
  [13] .plt               PROGBITS           0x0000000000001020 0x0000000000001020 0x0000000000000020 0x0000000000000010
  [14] .plt.got           PROGBITS           0x0000000000001040 0x0000000000001040 0x0000000000000010 0x0000000000000010
  [15] .plt.sec           PROGBITS           0x0000000000001050 0x0000000000001050 0x0000000000000010 0x0000000000000010
  [16] .text              PROGBITS           0x0000000000001060 0x0000000000001060 0x0000000000000112 0x0000000000000010
  [17] .fini              PROGBITS           0x0000000000001174 0x0000000000001174 0x000000000000000d 0x0000000000000004
  [18] .rodata            PROGBITS           0x0000000000002000 0x0000000000002000 0x0000000000000012 0x0000000000000004
  [19] .eh_frame_hdr      PROGBITS           0x0000000000002014 0x0000000000002014 0x0000000000000034 0x0000000000000004
  [20] .eh_frame          PROGBITS           0x0000000000002048 0x0000000000002048 0x00000000000000ac 0x0000000000000008
  [21] .init_array        PREINIT_ARRAY      0x0000000000003db8 0x0000000000002db8 0x0000000000000008 0x0000000000000008
  [22] .fini_array        GROUP              0x0000000000003dc0 0x0000000000002dc0 0x0000000000000008 0x0000000000000008
  [23] .dynamic           DYNAMIC            0x0000000000003dc8 0x0000000000002dc8 0x00000000000001f0 0x0000000000000008
  [24] .got               PROGBITS           0x0000000000003fb8 0x0000000000002fb8 0x0000000000000048 0x0000000000000008
  [25] .data              PROGBITS           0x0000000000004000 0x0000000000003000 0x0000000000000010 0x0000000000000008
  [26] .i'm a teapot      PROGBITS           0x0000000000004010 0x0000000000003010 0x0000000000000090 0x0000000000000010
  [27] .bss               NOBITS             0x00000000000040a0 0x00000000000030a0 0x0000000000000008 0x0000000000000001
  [28] .comment           PROGBITS           000000000000000000 0x00000000000030a0 0x000000000000002b 0x0000000000000001
  [29] .symtab            SYMTAB             0x0000000000000030 0x00000000000030d0 0x0000000000000368 0x0000000000000008
  [30] .strtab            STRTAB             0x0000000000000398 0x0000000000003438 0x00000000000001db 0x0000000000000001
  [31] .shstrtab          STRTAB             0x0000000000000573 0x0000000000003613 0x0000000000000128 0x0000000000000001

Program Headers:
  Type           Offset             VirtAddr           PhysAddr           FileSiz            MemSiz             Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040 0x00000000000002d8 0x00000000000002d8 R      0x8
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318 0x000000000000001c 0x000000000000001c R      0x1
  LOAD           000000000000000000 000000000000000000 000000000000000000 0x0000000000000628 0x0000000000000628 R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000 0x0000000000000181 0x0000000000000181 XR     0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000 0x00000000000000f4 0x00000000000000f4 R      0x1000
  LOAD           0x0000000000002db8 0x0000000000003db8 0x0000000000003db8 0x00000000000002e8 0x00000000000002f0 XWR    0x1000
  DYNAMIC        0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8 0x00000000000001f0 0x00000000000001f0 WR     0x8
  NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000030 0x0000000000000030 R      0x8
  NOTE           0x0000000000000368 0x0000000000000368 0x0000000000000368 0x0000000000000044 0x0000000000000044 R      0x4
  0x6474e553     0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000030 0x0000000000000030 R      0x8
  0x6474e550     0x0000000000002014 0x0000000000002014 0x0000000000002014 0x0000000000000034 0x0000000000000034 R      0x4
  0x6474e551     000000000000000000 000000000000000000 000000000000000000 000000000000000000 000000000000000000 WR     0x10
  0x6474e552     0x0000000000002db8 0x0000000000003db8 0x0000000000003db8 0x0000000000000248 0x0000000000000248 R      0x1

Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
   03     .init .plt .plt.got .plt.sec .text .fini 
   04     .rodata .eh_frame_hdr .eh_frame 
   05     .init_array .fini_array .dynamic .got .data .i'm a teapot .bss 
   06     .dynamic 
   07     .note.gnu.property 
   08     .note.gnu.build-id .note.ABI-tag 
   09     .note.gnu.property 
   10     .eh_frame_hdr 
   11     
   12     .init_array .fini_array .dynamic .got 

Thank you very much in advance for your help 😉

New contributor

Maxence Gama is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

6

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật