Thứ Hai, 1 tháng 4, 2013

Giới thiệu Reverse Engineering

Reverse Engineering là gì ?
Reverse Engineering là một process của việc lấy ra (extracting) knowledge hay là bản thiết kế chi tiết từ bất cứ thứ gì man-made.
RE thường được thực hiện để đạt được missing knowledge, ideas, và design philosophy khi những thông tin như vậy là unavailable. Trong một số trường hợp, thông tin được sở hữu bởi một cá nhân nào đó và họ không muốn chia sẻ. Trong một số trường hợp khác thông tin bị lost hay destroyed.

Truyền thống, RE thường taking shrink-wrapped products và physical dissecting chúng để tìm ra những secrets trong design của họ. Những secrets này sau đó được dùng để làm các sản phẩm tương tự hoặc là tốt hơn. Trong nhiều ngành công nghiệp, RE được yêu cầu examining sản phẩm dưới microscope, lấy những phần của nó và tìm hiểu xem mỗi phần trong đó làm những công việc gì.

Cách đây không lâu, RE công bằng mà nói chỉ là popular hobby, practiced bởi một số lượng người đông đảo.

Software RE: Reversing
Software là một trong nhiều công nghệ phức tạp và hấp dẫn xung quanh chúng ta ngày hôm nay và software RE nói về việc mở một program' "box" và nhìn vào bên trong đó. dĩ nhiên, chúng ta không cần bất cứ screwdrivers trên journey này. giống như soft

Thứ Tư, 27 tháng 3, 2013

Python Implementation of IP Checksum


Introduction

Tôi muốn viết một chương trình tính IP header checksum.

Background

Trước tiên chúng ta tìm hiểu cơ bản về IP checksum là gì, tại sao cần phải có IP checksum, cách tính IP checksum ? 

Checksum là gì?
checksum đơn giản là một giá trị được tính toán từ từ data packet nhằm kiểm tra tính toàn vẹn của nó (data packet). Thông qua checksum chúng ta có thể biết được dữ liệu được có bị lỗi hay không. Đó là bởi vì trong khi "traveling" qua mạng data packet có thể bị corrupt và phải có một cách nào đó để phía nhận dữ liệu biết được rằng dữ liệu bị corrupted hay không. Đó là lí do checksum field được thêm vào header. Ở phía nguồn, checksum được tính toán và được set trong header như là một trường. Ở phía đích checksum được tính toán lại một lần nữa và kiểm tra chéo với giá trị checksum tồn tại trong header để thấy được data packet có OK hay không. 

IP header checksum?

IP header checksum được tính toán trên IP header chỉ như phần dữ liệu, các TCP header , ICMP header đều có phần checksum riêng của nó. Now, để tính toán được một thuật toán tính checksum bạn cần phải có kiến thức cơ bản về cấu trúc của header trong IP protocol.

Cơ chế để tính IP checksum đó là : 16 bit one’s complement of the one’s complement sum of all 16 bit words in the header

có nghĩa là nếu chúng ta chia IP header thành các 16 bit words và thực hiện cộng dồn chúng lại và sau đó ta tiến hành đảo bit (0 --> 1, 1 --> 0) khi đó kết quả cuối cùng của các phép tính là checksum. 

Now, việc  tính toán phía trên được thực hiện tại phía nguồn gửi data packet. Tại phía đích nhận data packet sẽ thay thế giá trị checksum trong header bằng tất cả zeros và tính lại checksum dựa theo cơ chế giống như cơ chế tính tại nguồn. Sau bước này giá trị checksum đạt được sẽ được so sánh với giá trị checksum đến từ IP header. Việc so sánh sẽ quyết định xem IP header có bị corrupt hay fine. 

IP header checksum example

Dựa trên cơ sở nền tảng nêu phần trên, chúng ta thực hiện dump một IP header từ IP packet nhận được tại đích và tiến hành tìm hiểu cơ chế tính toán. Qua ví dụ sau:

Dưới đây là một IP header được lấy ra từ IP packet nhận được tại đích: 

                         4500 003c 1c46 4000 4006 b1e6 ac10 0a63 ac10 0a0c
* '45' tương ứng với hai trường đầu tiên trong IP header, '4 ' là IP version và '5' là header length. bởi vì header length được mô tả trong 4 byte words nên header length thực sự bắt đầu là 5 * 4 = 20 bytes.
* '00' ám chỉ đến trường TOS, giá trị này ám chỉ rằng đây là một operation bình thường. 
* '003c' là total length field của IP header. So, trong trường hợp này tổng độ dài của packet là 60.
* '1c46' là identification field.
* '4000' được chia trong 2 bytes. 2 bytes (tương ứng với 3 bits và 13 bits ) tương ứng với các trường flags và fragment offset. 
* '4006' được chia thành '40' và '06', '40' tương ứng với trường TTL, '06' tương ứng với trường protocol . '06' ám chỉ đây là TCP.
* 'be16' là giá trị checksum được tính tại nguồn (nơi gửi packet). Chú ý rằng trường này được set đến 0 khi ta tính toán checksum tại đích. 
* Tập hợp các bytes tiếp theo 'ac10' và '0a0c' tương ứng với các trường IP nguồn và IP đích.

Chúng ta thực hiện chuyển đổi tất cả giá trị của các trường theo cách phân chia 4 byte words sang giá trị nhị phân: 
4500 -> 0100010100000000
003c -> 0000000000111100
1c46 -> 0001110001000110
4000 -> 0100000000000000
4006 -> 0100000000000110
0000 -> 0000000000000000 // Note that the checksum is set to zero since we are computing checksum at destination end
ac10 -> 1010110000010000
0a63 -> 0000101001100011
ac10 -> 1010110000010000
0a0c -> 0000101000001100
Cộng các giá trị binary từng cặp với nhau 
4500 -> 0100010100000000
003c -> 0000000000111100
453C -> 0100010100111100  /// First result

453C -> 0100010100111100  // First result plus next 16-bit word.
1c46 -> 0001110001000110
6182 -> 0110000110000010 // Second result.

6182 -> 0110000110000010 // Second result plus next 16-bit word.
4000 -> 0100000000000000
A182 -> 1010000110000010 // Third result.

A182 -> 1010000110000010 // Third result plus next 16-bit word.
4006 -> 0100000000000110
E188 -> 1110000110001000 // Fourth result.

E188 -> 1110000110001000 // Fourth result plus next 16-bit word.
AC10 -> 1010110000010000
18D98 -> 11000110110011000 // One odd bit (carry),  add that odd bit to the result as we need to keep the checksum in 16 bits.

18D98 -> 11000110110011000
8D99 -> 1000110110011001 // Fifth result

8D99 -> 1000110110011001 // Fifth result plus next 16-bit word.
0A63 -> 0000101001100011
97FC -> 1001011111111100 // Sixth result

97FC -> 1001011111111100  // Sixth result plus next 16-bit word.
AC10 -> 1010110000010000
1440C -> 10100010000001100 // Again a carry, so we add it (as done before)

1440C -> 10100010000001100
440D -> 0100010000001101 // This is seventh result

440D -> 0100010000001101 //Seventh result plus next 16-bit word
0A0C -> 0000101000001100
4E19 -> 0100111000011001 // Final result.
Thực hiện việc đảo bit kết quả thu được 
4E19 -> 0100111000011001
B1E6 ->1011000111100110 // CHECKSUM

Thứ Ba, 26 tháng 3, 2013

Cấu trúc cơ bản của PE

Hình trên là cấu trúc của một PE file. Ở mức tối thiểu nhất thì PE file sẽ có 2 sections: 1 cho phần đoạn mã (code) và 1 cho phần dữ liệu (data). Một chương trình ứng dụng chạy trên nền tảng Windows NT có 9 sections được xác định trước có tên là : .text, .bss, .rdata, .data, .edata, .idata, .pdata, .debug  và .rsrc. Một số chương trình ứng dụng khác lại không cần tất cả sections này, trong khi cũng có một số chương trình lại định nghĩa nhiều sections riêng biệt để phù hợp với chúng.

 Những sections mà hiện giờ đang tồn tại và xuất hiện thông dụng nhất trong một file thực thi là :
1. Excutable Code Section, có tên là .text (Microsoft) hoặc là CODE (Borland).
2. Data Section, có những tên như .rdata, .data hoặc .bss (Microsoft) hoặc DATA (Borland).
3. Resources Section có tên là .rsrc (MicroSoft)
4. Export Data Section, có tên là .edata
5. Import Data Section, có tên là .idata.
6. Debug Information Section, có tên là .debug.

Những cái tên này thực sự là không thích hợp khi chúng bị lờ đi bởi HĐH và chúng ta tài liệu phục vụ cho lợi ích của các lập trình viên. Một điểm quan trọng nữa là cấu trúc của một PE file trên đĩa là hoàn toàn chính xác, đúng đắn giống hệt khi nó được nạp vào trong bộ nhớ nghĩa là ta có thể xác định chính xác thông tin file trên đĩa của một PE file nào đó mà sau đó ta phải kiếm tìm nó sau khi được nạp vào bộ nhớ.

Tuy nhiên nó không được sao chép lại một cách chính xác trong bộ nhớ. Các Windows loader sẽ quyết định phần nào được ánh xạ lên bộ nhớ, phần nào được bỏ qua. Phần dữ liệu mà không được ánh xạ lên sẽ được đặt phía cuối file, sau bất cứ phần nào sẽ được ánh xạ lên bộ nhớ, ví dụ Debug Information.

Cũng vì vậy vị trí của một mục trong file trên đĩa sẽ luôn khác biệt so với vị trí của nó khi được nạp vào bộ nhớ bởi sự quản lý bộ nhớ ảo dựa trên các trang mà Windows sử dụng. Khi các Sections được nạp vào RAM chúng được căn sao cho ăn khớp với 4KB memory Pages. Mỗi Sections sẽ bắt đầu trên một page mới. Một trường trong PE header sẽ thông báo cho hệ thống biết bao nhiêu bộ nhớ cần để riêng ra cho việc ánh xạ file

Main Memory

Lại phải vật lộn với một mớ hệ điều hành haizzz.

Như đã biết bộ nhớ là trung tâm trong hoạt động của hệ thống máy tính hiện đại. Bộ nhớ chứa mảng lớn các words hay bytes, mỗi trong số chúng có địa chỉ riêng. CPU lấy các instructions từ bộ nhớ dựa theo giá trị của program counter (pc). Nhiều instructions có thể gây ra việc loading thêm hay lưu trữ thêm đến các địa chỉ bộ nhớ xác định.

Một vòng thực thi lệnh tiêu biểu như sau: thứ nhất, lấy một instruction từ bộ nhớ. instruction này sau đó được decoded và có thể tạo ra các operands được fetch từ bộ nhớ. Sau khi lệnh được thực thi trên các operands, kết quả trả lại được lưu trong bộ nhớ. memory unit được xem như là một stream của các memory addresses, không biết làm thế nào chúng được sinh ra (bởi instruction counter (ic),  bởi indexing, indirection, literal address ...) hay chúng là gì ? (là lệnh hay dữ liệu). Theo đó chúng ra lờ đi việc làm thế nào chương trình sinh ra memory address thay vào đó chúng ta tập trung chú ý vào sequence các memory addresses sinh ra bởi chương trình đang chạy (running program).

1. Basic hardware
main memory và các registers được xây dựng bên trong processor chỉ là những nơi lưu trữ (storage) mà CPU có thể truy cập trực tiếp. Có nhiều machine instructions lấy các memory addresses như là các arguments, nhưng không một machine instructions nào sử dụng disk addresses. Do đó bất cứ lệnh nào được thực thi, hay bất cứ dữ liệu nào được sử dụng bới lệnh phải nằm trong bộ lưu trữ có thể truy cập trực tiếp. Nếu dữ liệu không nằm trong bộ nhớ, nó cần phải được di chuyển vào đó trước khi CPU operate trên chúng.
Registers được xây dựng sẵn trong CPU có thể được truy cập đến trong thời hạn một vòng của CPU clock.
Hầu hết CPU có thể decode các instructions và thực hiện được các operations đơn giản trên register contents ở tộc độ một hay nhiều operations trên clock tick. Điều này lại không đúng đối với bộ nhớ chính. Những gì có thể được truy cập via một giao dịch (transaction ) trên memory bus. hoàn thành một memory access có thể mất nhiều cycles của CPU clock. Trong những trường hợp như vậy processor cần phải stall, bởi vì nó không có dữ liệu yêu cầu để hoàn thành instruction mà nó được excuting. Hoàn cảnh như vậy là không thể chấp nhận đươc bởi vì tần số của memory addresses. Biện pháp khắc phục là thêm vào fast memory giữa CPU và main memory. Một memory buffer được sử dụng để accommodate tốc độ khác nhau, được gọi là cache. Chúng ta không chỉ concern về tốc độ truy cập bộ nhớ vật lý mà chúng ta còn phải đảm bảo correct operation để bảo vệ OS từ các user processes và thêm vào đó bảo vệ các user processes từ cái khác. Sự bảo vệ này phải được cung cấp từ hardware. Nó có thể được implemented theo một vài cách. Chúng ta outline một trường hợp có thể.

Thứ nhất chúng ta phải đảm bảo rằng mỗi process có một không gian bộ nhớ riêng biệt. Để làm được việc đó chúng ta cần phải xác định được cái range của legal addresses mà process có thể truy cập đến và phải đảm bảo rằng process chỉ có thể truy cập duy nhất đến những legal addresses đó. Chung ta có thể cung cấp sự bảo vệ đó bằng cách sử dụng 2 thanh ghi : limit và base. Thanh ghi base chứa địa chỉ hợp lệ nhỏ nhất mà process có thể truy nhập đến, thanh ghi limit chứa kích thước của range.

Việc bảo vệ không gian bộ nhớ được hoàn thành bởi có một CPU hardware thực hiện nhiệm vụ so sánh mọi địa chỉ sinh ra tại user mode với các registers. Bất cứ một nỗ lực nhằm vào việc truy nhập đến OS memory hoặc memory của user khác bởi một chương trình thực thi trong user mode sẽ rơi vào trap của OS, những gì "chăm sóc" sư nỗ lực này như là một fatal error. Cơ chế này nhằm ngăn một user program (cố ý hay tai nạn) thực hiện việc modifying code hay cấu trúc dữ liệu của OS hay của user khác.

Các thanh ghi limit và base chỉ có thể được loaded bởi OS những gì sử dụng một special priviled instruction. Bởi vì priviled instructions chỉ có thể được thực thi tại kernel mode, và bởi vì chỉ có OS thực thi trong kernel mode, chỉ có OS mới có thể load các thanh ghi limit và base. Cơ chế này giúp cho OS có thể thay đổi được các thanh ghi nhưng ngăn không cho user thay đổi nội dung thanh ghi.

OS thực thi trong kernel mode, nó được cấp các quyền truy nhập không giới hạn đến cả OS memory và cả user memory. Sự cung cấp này cho phép OS load các chương trình người dùng vào bộ nhớ của người dùng, để dump out nhiều chương trình trong trường hợp nó có lỗi, để truy cập và modify các parameters của các system calls và so on.

2. Address Binding

Thông thường, một chương trình đặt trong đĩa như là một binary excutable file. Để được thực thi, chương trình phải được mang vào trong bộ nhớ và đặt bên trong một process nào đó. Phụ thuộc vào cơ chế quản lý bộ nhớ ta sử dụng, process có thể di chuyển giữa đĩa và bộ nhớ trong suốt quá trình thực thi của nó. Các processes được đặt trên đĩa chờ đợi đến lượt mình thực thi có thể form thành input queue.

Thủ tục thông thường là ta lựa chọn một process trong input queue vào load process đó vào trong bộ nhớ. Như mỗi lần process được thực thi nó tiến hành truy nhập đến các instructions và data từ bộ nhớ. Cuối cùng khi process kết thúc thì không gian bộ nhớ của nó được khai báo là available.

Hầu hết OS cho phép một user process reside bên trong bất cứ phần nào của physical memory. Do đó, mặc dù không gian địa chỉ của máy tính bắt đầu là 00000, địa chỉ đầu tiên của user process không cần là 00000. Cách tiếp cận này ảnh hưởng đến các địa chỉ mà user program sử dụng. Trong hầu hết trường hợp user program có thể trải qua một vài bước, một số bước trong đó là optional trước khi thực thi. Các địa chỉ có thể được biểu diễn theo những cách khác nhau trong các bước. Addresses trong source program là biểu tượng (ví dụ như count). compiler sẽ thực hiện bind các symbolic addresses đến các relocatable addresses (ví dụ như: "14 bytes bắt đầu từ mở đầu của module này" ). editor hoặc loader bind các relocatable addresses đến absolute addresses (ví dụ như 74014). Mỗi binding là việc maping từ một không gian địa chỉ này đến một không gian địa chỉ khác.

Theo cách cổ điển, việc binding của instructions và data đến memory addresses có thể hoàn thành tại bất cứ step nào trong dây chuyền sau:
* compile time : nếu bạn biết rằng tại compile time nơi mà process sẽ được reside bên trong bộ nhớ khi đó absolute code được sinh ra. Cho ví dụ, nếu bạn biết rằng user process sẽ được reside trong memory bắt đầu từ location R, khi đó generated compiler code sẽ bắt đầu ở vị trí đó và mở rộng lên từ đó. Nếu sau đó starting location thay đổi, sau đó sẽ cần phải recompile this code. MSDOS .COM format programs được bind tại compile time.

* loading time : Nếu không biết được rằng tại compile time nơi process sẽ reside trong bộ nhớ, khi đó compiler sẽ sinh ra relocatable code. Trong trường hợp đó final binding sẽ được delayed cho đến loading time. Nếu starting address thay đổi chúng ta chỉ cần reload user code để kết hợp giá trị vừa thay đổi.

* excution time : nếu một process được di chuyển trong suốt quá trình thực thi của nó từ một memory segment sang memory segment khác, khi đó binding sẽ được delayed cho đến run time. Special hardware sẽ phải có sẵn cho cơ chế này làm việc. Hầu hết HĐH hiện nay sử dụng method này.

Một phần quan trọng là xét xem có bao nhiêu binding khác nhau được implement hiệu quả trong hệ thống máy tính và thảo luận về hardware support thích hợp.

Ta có thể mô tả multi step của processing một user program:
Ban đầu một source program trong giai đoạn compile time sẽ đi qua một bộ compiler or assembler kết quả sẽ sinh ra object module.
Tại giai đoạn load time object module sinh ra trong giai đoạn compile time sẽ kết hợp với các object module khác nhờ vào linkage editor kết quả sinh ra sẽ là load module. Cũng trong giai đoạn load time này còn có sự xuất hiện của loader với nhiệm vụ kết hợp load module và system library.
Trong giai đoạn execution time kết của sự kết hợp phía trên sẽ được kết hợp với các thư viện liên kết động và kết quả sẽ là các in-memory binary memory image.

3. Không gian địa chỉ ảo và không gian địa chỉ vật lý

Một địa chỉ được sinh bởi CPU thường được nhắc tới như là một logical address, trái lại một địa chỉ được nhìn như là một memory unit , đó là một thứ được load vào trong memory-address register của memory, đó thường được nhắc tới như physical address. Các phương thức address-bindings tại compile time và load time sinh ra các địa chỉ physical và logical giống hệt nhau, tuy nhiên execution-time address-binding scheme đưa ra kết quả các địa chỉ logical và physical khác nhau. Trong trường hợp đó chúng ta thường refer logical address như là virtual address. Tập hợp của tất cả logical addresses sinh ra bởi chương trình được gọi là logical address space, tập hợp tất cả các physical addresses tương ứng với các logical addresses được gọi là physical address space. Do đó trong executable-time address binding scheme, không gian địa chỉ logical và physical là khác nhau.

run-time mapping từ virtual đến physcal addresses dưới sự hỗ trợ của một thiết bị phần cứng đó là memory-management unit (MMU). Chúng ta có thể sử dụng các methods khác để hoàn thành việc mapping này. Chúng ta sẽ thực hiện mapping với một MMU scheme đơn giản đó là một sự tổng quát của base-register scheme. base register bây giờ được gọi là relocation register. giá trị của relocation register sẽ được cộng vào mọi địa chỉ sinh ra bởi process tại thời điểm địa chỉ được gửi tới memory. MS DOS chạy trên họ processor 8086 sử dụng 4 relocation registers khi loading và running processes.

User program không bao giờ thấy được real physical addresses. user program giải quyết với logical addresses. memory-mapping hardware converts logical addresses vào trong physical addresses.

Chúng ta có 2 kiểu địa chỉ : logical address (trong range từ 0 đến max) và physcal addresses (trong range từ R + 0 đến R + max, trong đó R là base value). users chỉ sinh ra địa chỉ logical và nghĩ rằng process run trong locations 0 đến max. Tuy nhiên logical addresses phải được map đến physical addresses trước khi chúng được sử dụng.

4. Dynamic Loading
Toàn bộ chương trình và tất cả dữ liệu của process phải nằm trong physical memory để process được thực thi. Do đó kích thước của process bị giới hạn bởi kích thước của physical memory. Để sử dụng tốt hơn memory-space, chúng ta có thể sử dụng dynamic loading, với dynamic loading một routine sẽ không được load cho đến khi nó được gọi. Tất cả các routines được giữ trên disk trong một relocatable load format. main program được load vào trong bộ nhớ và được thực thi. Khi một routine cần gọi một routine khác, calling routine đầu tiên kiểm tra có hay không routine khác đã được loaded. Nếu chưa có thì relocatable link loader được gọi để load routine mong muốn vào trong memory và update program's address tables để phản ánh sự thay đổi đó. Sau đó control được chuyển đên routine mới được loaded.
Lợi thế của dynamic routine là một unused routine sẽ không bao giờ được loaded. Phương thức này hữu dụng khi một lượng lớn code cần được handle không thường xuyên, ví dụ như error routines. Trong trường hợp đó mặc dù chương trình là lớn nhưng phần cần được handle là nhỏ.

Dynamic loading không yêu cầu một special support từ OS. Nó thuộc về trách nhiệm của user để thiết kế chương trình của họ để tạo lợi thế với phương thức này. OS có thể giúp programers qua việc cung cấp library routines để implement dynamic loading.

5. Dynamic linking và Shared libraries
Một vài OS chỉ support static linking, nơi những system language libraries được treated giống như bất cứ object module khác và được combined bởi loader vào trong binary program image. Dynamic linking ngược lại, tương tự với dynamic loading. Ở đây, mặc dù, linking, hơn loading, được trì hoãn cho tới excution time. Đặc tính này được sử dụng với system libraries, như là language subroutine libraries. Nếu không có facility đó, mỗi chương trình trên hệ thống phải include một bản sao language library của nó. với requirement như vậy sẽ lãng phí disk space và main memory.

Với dynamic linking, một stub đượ included trong image cho mỗi library routine tham chiếu tới. Stub là một piece of code nó chỉ ra làm thế nào để locate một memory-resident library thích hợp hoặc làm thế nào để load memory nếu routine không present. Khi stub excuted, nó kiểm tra xem có routine cần thiết đã có sẵn trong bộ nhớ hay chưa. Nếu chưa có, chương trình sẽ load routine vào trong memory. Hay một cách khác, Stub sẽ thay thế chính nó bằng địa chỉ của routine và executes routine. Do đó, next time particular code segment được reached, library routine được thực thi trực tiếp, không phải gánh chịu thêm chi phí cho dynamic linking. Dưới cơ chế này, tất cả các processes sử dụng language library chỉ thực thi duy nhất một bản sao của library code.

Tính năng này có thể mở rộng sang library updates (ví dụ như bug fixes ). Một library có thể được thay thế bởi new version, và tất cả programs tham chiếu đến thư viện đó sẽ tự động sử dụng new version. Nếu không có dynamic linking, tất cả các chương trình như trên sẽ phải được relinked để đạt được truy cập đến new library. Vì thế chương trình sẽ không vô tình execute các thư viện mới, không tương thích, các thông tin về version có thể đươc included trong cả chương trình và thư viện. Nhiều hơn 1 version của library có thể được loaded vào trong memory, và các chương trình sử dụng thông tin về version để quyết định xem bản sao nào của library được sử dụng. Versions với những sự thay đổi nhỏ sẽ giữ nguyên version number, trái lại versions với những sự thay đổi quan trọng sẽ tăng con số này. Do đó, chỉ các chương trình được biên dịch với new library version sẽ bị ảnh hưởng bởi bất cứ sự thay đổi không tương thích kết hợp trong nó. Các chương trình cũ được linked trước khi new library được cài đặt sẽ tiếp tục sử dụng older library. Hệ thống đó được biết đến như shared libraries.

Không giống như dynamic loading, dynamic linking yêu cầu sự trợ giúp từ OS. Nếu các processes trong memory được bảo vệ tránh khỏi những processes khác, khi đó OS chỉ là một thực thể để kiểm tra xem có hay không một needed routine nằm trong memory space của process khác hay là có thể cho phép nhiều process truy nhập đến cùng memory addresses.

6. SWAPPING
Một process phải được nằm trong memory để excuted. Tuy nhiên, một process có thể được swapped tạm thời ra ngoài memory đên một backing store và sau đó được đưa trở về cho continued excution. Cho ví dụ, trong một môi trường mutiprogramming với round-robin CPU scheduling algorithm. khi một quantum hết hạn, memory manager sẽ bắt đầu swap out process vừa kết thức, và swap một process khác vào trong memory space đã được giải phóng. Cũng trong lúc này, CPU sẽ scheduler sẽ allocate một time slice cho một số process khác trong bộ nhớ. khi mỗi process kết thúc quantum của nó, nó được swapped với process khác. CPU có thể swap processes đủ nhanh để một vài processes sẽ nằm trong bộ nhớ, ready to execute, khi CPU scheduler muốn reschedule CPU. Thêm vào đó, quantum phải đủ rộng để cho phép một số lượng tính toán hợp lý được hoàn thành giữa swaps.

Một biến đổi của chính sách swapping này được sử dụng cho priority-based scheduling algorithms. Nếu một process có độ ưu tiên cao hơn đến và wants service, memory manager có thể swap out process có độ ưu tiên nhỏ và sao đó load và thực thi process có độ ưu tiên lớn hơn. Khi hoàn thành xong process có độ ưu tiên lớn hơn, process có độ ưu tiên thấp hơn có thể  được swap trở lại để thực thi tiếp. Biến thể này của swapping thỉnh thoảng còn được gọi là roll out, roll in.

Thông thường, một process được swap out sẽ được swapped back lại trong cùng memory space nó chiếm lúc trước khi bị swap out. Sự hạn chế này được chi phối bởi phương thức address binding. Nếu binding kết thúc tại assembly hay là load time, sau đó process không thể dễ dàng đến location khác. Nếu execution binding được sử dụng, process có thể được swap vào bên trong memory space khác, bởi vì physical address được tính toán trong suốt execution time.

Swapping yêu cầu một backing store. backing store phổ biến có thể là một fast disk. Nó phải đủ lớn để chứa các bản sao của tất cả memory images cho tất cả users, và nó phải cung cấp truy nhập trực tiếp đến các memory images. Hệ thông duy trì một ready queue bao gồm tất cả processes sở hữu memory images lưu trên backing store hoặc trong memory và ready to run. Bất cứ khi nào CPU scheduler quyết định thực thi process, nó gọi một dispatcher. dispatcher sẽ kiểm tra xem có hay không next process trong queue nằm trong memory. Nếu không có, và nếu không có free memory region, dispatcher swap out process hiện hành trong memory và swap in process mong muốn. Nó sau đó reload registers và chuyển điều khiển đến selected process.

context switch time trong hệ thống swapping là khá cao, để có thể tính được context switch time, ta giả sử user process có kích thước là 100 MB và backing store là một standard hard disk với transfer rate là 50MB/s. Tốc độ thực để transfer 100MB-process đến main memory hay lấy từ memory ra là
                               100MB/50MB per second  =  2 seconds.

Giả sử độ trễ trung bình là 8 miliseconds và swap time là 2008 miliseconds. Bởi vì chúng ta vừa phải swap in và swap out thế nên tổng thời gian swap là 4016 miliseconds.

Chú ý rằng phần chính của swap time là transfer time. Tổng thời gian transfer tỷ lệ trực tiếp với lượng memory swapped. Nếu chúng ta có một computer system với 4G main memory và OS chiếm 1G, kích thước tối đa của user process là 3G. Tuy nhiên, nhiều user processes có thể nhỏ hơn so với mức này

Một cách tiếp cận String hay

Cách tiếp cận String trong cuốn Headfirst khá thú vị:

Bài toán đặt ra rằng: CEO của Starbuzz Coffee muốn có một chương trình show cho khách hàng của ổng xem giá cả hiện tại của coffee beans, công việc của chúng ta là viết một chương trình hiển thị kết quả đó ?

Hướng đi của bài toán là ta sẽ sử dụng một đoạn code để lấy thông tin trên từ địa chỉ http://beans.itcarlow.ie/prices.html . Đoạn code đó như sau :
import urllib.request
page = urllib.request.urlopen("http://www.beans-r-us.biz/prices.html")
text = page.read().decode("utf8")
print(text)

Đây là ouput :

<html><head><title>Welcome to the Beans'R'Us Pricing Page</title>
<link rel="stylesheet" type="text/css" href="beansrus.css" />
</head><body>
<h2>Welcome to the Beans'R'Us Pricing Page</h2>
<p>Current price of coffee beans = <strong>$6.50</strong></p>
<p>Price valid for 15 minutes from Tue Mar 26 08:02:01 2013.</p>
</body></html>

Đoạn HTML trên chính là một string được lưu trong biến text. Tuy nhiên với output như vậy vẫn chưa làm hài lòng vị CEO thế nên ta cần từ đoạn text trên có thể lấy ra chỉ giá trị của coffee, tới đây lại liên quan đên khái  niệm như index, substring... Để lấy được giá trị 6.50 ta cần phải xác định được offset của kí tự 6 và kí tư 0 (offset đầu tiên và cuối cùng của substring) sau đó sử dụng slice để trích giá trị đó ra, trong trường hơp này là text[234, 238].

Yeah một bài toán mà có thể cho ta những kiến thức cơ bản về sử dụng text trong Python.

Tạo button có thể click được trong Python


>>> from tkinter import *   # import nội dung của tkinter module (1)
>>> tk = Tk()                    # Tạo một biến chứa một đối tượng của class Tk  (2)
>>> btn = Button(tk, text="click me")  # Tạomột button (3)
>>> btn.pack()   # Hiển thị button (4)

(1) import theo cách trên chúng ta sẽ có thể sử dụng nội dung trong module tkinter mà không cần phải gọi tên của nó. Ví dụ trong trường hợp của module turtle ta có hai cách import sau
Cách thứ nhất : import turtle
                        t = turtle.Pen()
Cách thứ hai : from turtle import *
                      t = Pen()
Sự khác nhau giữa hai cách là chúng ta có thể gọi một đối tượng trong một lớp thuộc một module mà không cần phải gọi tên module đó, điều này thuận lợi khi chúng ta muốn sử dụng nhiều class trong một module nhưng không muốn gõ nhiều chứ về bản chất hai cách trên giống nhau.

(2): Giống như việc chúng ta tạo Pen object của module turtle, tk object tạo ra một cửa sổ, trên cửa sổ ấy chúng ta có thể thêm những thứ khác, như là button, input boxes hay là một canvas để vẽ trên đó. Đó là class chính cung cấp bởi tkinter module, nếu bạn không tạo một đối tượng của Tk class bạn không thể làm bất cứ thứ gì liên quan đến đồ họa, hoạt hình.

(3) Tạo một button với tk là tham số đầu tiên, và "click me" là một text mà button sẽ display

(4) Dẫu cho ta đã add button vào cửa sổ nhưng nó sẽ không displayed cho đến khi ta enter dòng này.

click me  button lúc này chưa làm được gì cả, bạn có thể click chúng cả ngày nhưng sự thực là chẳng có gì thay đổi cả. Đóng cửa sổ ta mới tạo hồi nãy, và thực hiện một số thay đổi trong code:

Định nghĩa một hàm với nhiệm vụ in ra dòng "hello there"
def hello():
      print('Hello there')

Sửa lại đoạn code phía trên
from tkinter import *
tk = Tk()
btn = Button(tk, text = "click me", command = hello)
btn.pack()

Một sự thay đổi thấy rõ khi ta có thêm thông số command, thông số này nói cho ta biết sẽ thực hiện hàm hello khi ta click vào button.


Thứ Sáu, 22 tháng 3, 2013

Tổng quan về PE

Một vài khái niệm trong bài viết
- VxDs là một mô hình device driver được sử dụng trong Microsoft Windows/386, các mode 386 tăng cường của Windows 3.x và Windows 9.x và một vài mở rộng bởi Novell DOS 7, OPEN DOS 7.01 và DR DOS 7.02 multitasker. VxDs có truy nhập đến bộ nhớ của kernel và các running process cũng như có những raw access đên hardware, VxD là viết tắt của "virtual xxx driver", trong đó xxx là một vài class của hardware . Nó xuất phát từ việc hầu hết các drivers có filenames dưới dạng vxxxd.386 trong Windows 3.x. Một vài ví dụ là: vjoyd.386(joystick), vmm.386 (memory management). VxDs thường có filename mở rộng dưới dạng .386 trong Windows 3.x và .vxd trong Windows 95. VxDs được viết trong Windows 3.x có thể sử dụng trong Windows 95 nhưng điều ngược lại là không đúng.
- Shareware: là loại phần mềm người dùng được dùng thử một thời gian (free trial) khi thời hạn dùng thử đã hết người dùng phải trả tiền bản quyền để có thể tiếp tục sử dụng. Đây là một mô hình kinh doanh trong phân phối phần mềm.
- Windows NT: là một họ của hệ điều hành được sản xuất bởi Microsoft, phiên bản đầu tiên được đưa ra vào tháng 6 năm 1993. Nó là một hệ điều hành mạnh mẽ dựa trên ngôn ngữ lập trình bậc cao, processor - independent, multiprocessing, hệ điều hành đa người dùng với các đặc tính được so sánh với Unix. Nó được dự tính bổ sung cho consumer versions của Windows dựa trên MS-DOS. NT là phiên bản 32 bit đầy đủ đầu tiên của Windows, trái với các đối tác hướng người dùng của nó, Windows 3.x và Windows 9.x là hệ điều hành lai 16bit/32bit, Windows 2000, Windows XP, Windows Server 2003, Windows Vista, Windows Home Server, Windows server 2008, Windows 7, Windows Server 2008 R2, Windows 8, Windows Phone 8, Windows RT và Windows Server 2012 là các thành viên của họ Windows NT.
Trước tiên chúng ta cần định nghĩa PE là gì ? PE là định dạng file riêng của Win32. Tất cả các file có thể thực thi trong Win32 (ngoại trừ các tập tin VxDs và các file Dlls 16bit) đều sử dụng định dạng PE. Các file Dlls 32 bit, các file COMs, các điều khiển OCX, các trình ứng dụng nhỏ trong Control Panel (.CPL files) và các ứng dụng .NET tất cả đều là định dạng PE. Thậm chí các chương trình được khiển tại kernel mode của hệ điều hành NT cũng sử dụng định dạng file PE.

Thế tại sao chúng ta cần tìm hiểu về PE? Có hai lý do chính như sau : Thứ nhất, chúng ta muốn thêm code vào trong những file thực thi (Ví dụ kĩ thuật keygen Injection hoặc thêm chức năng) và thứ hai là công việc unpacking bằng tay (manual unpacking ) các file thực thi. Hầu hết mọi sự quan tâm đều dồn về lý do thứ hai, đó là hầu như ngày nay các phần mềm shareware nào cũng được "Packed" với mục đích làm giảm kích thước của file đồng thời cung cấp thêm một lớp bảo vệ cho file.

Ở bên trong một file thực thi đã bị packed thì các bàng import tables thường thường là đã bị thay đổi, làm mất hiệu lực và phần dữ liệu thì luôn luôn bị mã hóa. Các chương trình packer sẽ chèn thêm mã lệnh để unpack file trong bộ nhớ vào lúc thực thi và sau đó nhảy tới OEP (original entry point) (Đây là nơi chương trình gốc thực sự bắt đầu thực thi, thi hành.). Nếu chúng ta tìm đươc cách để dump (kết xuất) sau khi chương trình packer hoàn tất được quá trình unpacking file thực thi, đồng thời thêm vào đó chúng ta cũng cần chỉnh sửa lại Section và import tables trước khi mà ứng dụng của chúng ta sẽ run. Làm thế nào chúng ta có thể làm được điều này nếu không có một tí tẹo hiểu biết nào về PE file ?

Chương trình thực thi được tôi sử dụng trong suốt bài viết này là BASECALC.EXE một chương trình rất hữu ích từ trang web của Fravia, nó cho phép tính toán và chuyển đổi giữa các hệ số decimal, hexa, binary và octal. Chương trình này được tác giả của nó coded bằng ngôn ngữ Borland Delphi 2.0, chính vì thế nó là một file lý tưởng để tôi lấy làm mình họa cho việc làm thế nào trình biên dich Borland để cho OriginalFirst Thunks null