Việc tô màu đầu vào của người dùng rất khó vì trong một nửa trường hợp, nó được đầu ra bởi trình điều khiển đầu cuối (có tiếng vang cục bộ), vì vậy trong trường hợp đó, không có ứng dụng nào chạy trong thiết bị đầu cuối đó có thể biết khi nào người dùng sẽ nhập văn bản và thay đổi màu đầu ra cho phù hợp . Chỉ trình điều khiển thiết bị đầu cuối giả (trong nhân) biết (trình giả lập thiết bị đầu cuối (như xterm) gửi cho nó một số ký tự khi nhấn phím và trình điều khiển đầu cuối có thể gửi lại một số ký tự cho tiếng vang, nhưng xterm không thể biết liệu chúng có phải từ tiếng vang cục bộ hoặc từ những gì ứng dụng xuất ra phía phụ của thiết bị đầu cuối giả).
Và sau đó, có chế độ khác mà trình điều khiển đầu cuối được yêu cầu không lặp lại, nhưng ứng dụng lần này tạo ra một cái gì đó. Ứng dụng (như những ứng dụng sử dụng readline như gdb, bash ...) có thể gửi nó trên thiết bị xuất chuẩn hoặc thiết bị xuất chuẩn, điều này sẽ khó phân biệt với thứ gì đó xuất ra cho những thứ khác ngoài việc lặp lại đầu vào của người dùng.
Sau đó, để phân biệt thiết bị xuất chuẩn của ứng dụng với thiết bị xuất chuẩn của nó, có một số cách tiếp cận.
Nhiều trong số chúng liên quan đến việc chuyển hướng các lệnh stdout và stderr sang các đường ống và các đường ống được đọc bởi một ứng dụng để tô màu cho nó. Có hai vấn đề với điều đó:
- Khi thiết bị xuất chuẩn không còn là thiết bị đầu cuối (như đường ống thay thế), nhiều ứng dụng có xu hướng điều chỉnh hành vi của chúng để bắt đầu đệm bộ đệm đầu ra, điều đó có nghĩa là đầu ra sẽ được hiển thị trong các khối lớn.
- Ngay cả khi đó là cùng một quy trình xử lý hai đường ống, không có gì đảm bảo rằng thứ tự văn bản được viết bởi ứng dụng trên thiết bị xuất chuẩn và thiết bị xuất chuẩn sẽ được giữ nguyên, vì quá trình đọc không thể biết (nếu có gì đó được đọc từ cả hai) nên bắt đầu đọc từ ống "stdout" hay ống "stderr".
Một cách tiếp cận khác là sửa đổi ứng dụng để nó tô màu cho thiết bị xuất chuẩn và stdin của nó. Nó thường không thể hoặc thực tế để làm.
Sau đó, một mẹo (đối với các ứng dụng được liên kết động) có thể là chiếm quyền điều khiển (sử dụng $LD_PRELOAD
như trong câu trả lời của bệnh nhân ), các hàm xuất ra được ứng dụng gọi để xuất ra một cái gì đó và bao gồm mã trong đó đặt màu nền trước dựa trên việc chúng có nghĩa là xuất ra thứ gì đó không trên thiết bị xuất chuẩn hoặc thiết bị xuất chuẩn. Tuy nhiên, điều đó có nghĩa là chiếm quyền điều khiển mọi chức năng có thể từ thư viện C và bất kỳ thư viện nào khác có write(2)
ứng dụng được gọi trực tiếp bởi ứng dụng có khả năng kết thúc bằng cách viết một cái gì đó trên thiết bị xuất chuẩn hoặc stderr (printf, put, perror ...), và thậm chí sau đó , điều đó có thể sửa đổi hành vi của nó.
Một cách tiếp cận khác có thể là sử dụng các thủ thuật PTRACE như strace
hoặc gdb
thực hiện để tự móc mỗi khi write(2)
cuộc gọi hệ thống được gọi và đặt màu đầu ra dựa trên việc write(2)
có trên mô tả tệp 1 hoặc 2 hay không.
Tuy nhiên, đó là một việc khá lớn để làm.
Một mẹo mà tôi vừa chơi là đánh cắp strace
chính nó (công việc bẩn là tự móc trước mỗi cuộc gọi hệ thống) bằng LD_PRELOAD, để bảo nó thay đổi màu đầu ra dựa trên việc nó có phát hiện ra write(2)
trên fd 1 hay không 2.
Từ việc xem strace
mã nguồn, chúng ta có thể thấy rằng tất cả các kết quả đầu ra được thực hiện thông qua vfprintf
chức năng. Tất cả chúng ta cần làm là chiếm quyền điều khiển chức năng đó.
Trình bao bọc LD_PRELOAD sẽ trông như sau:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
int vfprintf(FILE *outf, const char *fmt, va_list ap)
{
static int (*orig_vfprintf) (FILE*, const char *, va_list) = 0;
static int c = 0;
va_list ap_orig;
va_copy(ap_orig, ap);
if (!orig_vfprintf) {
orig_vfprintf = (int (*) (FILE*, const char *, va_list))
dlsym (RTLD_NEXT, "vfprintf");
}
if (strcmp(fmt, "%ld, ") == 0) {
int fd = va_arg(ap, long);
switch (fd) {
case 2:
write(2, "\e[31m", 5);
c = 1;
break;
case 1:
write(2, "\e[32m", 5);
c = 1;
break;
}
} else if (strcmp(fmt, ") ") == 0) {
if (c) write(2, "\e[m", 3);
c = 0;
}
return orig_vfprintf(outf, fmt, ap_orig);
}
Sau đó, chúng tôi biên dịch nó với:
cc -Wall -fpic -shared -o wrap.so wrap.c -ldl
Và sử dụng nó như:
LD_PRELOAD=/path/to/wrap.so strace -qfo /dev/null -e write -s 0 env -u LD_PRELOAD some-cmd
Bạn sẽ chú ý làm thế nào nếu bạn thay thế some-cmd
bằng bash
, dấu nhắc bash và những gì bạn gõ xuất hiện màu đỏ (stderr) trong khi zsh
nó xuất hiện màu đen (vì zsh dups stderr lên một fd mới để hiển thị dấu nhắc và tiếng vang của nó).
Nó dường như hoạt động tốt đáng ngạc nhiên ngay cả đối với các ứng dụng mà bạn không mong đợi (như những ứng dụng sử dụng màu sắc).
Chế độ tô màu là đầu ra trên thiết bị strace
lỗi chuẩn được coi là thiết bị đầu cuối. Nếu ứng dụng chuyển hướng thiết bị xuất chuẩn hoặc thiết bị xuất chuẩn của nó, bước tiến bị tấn công của chúng tôi sẽ tiếp tục viết các chuỗi thoát màu trên thiết bị đầu cuối.
Giải pháp đó có những hạn chế:
- Những
strace
vấn đề cố hữu đối với : các vấn đề về hiệu năng, bạn không thể chạy các lệnh PTRACE khác như strace
hoặc gdb
trong đó hoặc các vấn đề setuid / setgid
- Nó được tô màu dựa trên
write
s trên stdout / stderr của từng tiến trình riêng lẻ. Vì vậy, ví dụ, trong sh -c 'echo error >&2'
, error
sẽ có màu xanh vì echo
xuất ra nó trên thiết bị xuất chuẩn của nó (được chuyển hướng đến stderr của sh, nhưng tất cả các bước nhìn thấy là a write(1, "error\n", 6)
). Và trong sh -c 'seq 1000000 | wc'
, seq
thực hiện rất nhiều hoặc write
s cho thiết bị xuất chuẩn của nó , vì vậy trình bao bọc cuối cùng sẽ tạo ra rất nhiều chuỗi thoát (vô hình) cho thiết bị đầu cuối.