Việc sử dụng công cụ xác định định dạng% n trong C là gì?


125

Việc sử dụng %nđịnh dạng định dạng trong C là gì? Bất cứ ai có thể giải thích với một ví dụ?


25
Điều gì đã trở thành nghệ thuật tốt của việc đọc hướng dẫn sử dụng tốt?
Jens

8
Tôi nghĩ câu hỏi thực sự là ĐIỂM của một tùy chọn như thế này là gì? tại sao bất cứ ai cũng muốn biết giá trị của số char được in ít hơn nhiều ghi giá trị đó trực tiếp vào bộ nhớ. Nó giống như các nhà phát triển đã chán và quyết định đưa một lỗi vào hạt nhân
jia chen

Đó là lý do tại sao Bionic từ bỏ nó.
solidak

1
Thực tế đây là một câu hỏi hợp lệ, và một câu hỏi mà các hướng dẫn sử dụng tốt có thể sẽ không trả lời; nó đã được phát hiện %nkhiến printfTuring- Complete vô tình và bạn có thể thực hiện Brainfuck trong đó, xem github.com/HexHive/printbfOilshell.org/blog/2019/02/07.html#appcill-a-minor-subliances
John Frazer

Câu trả lời:


147

Không có gì được in. Đối số phải là một con trỏ tới một int đã ký, trong đó số lượng ký tự được viết cho đến nay được lưu trữ.

#include <stdio.h>

int main()
{
  int val;

  printf("blah %n blah\n", &val);

  printf("val = %d\n", val);

  return 0;

}

Các mã trước in:

blah  blah
val = 5

1
Bạn đề cập rằng đối số phải là một con trỏ tới một int đã ký, sau đó bạn đã sử dụng một int unsign trong ví dụ của bạn (có thể chỉ là một lỗi đánh máy).
bta

1
@AndrewS: Bởi vì hàm sẽ sửa đổi giá trị của biến.
Jack

3
@Jack intluôn được ký.
jamesdlin

1
@jamesdlin: Sai lầm của tôi. Tôi xin lỗi .. Tôi không biết tôi đã đọc nó ở đâu.
Jack

1
Đối với một số lý do mẫu tăng lỗi với ghi chú n format specifies disabled. Lý do là gì?
Johnny_D 20/07/14

186

Hầu hết các câu trả lời này giải thích điều gì ( %n không in gì và viết số lượng ký tự được in cho đến một intbiến), nhưng cho đến nay vẫn chưa có ai thực sự đưa ra ví dụ về việc sử dụng nó. Đây là một:

int n;
printf("%s: %nFoo\n", "hello", &n);
printf("%*sBar\n", n, "");

sẽ in:

hello: Foo
       Bar

với Foo và Bar được căn chỉnh. (Thật là tầm thường khi làm điều đó mà không sử dụng %ncho ví dụ cụ thể này và nói chung, người ta luôn có thể chia tay printfcuộc gọi đầu tiên đó :

int n = printf("%s: ", "hello");
printf("Foo\n");
printf("%*sBar\n", n, "");

Liệu sự tiện lợi được thêm vào một chút có đáng để sử dụng một cái gì đó bí truyền như %n(và có thể giới thiệu các lỗi) hay không để tranh luận.)


3
Ôi trời - đây là phiên bản dựa trên ký tự tính toán kích thước pixel của chuỗi trong một phông chữ nhất định!

Bạn có thể giải thích tại sao & n và * s là cần thiết. Cả hai đều là con trỏ?
Andrew S

9
@AndrewS &nlà một con trỏ ( &là địa chỉ của toán tử); một con trỏ là cần thiết vì C là giá trị truyền qua và không có con trỏ, printfkhông thể sửa đổi giá trị của n. Việc %*ssử dụng trong printfchuỗi định dạng sẽ in một bộ %sxác định (trong trường hợp này là chuỗi trống "") bằng cách sử dụng độ rộng trường của các nký tự. Một lời giải thích về các printfnguyên tắc cơ bản về cơ bản nằm ngoài phạm vi của câu hỏi này (và câu trả lời); Tôi khuyên bạn nên đọc printftài liệu hoặc đặt câu hỏi riêng của bạn về SO.
jamesdlin

3
Cảm ơn đã cho thấy một trường hợp sử dụng. Tôi không hiểu tại sao mọi người về cơ bản chỉ sao chép-dán hướng dẫn vào SO và đôi khi điều chỉnh lại. Chúng ta là con người và mọi thứ được thực hiện vì một lý do luôn cần được giải thích trong câu trả lời. "Không có gì" giống như nói "Từ mát có nghĩa là mát mẻ" - kiến ​​thức gần như vô dụng.
the_endian

1
@PSkocik Nó đủ phức tạp và dễ bị lỗi mà không cần thêm một mức độ gián tiếp.
jamesdlin

18

Tôi chưa thực sự thấy nhiều cách sử dụng thực tế của trình %nxác định trong thế giới thực , nhưng tôi nhớ rằng nó đã được sử dụng trong các lỗ hổng printf của oldschool với một chuỗi tấn công định dạng khá lâu.

Một cái gì đó đã đi như thế này

void authorizeUser( char * username, char * password){

    ...code here setting authorized to false...
    printf(username);

    if ( authorized ) {
         giveControl(username);
    }
}

nơi người dùng độc hại có thể tận dụng lợi thế của tên người dùng tham số nhận được thông qua vào printf là chuỗi định dạng và sử dụng một sự kết hợp của %d, %choặc w / e phải trải qua các cuộc gọi stack và sau đó sửa đổi các biến được uỷ quyền một giá trị đích thực.

Vâng, đó là cách sử dụng bí truyền, nhưng luôn hữu ích khi biết viết daemon để tránh lỗ hổng bảo mật? : D


1
Có nhiều lý do hơn %nđể tránh sử dụng chuỗi đầu vào không được kiểm tra làm printfchuỗi định dạng.
Keith Thompson

13

Từ đây chúng ta thấy rằng nó lưu trữ số lượng ký tự được in cho đến nay.

n Đối số sẽ là một con trỏ tới một số nguyên được ghi số byte được ghi vào đầu ra cho đến nay bằng lệnh gọi này đến một trong các fprintf()hàm. Không có đối số được chuyển đổi.

Một ví dụ sử dụng sẽ là:

int n_chars = 0;
printf("Hello, World%n", &n_chars);

n_charssau đó sẽ có một giá trị 12.


10

Cho đến nay tất cả các câu trả lời là về điều %nđó, nhưng không phải tại sao mọi người muốn nó ở nơi đầu tiên. Tôi thấy nó hơi hữu ích với sprintf/ snprintf, khi bạn có thể cần phải chia nhỏ hoặc sửa đổi chuỗi kết quả, vì giá trị được lưu trữ là một chỉ mục mảng vào chuỗi kết quả. Ứng dụng này hữu ích hơn rất nhiều, tuy nhiên, sscanfđặc biệt là vì các chức năng trong scanfgia đình không trả về số lượng ký tự được xử lý mà là số trường.

Một cách sử dụng hackish thực sự khác là nhận được một giả log10 miễn phí cùng một lúc trong khi in một số như là một phần của hoạt động khác.


+1 để đề cập đến việc sử dụng cho %n, mặc dù tôi xin khác biệt về "tất cả các câu trả lời ...". = P
jamesdlin

1
Những kẻ xấu cảm ơn bạn đã sử dụng printf /% n, sprintf và sscanf;)
jww

6
@noloader: Làm sao vậy? Sử dụng %n hoàn toàn không có nguy cơ dễ bị tổn thương đối với kẻ tấn công. Sự khét tiếng không đúng chỗ %nthực sự thuộc về thực tiễn ngu ngốc khi truyền một chuỗi thông điệp chứ không phải là một chuỗi định dạng như đối số định dạng. Tình huống này tất nhiên không bao giờ phát sinh khi %nthực sự là một phần của chuỗi định dạng có chủ ý đang được sử dụng.
R .. GitHub DỪNG GIÚP ICE

% n cho phép bạn ghi vào bộ nhớ. Tôi nghĩ rằng bạn đang cho rằng kẻ tấn công không kiểm soát con trỏ đó (tôi có thể sai). Nếu kẻ tấn công điều khiển con trỏ (nó chỉ là một tham số khác cho printf), anh ta / cô ta có thể thực hiện ghi 4 byte. Liệu anh ấy / cô ấy có thể lợi nhuận là một câu chuyện khác nhau.
jww

8
@noloader: Điều đó đúng về bất kỳ việc sử dụng con trỏ. Không ai nói "kẻ xấu cảm ơn bạn" vì đã viết *p = f();. Tại sao %n, đó chỉ là một cách viết kết quả khác cho đối tượng được trỏ bởi một con trỏ, được coi là "nguy hiểm", thay vì xem xét chính con trỏ đó nguy hiểm?
R .. GitHub DỪNG GIÚP ICE

10

Đối số được liên kết với %nsẽ được coi là một int*và chứa đầy số lượng ký tự được in tại thời điểm đó trong printf.


9

Một ngày khác, tôi thấy mình trong một tình huống %nsẽ giải quyết tốt vấn đề của tôi. Không giống như câu trả lời trước đây của tôi , trong trường hợp này, tôi không thể nghĩ ra một phương án tốt.

Tôi có một điều khiển GUI hiển thị một số văn bản được chỉ định. Điều khiển này có thể hiển thị một phần của văn bản đó in đậm (hoặc in nghiêng hoặc gạch chân, v.v.) và tôi có thể chỉ định phần nào bằng cách chỉ định các chỉ số ký tự bắt đầu và kết thúc.

Trong trường hợp của tôi, tôi đang tạo văn bản cho điều khiển snprintfvà tôi muốn một trong những thay thế được in đậm. Tìm các chỉ số bắt đầu và kết thúc cho sự thay thế này là không tầm thường bởi vì:

  • Chuỗi chứa nhiều thay thế và một trong những thay thế là văn bản do người dùng chỉ định. Điều này có nghĩa là thực hiện tìm kiếm văn bản cho sự thay thế mà tôi quan tâm có khả năng mơ hồ.

  • Chuỗi định dạng có thể được bản địa hóa và nó có thể sử dụng $phần mở rộng POSIX cho các chỉ định định dạng vị trí. Do đó, việc tìm kiếm chuỗi định dạng ban đầu cho chính các bộ định dạng định dạng là không tầm thường.

  • Khía cạnh nội địa hóa cũng có nghĩa là tôi không thể dễ dàng chia chuỗi định dạng thành nhiều cuộc gọi đến snprintf.

Do đó, cách đơn giản nhất để tìm các chỉ số xung quanh một sự thay thế cụ thể sẽ là:

char buf[256];
int start;
int end;

snprintf(buf, sizeof buf,
         "blah blah %s %f yada yada %n%s%n yakety yak",
         someUserSpecifiedString,
         someFloat,
         &start, boldString, &end);
control->set_text(buf);
control->set_bold(start, end);

Tôi sẽ cho bạn +1 cho trường hợp sử dụng. Nhưng bạn sẽ thất bại trong một cuộc kiểm toán, vì vậy có lẽ bạn nên nghĩ ra một cách khác để đánh dấu sự bắt đầu và kết thúc của văn bản in đậm. Có vẻ như ba snprintftrong khi kiểm tra giá trị trả về sẽ hoạt động tốt vì snprintftrả về số lượng ký tự được viết. Có lẽ cái gì đó như: int begin = snprintf(..., "blah blah %s %f yada yada", ...);int end = snprintf(..., "%s", ...);và sau đó là đuôi: snprintf(..., "blah blah");.
jww

3
@jww Vấn đề với nhiều snprintfcuộc gọi là các sự thay thế có thể được sắp xếp lại ở các địa phương khác, vì vậy chúng không thể bị phá vỡ như vậy.
jamesdlin

Cảm ơn ví dụ. Nhưng bạn không thể, như, viết một chuỗi điều khiển đầu cuối để làm cho đầu ra được in đậm ngay trước trường và sau đó viết một chuỗi sau nó? Nếu bạn không mã hóa các chuỗi điều khiển đầu cuối, bạn cũng có thể đặt chúng theo vị trí (sắp xếp lại).
PSkocik

1
@PSkocik Nếu bạn xuất ra thiết bị đầu cuối. Nếu bạn đang làm việc với điều khiển chỉnh sửa phong phú Win32, điều đó sẽ không hữu ích trừ khi bạn muốn quay lại và phân tích các chuỗi điều khiển đầu cuối sau đó. Điều đó cũng giả định rằng bạn muốn tôn vinh các chuỗi điều khiển đầu cuối trong phần còn lại của văn bản được thay thế; nếu bạn không, thì bạn phải lọc hoặc thoát chúng. Tôi không nói rằng không thể làm mà không có %n; Tôi cho rằng việc sử dụng %nđơn giản hơn các lựa chọn thay thế.
jamesdlin

1

Nó không in bất cứ thứ gì. Nó được sử dụng để tìm ra có bao nhiêu ký tự đã được in trước khi %nxuất hiện trong chuỗi định dạng và xuất ra ký tự int được cung cấp:

#include <stdio.h>

int main(int argc, char* argv[])
{
    int resultOfNSpecifier = 0;
    _set_printf_count_output(1); /* Required in visual studio */
    printf("Some format string%n\n", &resultOfNSpecifier);
    printf("Count of chars before the %%n: %d\n", resultOfNSpecifier);
    return 0;
}

( Tài liệu cho_set_printf_count_output )


0

Nó sẽ lưu trữ giá trị của số lượng ký tự được in cho đến nay printf() chức năng .

Thí dụ:

int a;
printf("Hello World %n \n", &a);
printf("Characters printed so far = %d",a);

Đầu ra của chương trình này sẽ là

Hello World
Characters printed so far = 12

Khi tôi thử mã, nó cung cấp cho tôi: Xin chào các ký tự thế giới được in cho đến nay = 36 ,,,,, tại sao 36?! Tôi sử dụng GCC 32 bit trong máy windows.
Sina Karvandi

-6

% n là C99, không hoạt động với VC ++.


2
%ntồn tại trong C89. Nó không hoạt động với MSVC vì Microsoft đã tắt nó theo mặc định vì những lo ngại về bảo mật; bạn phải gọi _set_printf_count_outputtrước để kích hoạt nó. (Xem câu trả lời của Merlyn Morgan-Graham.)
jamesdlin

Không, C89 định nghĩa không phải tính năng này / backdoor. Xem K & R + ANSI-C amazon.com/Programming-Lingu-2nd-Brian-Kernighan/dp/, ?? ?? URL-tagger ở đâu để nhận xét ??
user411313

4
Bạn đơn giản là sai. Nó được liệt kê rõ ràng trong Bảng B-1 ( printfchuyển đổi) của Phụ lục B của K & R, phiên bản 2. (Trang 244 của bản sao của tôi.) Hoặc xem phần 7.9.6.1 (trang 134) của thông số kỹ thuật ISO C90.
jamesdlin

Android cũng loại bỏ công cụ xác định% n.
jww

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.