INT 21h AH=07h waits for a key and returns it in AL, and that is all: nothing is echoed and no key is special. Ctrl+C comes back as 03h. It is the read for programs that handle every key themselves: editors, games, menus, and anything that has hooked a vector and must not be stopped halfway.

prints: 03 00 3B
MOV SI, OFFSET KEYS1
CALL STUFF1 ; Ctrl+C, then F1
MOV AH, 07h ; DOS: read a key, no echo, no Ctrl+C check
INT 21h ; AL = 03h: Ctrl+C, an ordinary key here
CALL HEX2
MOV DL, " "
MOV AH, 02h ; DOS: print the character in DL
INT 21h
MOV AH, 07h
INT 21h ; AL = 00h: a special key, the scan code follows
CALL HEX2
MOV DL, " "
MOV AH, 02h ; DOS: print the character in DL
INT 21h
MOV AH, 07h
INT 21h ; AL = 3Bh: the scan code of F1
CALL HEX2
KEYS1 DW 2E03h, 3B00h, 0 ; Ctrl+C, F1: scan code and ASCII
STUFF1 PROC NEAR ; "type" the keys at DS:SI: words, 0 ends the list
CLD
ST1:
LODSW ; AH = scan code, AL = ASCII
TEST AX, AX
JZ ST2
MOV CX, AX ; CH = scan code, CL = ASCII
MOV AH, 05h ; BIOS: store a keystroke in the keyboard buffer
INT 16h
JMP ST1
ST2:
RET
STUFF1 ENDP
HEX2 PROC NEAR ; print AL as two hex digits
PUSH AX
MOV CL, 4
SHR AL, CL ; the high digit first
CALL NIB1
POP AX
AND AL, 0Fh ; then the low one
CALL NIB1
RET
HEX2 ENDP
NIB1 PROC NEAR ; print AL (0-15) as one hex digit
ADD AL, "0"
CMP AL, "9"
JBE NIB2
ADD AL, 7 ; 10-15 become "A"-"F"
NIB2:
MOV DL, AL
MOV AH, 02h
INT 21h
RET
NIB1 ENDP

The example puts Ctrl+C (scan code 2Eh, ASCII 03h) and F1 (scan code 3Bh, ASCII 0) into the BIOS buffer through INT 16h AH=05h, then reads three bytes. Ctrl+C is just a byte. F1 takes two calls: the first returns 0, which means "a special key", the second its scan code. A key loop handles that like this:

MOV AH, 07h ; DOS: read a key, no echo
INT 21h
TEST AL, AL ; 0: a special key
JNZ PLAIN1
MOV AH, 07h ; read again: the scan code
INT 21h
; AL = 48h up, 50h down, 4Bh left, 4Dh right, 3Bh F1 ...
PLAIN1:
ServiceWaits for a keyEchoCtrl+C
INT 21h AH=01hyesyes^C, INT 23h: normally the end of the program
INT 21h AH=06h (DL = FFh)no: ZF = 1 when none is waitingnoreturned as 03h
INT 21h AH=07hyesnoreturned as 03h
INT 21h AH=08hyesno^C, INT 23h
INT 21h AH=0Ahfor a whole line, up to Enteryes^C, INT 23h

Pitfalls

A program that reads only with 07h has switched off the user's emergency exit, so it should offer one of its own, usually Esc (1Bh). The call still reads handle 0: with input redirected from a file it returns the file's bytes, and a program waiting for a particular key may wait past the end of the file. For the keyboard itself, whatever the redirection, use INT 16h AH=00h, which also returns the scan code of every key in AH in one call.