레이블이 deep인 게시물을 표시합니다. 모든 게시물 표시
레이블이 deep인 게시물을 표시합니다. 모든 게시물 표시

[컴] 버그와 개발

테스트와 개발 / TDD / 완벽한 개발

버그

몇달전 reddit 에 올라온 링크가 있다. 이글을 통해 소프트웨어 개발과 버그에 대한 고민을 좀 해보자.

이 NASA 의 글에서 Test 는 정말 엄청나다. 새로운 기능을 하나 구현하는데 그들은 2년이 걸렸다. 그들은 수많은 테스트를 시행했지만 결국 그들의 프로그램이 완전무결하다는 것을 보증할 수 없었다고 한다.
  1. Developing software for the space shuttle : programming, 2017년 12월31일
  2. Computers in the Space Shuttle Avionics System, Computers in Spaceflight: The NASA Experience
우리가 계속 사용하는 software들도 계속 버그가 나타난다.
  1. 윈도우10 레드스톤3 벌써부터 버그 발생? | 케이벤치
  2. 인텔 CPU 커널 버그 총정리 : 대규모 보안 결함으로 성능 악영향 가능 - ITWorld Korea
  3. 씨넷코리아 | 맥OS의 어이없는 버그 “루트 권한 프리패스?”



개인적 결론

개발자가 버그를 너무 무시하는 것은 좋지 않다. 그것은 정말 안좋은 프로그램을 만들 수도 있기 때문이다.

하지만 빠른 개발을 위해서라면, 핵심에 집중하는 것이 낫다. 중요한(critical) 버그가 아니라면 그저 넘어가는 것이 낫다.

이것의 핵심은 어떤 기능이 가져다줄 가치가 버그로 인해 잃어버리게 될 가치보다 충분히 크다면 개발에 집중해야 한다고 본다.

그래서 결론은 reddit의 comment 에도 있지만, 우리가 버그를 잡는데에 공을 드리기 보다는 빠르게 prototype 을 만들고, 그것의 실패을 통해 bug를 잡는 방법(예를 들면, open beta test 등)을 확장하는 것이 더 나은 방법이라고 여겨진다.

물론 안타깝게도 개발자는 소프트웨어의 책임자가 아니라서, 매니저가(또는 사장)이 이런 방법에 동의할 수 있어야 할 듯 하다.









[컴] NERF 프로젝트

UEFI 부팅과정


구글 엔지니어 로널드 미니치(Ronald Minnich)의 발표








사용자가 인식하지 못하는 3가지 OS

  1. ME
    1. 사용자는 모르지만 인텔CPU는 그 동작을 인식할 수 있는 CPU 자원 제어(Ring -2)
    2. 시스템을 켜고 끄거나 인텔CPU 모르게 전원이 차단된 컴퓨터의 디스크 이미지를 재구성(Ring -3) 할 수 있다.
    3. 오픈소스 운영체제(OS) 미닉스(MINX) 3 버전의 변종이지만, 그 소스코드는 비공개다.
    4. 인텔 CPU의 액티브매니지먼트테크놀로지(AMT) 기능도 ME를 기반으로 구동된다.
  2. UEFI(통합확장펌웨어인터페이스)
    1. 메인 CPU에서 구동
    2. 복잡한 커널을 지녔으며 수백만줄의 코드로 구성
    3. 부팅이 완료된 뒤 UEFI 애플리케이션이 구동
    4. 보안모델이 불명확
    5. 다양한 시스템 기능에 접근할 수 있는 컴포넌트를 포함
  3. SMM
    1. 바이오스(BIOS) 영역에서 실행되는 시스템관리모드(SMM)
    2. ME 가 Ring -3 이라면 SMM과 UEFI는 Ring -2
    3. 오래된 16비트 마이크로프로세서(인텔 8086)용 코드의 통로
    4. 인텔의 칩셋은 한 번 SMM을 설치하면 이후 동작 단계에서 D램의 상위 8MB 크기 영역을 숨겨버린다. 그래서 사용자나 관리자는 그 코드를 control 할 수 없다.
    5. SMM은 제조사가 사용자 시스템 통제를 지속하는 역할을 한다.

사용자가 볼 수 있는 부분

  1. 일반 애플리케이션(Ring 3)
  2. 사용자 OS(Ring 0)
  3. OS를 가상화하는 SW(Ring -1)

NERF 프로젝트(Non-Extensible Reduce Firmware)

  • UEFI 펌웨어 기능을 "작은 리눅스 커널"과 "초기 램 파일 시스템(initial RAM file system, initramfs)"으로 대체하는 작업, code의 custom portion 들이 GO(언어 고) 로 만들어져있다.
  • 인텔CPU ME 펌웨어의 세부 구성요소를 파헤쳐서 가능한 많은 기능을 분석한 다음 그 전반적인 동작을 리눅스OS의 영역으로 되가져오기로 했다
  • 분석 결과 인텔CPU ME 펌웨어 자체를 전혀 쓰지 않는 것은 불가능
  • ME의 구성요소 대부분은 제거할 수 있는 것으로 파악
  • NERF 의 목표
    1. 해로운 동작을 할 수 있는 능력을 덜어낼 것.
    2. 동작을 더 가시화할 것
    3. 없애기 힘든 ME 펌웨어 자체를 제외한, 웹서버 및 IP스택 등 모든 런타임 구성요소를 제거
    4. UEFI의 IP스택과 다른 드라이버도 제거
    5. ME와 UEFI의 자체 덮어쓰기(self-reflash) 기능을 없앨 것
    6. 펌웨어의 플래시 업데이트를 리눅스에서 관리할 것

 UEFI 구성요소

UEFI boot 과정

References

  1. 인텔CPU 보안구멍, 구글이 메운다 - 지디넷코리아




[컴] Desktop HDD 와 NAS 의 차이

엔터프라이즈 NAS의 장점, NAS 와 일반하드의 차이점 /




이곳의 내용은 ref. 1 의 내용을 번역하고 재 구성한 것에 지나지 않는다. 정확한 내용은 ref. 1을 확인하자.

ref. 1에서 하드 선택시 아래3가지를 중요하게 여겨야 한다고 한다.

  1. RV sensors
  2. disc clamps 
  3. structural rigidity of the base plate



Desktop HDD 와 NAS 의 차이가 있는 부분

ref. 1 의 테이블을 확인하면 3가지 HDD의 차이점을 확인할 수 있다.

  1. Reliability
  2. Work Load Rating*
  3. Usage
  4. Usage By Form Factor
  5. Balance Control
  6. Seagate Acu_trac
  7. Heads
    1. NAS 드라이브와 Enterprise NAS 드라이브 모두 Desktop HDD 에 비해 높은 성능의 head 를 사용한다.
    2. 높은 성능의 head 는 좁은 물리적 크기들과 높은 signal to noise ratio 를 갖는다.
    3. 하드에러의 margin을 높여서 긴작업을 해도 error 가 적게 발생하게 된다.
    4. 이 증가된 margin 은 "높은 작업 온도", "높은 진동 환경"과 "높고 지속적인 부하(work loads)"과 같은 여러 스트레스에 대한 견고함을 향상시키는데 이용된다.
    5. "높은 작업 온도", "높은 진동 환경"과 "높고 지속적인 부하(work loads)", 이 3개의 요소가 NAS 드라이브의 성능에 중요한 영향을 미친다.
    6. head 의 품질이 NAS 드라이브의 신뢰성(reliability) 를 극적으로 증가시킨다.(MTBF, worload rating, usage)
    7. 드라이브가 error recovery 에 좀 덜 의존적이면 "좀 더 긴 사용시간", "더 높은 성능"을 갖게된다.
    8. 드라이브가 트랙사이의 빈 공간을 이용해서 좀 더 효과적으로 외부의 이벤트들을 흡수할 수 있을때 한결같은 data rate 은 더 높은 레벨에서 유지되어 진다.
  8. Disks
  9. Firmware
  10. Enterprise NAS HDD 만 다른 경우
    1. Motor : 
      1. 회전축(spindle) 을 위쪽 커버에 나사를 추가적으로 넣어서 좀 더 고정해 준다.
      2. 모터는 위아래 양쪽에 있다.(기본은 아래에만 있다.) 이로인해 50%정도 radial response 가 50% 정도향상된다. 그리고 상단에 있는 모터를 같이 사용하는 것은 진동이 심한 환경에서 잠재적인 이득이 있다.
      3. 이것은 결국 아래쪽에 한개만 있는 것보다 높은 수준의 구조적견고함(structural rigidity)을 가져다 준다.
      4. 이때문에 최근에 Enterprise NAS HDD는 높은 pack 부하(high pack loads)를 견딜 수 있다.
      5. disk windage plates 도 중요: 플래터 사이의 공기간섭(air disturbance)을 감소시켜준다.
      6. 베어링 구조: bubbles form, they are expelled
        많은 기름을 가지고 있는 centrifugal seals 를 갖고 있어서 높은 온도에서 장점을 갖는다.
    2. Rotational Vibration : 진동이 더 많다.
    3. Vibration Control
      1. RV sensors 는 가속도를 측정하는 센서로, 이것이 가속(acceleration), 진동(vibration) 과 충격(shock) 에 비례하는 결과를 제공한다.
      2. HDD 의 PCBA 에 붙어있다. 드라이브가 놓여있는 공간의 외부 진동을 측정하기 한다. (팬, 소리간섭, 랙(rack)의 진동등) 그래서 이 정보를 드라이브에 제공하고, 드라이브는 이런 환경의 변화에 따라 수정하고 보완한다.
      3. 이 덕에 head 가 좀 더 정확한 지점에 오래 머무를 수 있게 되는데, 이것은 그렇지 않을때 생기는 recovery step 등을 하지 않게 해줘서 처리능력(throughput performance)이 일정하게 유지 될 수 있게 해준다. 
      4. 이건 좀 더 빠른 spindle speed 를 가지는 Enterprise NAS HDD 에서 중요한다.
      5. 이것은 desktop HDD pcb 에는 없다.
    4. Base Plate
    5. Top Cover Attached
    6. Voice Coil Magnets(아래 그림 참조)
      1. 전기-기계 발생기(actuator)는 돌아가는 disc 표면에서 write/read 요소를 움직이는 방법이다. 추가로 writer/reader 가 원하는 위치(data track)로 움직이게 해준다.
      2. coil 감는 것(coil winding) 의 디자인과 자력 강도(magnet strength)가 voice coil motor 의 가속능력을 결정한다.
      3. 즉, 강한 VCM 디자인은 더 높은 가속능력과 더 빠른 움직이는 시간을 갖게 되고 그로인해 성능이 올라간다.
      4. Enterprise NAS HDD 는 좀 더 강한 자력의 VCM 을 사용한다.
    7. Disk Clamps
    8. Humidity Sensors






Desktop HDD 를 NAS 에 사용하기 어려운 이유


  • Desktop HDD 은 끊임없이 사용되는 것(constant use) 을 전제로 만들어지지 않았다. 그래서 NAS 에 사용하면 컴퓨터에서 사용할때보다 더 빠르게 노화되고, 닳게 된다.
  • tolerance: NAS HDD 는 더 높은 tolerance 를 갖는다. 이것은 다른 여러개의 HDD 와 같은 공간에서 돌아가게 되는 NAS HDD 의 필수요소이다.
  • firmware: firmware 도 다르다. Desktop HDD 는 desktop 사용에 최적화 되어 있다. NAS HDD는 끊임없이 사용되는 것에 최적화되어 있다. NAS 드라이브 firmware 는 또한 RAID 와 rebuild 시간에 최적화 되어 있다. 그래서 NAS에서 rebuild 가 훨씬 빠르다.
  • 플래터종류 : 다른 드라이브타입(desktop, NAS, enterprise NAS, ..)에는 다른 등급의 플래터(platter) 들이 있다. NAS 드라이브, 특히 enterprise NAS drive)는 더 높은 부하량(work load rating) 을 갖게 된다. 그래서 완전 다른 타입의 플래터가 필요하다.



References


  1. Pick The Right Drive For The Job — 24/7 NAS HDDs vs. Desktop HDDs | StorageReview.com - Storage Reviews, 2015년 7월

[컴] Spectre



Microarchitectural Side-Channel Attacks

이 구성요소들은 미래의 프로그램의 행동 예측을 통해 프로세서의 성능을 향상시킨다.
행동예측을 위해서 그 구성요소들은 과거 프로그램 동작과 관련된 state 를 유지한다. 그리고 미래 행동이 과거의 행동이랑 연관이 있거나 비슷할 것이라고 가정한다.

여러개의 프로그램들이 같은 하드웨어에서 동작할 때 (동시에 또는 time sharing 을 통해 ) 어느 한 프로그램에 의해 이뤄진 microarchitectural state 의 변화는 다른 프로그램에 영향을 준다. 결과적으로 이것이 한 프로그램에서 다른 프로그램으로의 의도치 않은 정보의 유출을 일으킬 수 있다.

과거의 작업들은 BTB, branch history, caches 을 통한 정보유출을 보여줬다. 여기서 우리는 Flush+Reload 와 그것의 변형인 Evict+Reload 를 사용해서 민감한 정보를 유출할 것이다.

이 기술들을 이용해서 공격자는 cache 에서 희생자(victim)가 공유한 cache line 을 추출할 수 있다.

희생자가 잠시 프로그램을 수행한 후에 공격자는 추출된 cache line(evicted cache line) 에 해당하는 주소에서 메모리를 읽는데 얼마나 시간이 걸리는지 측정하게 된다.

만약 victim 이, 모니터되고 있는 cache line 에 접근하면, 그 데이터는 cache 에 있게 되고, 접근은 빨라진다. 그렇지 않고, 만약 victim 이 그 line 에 접근하지 않았다면, read는 느리게 된다. 그렇기 때문에 access time 을 측정하는 것으로, 공격자는 victim 이 추출(eviction step)과 조사(probing step) 과정사이에서 monitored cache line 에 접근했는지 여부를 알 수 있다.

2개의 기술사이의 중요한 차이점은 cache 에서 monitored cache line 을 추출(evicting ) 할 때 사용되는 방법(mechanism) 이다.

Flush+Reload 기술에서는 공격자는 line 을 추출하기 위해 x86의 clflush 같은 기계어처럼 원래 그런 목적으로 만들어진 instruction을 사용한다.(dedicated machine instruction)

Evict+Reload 에서는 추출(eviction) 이 cache line 을 저장하고 있는 cache set 에서의 경합(contetion)을 만들어서 추출하게 된다. 즉, 다른 memory location 들을 접근해서 이 정보가 cache 로 들어오고 이전에 추출한 line 을 버리도록 만든다.



작성중...


See Also


References

  1. Meltdown and Spectre

[컴] Meltdown







요즘 CPU의 심각한 보안 구멍(security hole) 이 있다고 여기저기서 난리다. 그래서 내용을 좀 파보기로 했다.

일단 이 문제에 대해 자세한 설명을 해주는 page 는 아래와 같다.

크게 2개의 bug 가 보고 되었는데, meltdown 과 spectre 라 불린다.
  1. Meltdown
  2. Spectre
여기서는 Meltdown 리포트에 있는 글 가운데 몇개의 내용을 번역하고, 정리해봤다.


Meltdown

Out-of-order execution

Out-of-order execution 은 최적화 기술의 하나이다. 이 기술은 CPU core 의 모든 실행 단위(execution units) 에 대한 사용을 최대화 하기 위한 기술이다.(즉, execution unit 이 놀지않고 쉬지않고 일하도록 한단 이야기다.)

CPU 가 instruction들을 "순차적인 프로그램 순서(sequential program order)"로만 처리하는 것 대신에, 모든 필요한 resource(자원)가 갖춰지자마자 실행하게 된다. 순서를 벗어나서 실행을 하기 때문에 이름이 out-of-order execution 이다.

이러면 현재 동작에 의해 execution unit이 점유되어 있는 동안에도, 다른 execution unit 들은 다른 것들을 실행할 수 있다. 그래서 instruction들은 그들의 결과가 architectural definition을 따르는 만큼 병렬로 실행될 수 있다.

좀 더 쉽게 이야기하면, 원래 프로그램은 프로그램 내에서도 분리될 수 있는 부분이 있다. 예를 들면, 아래와 같은 코드가 있다면,
(1) c = a+b
(2) f = d+e
(3) h = c+f
(1) 과 (2) 는 아무런 연관성이 없기 때문에 병렬로 진행해도 된다. 만약 이것을 sequential program order 로 수행한다면, (1) 이후에 (2)가 진행되고, 그 후에 (3)이 진행될 것이다.

실제로,  out-of-order  execution을 지원하는 CPU 들은 추측해서 operation 을 수행하는 기능을 제공한다. 이 기능은 CPU가 instruction 이 필요해지고 commit되는 것인지 아닌지 대해 확실해 지기 전(즉, 결과를 확정짓기전에), 프로세서의 out-of-order logic 이 instruction들을 처리할 때까지는 수행된다.
In  practice,  CPUs  supporting  out-of-order  execution support  running  operations
speculatively to  the  extent that the processor’s out-of-order logic processes instruc-
tions before the CPU is certain whether the instruction will be needed and committed.

모든 이전의 instruction 들의 결과를 commit 하기전에 프로세서가 operation 을 수행하는 것을 여기서는 out-of-order execution 이라고 하자.


branch prediction units

branch prediction unit 들은 어떤 instruction 이 다음에 수행될 지에 대한 학습된 추측(educated guess) 을 얻게 된다. Branch predictor 들은 조건이 실제로 정해지기전에 어떤 branch 의 방향으로 갈 것인지를 정하기 위해 노력한다.

그래서 정해진 branch 에 있는 dependency 가 없는 instruction 들은 미리 수행되고, 만약 예측이 맞는다면 그대로 그 결과를 사용하게 된다.
만약 예측이 틀리면, reorder buffer 를 clearing 하고 unified reservation station 을 다시 초기화를 한다.

branch prediction 종류

  • static branch prediction
  • Dynamic branch prediction
  • One-level branch prediction 
  • neural branch prediction


주소 공간 Address Spaces


translation table

각 프로세스들을 독립적으로 하기위해서 CPU들은 가상메모리 공간(virtual address spaces)을 제공한다. virtual address 들은 물리적인 memory 주소로 번역(translate)된다.

virtual address spaces 들은 page 들의 집합(set)들로 나눠진다. "multi-level page translation table"을 통해서 이 page set들은 은 물리적인 메모리에 mapping 된다.

translation table 들은 가상주소에서 물리적주소로의 mapping 을 정의한다. 또한 readable, writeable, executable, user-accessible 같은 권한 체크(privilege check)를 수행할 때 사용되는 protection property 들을 정의한다.
  • virtual address --> physical address
  • protection property
특별한 CPU register 에 최근에 사용된 translation table 이 저장되어 있다. process 마다 virtual address space 를 구현하기 위해서 context switch 마다 OS 는 next process의 translation table address리 이 register 로 가져온다.

이 덕에 각 process는 그들의 virtual address space 에 속해있는 data 에만 접근할 수 있다. 이것은 해당하는 translation table들의 user-accessible property 를 disable 하는 방법을 통해서 OS 에 의해 수행된다.


kernel address space

kernel address space 는 kernel 의 사용만을 위한 memory mapped 만을 가지고 있는 것이 아니라 user page 들에 대한 operation 들을 수행한다. 즉, data 를 그곳에 write 할 수 있다. 결과적으로, 모든 물리적 memory 는 일반적으로 kernel 에 mapped 된다.

리눅스OS X 에서 이것은 direct-physical map을 통해서 이뤄진다. 즉, 모든 물리적인 메모리는 직접적으로 이미 정의된 virtual address 에 mapped 된다.

윈도우에서는 direct-physical map 대신에 paged pools, non-paged pools, system cache 라고 불리는 것들을 이용한다. 이 pool 들은 kernel memory space 에 있는 virtual memory 영역들이다. 이 virtual memory 영역들은 물리적인 페이지들을 virtual address 들에 mapping 해준다. virtual address 들은 memory 에 남거나 (non-paged pool) 또는 copy 가 이미 disk 에 저장됐기 때문에 memory 에서 지워질 수 있다.(paged pool) system cache 는 모든 file-backed page들의 mapping들을 포함한다. 이 memory pool들은 일반적으로 물리적 메모리의 많은 부분(large fraction) 을 모든 process의 kernel address space로 map 한다.


ASLR

memroy corruption 버그들은 종종 특정 data 의 주소들의 지식을 요구한다. memory corruption bug 들을 이용한 공격들을 지연시키기 위해서 non-executable stacks 와 stack canaries 가 와 함께 address space layout randomization(ASLR) 이 소개됐다.

  • non-executable stacks
  • stack canaries
  • address space layout randomization(ASLR)

kernel 을 보호하기 위해 KASLR 은 모든 boot 에서 드라이버가 위치하는 곳에 대한 offset들을 randomize 한다. kernel data 구조의 위치를 추측하게 만들기 때문에 결과적으로 공격을 하기 어렵게 한다.

side-channel attack

그러나 side-channel attack 들은 kernel data 구조들의 특정 위치를 알 수 있게 해주거나 자바스크립트에서 ASLR 을 무력화 시킨다. 소프트웨어의 버그와 이 주소들의 지식은 높은 권한의 코드 수행을 가능하게 해준다.


Cache attack

보통 CPU 들은 cache 를 가지고 있다. 우리가 거의 상식적으로 알고 있듯이 cache 는 자주쓰는 내용을 저장해 놓고, 빠르게 불러오는 용도의 저장소라고 할 수 있다.
Address space translation tables 들도 memory 에 저장되면서 동시에 regular cache 들에 cached 된다.

Cache side-channel attacks 은 cache 들로 인해 생기는 시간차(timing differences)를 이용한다.

예제, toy example

아래의 코드를 보자.
raise_exception();
// the line below is never reached
access(probe_array[data * 4096]);

이 코드에서 access(probe_array[data * 4096]); 부분은 실제로 수행될 수 없다. 일단은 앞에 raise_exception() 에서 프로그램이 종료되기 때문이기도 하고, raise_exception() 이 없다고 해도 이부분은 OS 에서 exception 으로 처리해서 이론적으로는 접근할 수 없다.

그런데, out-of-order exception 때문에 CPU 는 아마 이미 위의 코드에 해당하는 모든 instruction 들을 수행했을 것이다. 왜냐하면, exception 코드와 access(probe_array[data * 4096]); 코드는 dependency 가 없기 때문이다.

이것은 register 나 memory 로 load 되지 않았기 때문에 크게 문제되지 않지만, 미세구조적인 부작용(microarchitectural side effect)이 있다.

out-of-order execution 를 수행하는 동안에 참조되어지는 memoryregister 에 fetch 되고, 또한 cache 에 저장된다. 만약 out-of-order execution 이 버려져야 하면, register 와 memory 에 있는 내용들은 commit 되지 않는다. 그럼에도, cached 된 내용은 cache 에 그냥 남아 있게 된다.

Flush+Reload 로 "특정 memory 위치가 cache 됐는지 여부"를 알아낼 수 있는데, 이 방법을 통해 이 microarchitectural side-channel attack 의 효과를 크게 할 수 있다.

access(probe_array[data * 4096]); 는 probe_array를 data 값에 따라 4096 byte(4kB) 간격으로 접근하게 된다. 그래서 data 의 값으로 부터 memory page 까지의 injective mapping 이 있다. 즉 2개의 값은 같은 page 로 접근이 되지 않는다. 결과적으로 만약 page의 cache line 이 cached 되면 우리는 data 의 값을 알 수 있다.
(역자: 이것에 대한 좀 더 이해하기 쉬운 설명은 ref. 3을 확인하자. ref. 3 의 설명은 data 의 값은 cache된다 하더라도 user 가 접근할 수 없기에, data 을 주소를 가리키는 index 로서 사용해서 data 가 array 의 어떤 부분을 가리키는지를 확인하므로써 data 의 값을 판단한다고 한다.)

prefetcher는 page 범위들을 넘어서 data 를 접근할 수 없기 때문에, 다른 페이지들로 펼치는 것은 prefetcher 에 의한 false positive (틀렸는데 맞다고 하는 경우)들을 없애준다.




Meltdown


Meltdown(멜트다운) 은 2개의 빌딩 블럭들을 합쳐 놓은 것이다.
먼저 공격자는 CPU 가 transient instruction sequence(일시적으로 머무르는 명령어 순서) 를 실행하게 한다. 이 transient instruction sequence는 물리적인 메모리 어딘가에 저장해 놓은 "접근할 수 없는 비밀 값(inaccessible secret value)" 을 사용한다.

transient instruction sequence 은 은신처의 송신기처럼 동작해서 결국에 공격자에게 이 비밀값을 넘겨주게 된다.

Meltdown 은 3가지 단계로 구성된다.

Step 1

공격자가 접근할 수 없는 특정 메모리 위치의 내용이 register 로 load 된다.

원래 user application 의 virtual address 에도 kernel memory 의 내용이 mapping 된다. 그러나 user 의 권한으로 이 부분을 접근하면, OS 는 exception 을 발생시킨다.
여기서 meltdown 은 out-of-order execution 취약점을 이용한다.

Step 2

transient  instruction 은 register 의 비밀내용을 바탕으로 cache line 에 접근한다.

Step 3

공격자는 Flush와 Reload 를 이용해서 자신이 접근할 cache line을 정할 수 있게 된다. 그래서 결국 선택된 메모리 위치에 저장된 비밀을 접근할 수 있게 된다.

여러 메모리 위치(memory location) 에 대해 이 방법을 반복해서 사용하므로써 공격자는 물리적인 메모리 전체와 kernel memory 를 dump 할 수 있다.



See Also

  1. CPU.fail
  2. 멜트다운과 스펙터가 울린 경보음··· 하드웨어 취약점 33가지 살펴보기 - CIO Korea, 2020-01-11 

References

  1. 인텔 CPU 보안 무엇이 문제?...패치하면 성능 저하도, 동아사이언스
  2. Meltdown and Spectre
  3. https://pgr21.com/pb/pb.php?id=freedom&no=75348

[컴] 플래시 메모리 SLC, MLC, TLC

간단한 정리 SLC, MLC, TLC / 플레쉬 / 플래쉬 / nand flash /  메모리 / erase

간단한 flash memory 동작

쓰고, 지우기

셀에 전자를 채워넣고, 채워진 전자를 빼내는 방식을 하는 것이 우리가 흔히 아는 write, erase 가 된다.

읽기

  1. 채워진 전자의 양에 따라 자기장의 크기가 달라지는데, 이것에 의해 그 밑을 지나는 N 채널의 전도폭이 변한다.
  2. 이 N채널의 전도폭의 차이로 인해 전하량에(전류값에) 차이가 발생
  3. 이 전하량을 읽어서 어디부터 어디까지는 0으로, 어느값부터 어느값까지는 1로 구분한다.

SLC, MLC, TLC 의 cell

SLC 는 cell 하나에 2의 state 만 표현하는 것이다. 즉 전자가 채워져 있으면 1로 보고, 비워져 있으면 0으로 보면 된다.
MLC 는 cell 하나에 전자가 채워진 양에 따라 4가자의 state 를 표현하게 된다. 전자가 1/3 정도 채워지면 01, 2/3 정도 채워지면 10, 3/3 채워지면 11, 비워져 있으면 00 으로 보게 된다.
TLC 는 cell 하나에서 8가지 state 를 표현한다. MLC 와 마찬 가지로 1/7 정도 채워져 있으면 001, 2/7 정도 채워져 있으면 010, … 그래서 7/7 차면, 111 이 된다.

ECC 코드

위에서 한 이야기 처럼 SLC 보다 MLC, TLC 는 전자를 세밀하게 조절해야 한다. 그래야 하나의 cell 에서 여러가지를 표현할 수 있다. 그런데 이렇게 세밀하게 조절하면 필연적으로 간섭이 발생한다. 그래서 MLC, TLC 들은 확실히 SLC 보다 error 가 많다. 그래서 이 에러를 줄이기 위해 ECC(Error correcting code) 를 크게 줄 수 밖에 없다. 그래서 MLC, TLC 가 SLC 의 2배 용량이 되지 못하고, 성능도 좋지 못한 이유다.

산화막

cell 에 전자를 가두고, 흘려버리기 위해 산화막을 사용하는데, 이 녀석을 전자가 계속 왔다갔다 하다 보면 전자가 이 산화막에 조금씩 쌓이게 된다. (대충 철에 자력이 걸려서 철이 자석이 되는 것을 상상하면 된다.)

이렇게 되면 이 산화막을 통과하는 양이 줄어들거나 할 텐데, SLC 는 state 간의 전압차가 커서 어느정도 전하량이 줄어들어도 인식하는데 큰 문제가 없지만, MLC 나 TLC 는 state 간의 전압차가 크지 않아서 전압이 미세하게 틀어져도 잘못된 값으로 write / erase 될 수 있다.

그래서 MLC, TLC 의 수명이 SLC 에 비해 떨어진다.그래서 이것을 극복하기 위해 spare 영역, over provisioning 부분을 추가해 놓는다.

from: 전기 On·Off 썸타는 반도체, 스마트폰의 ‘줄기세포’

적층구조

위 그림 설명의 GATE 가 최초에는 ‘floating gate’ 라는 이름으로 ’도체’를 사용했다. 그래서 그 안으로 (-)전자를 끌어드렸다. 이 도체 floating gate의 상태가 변하고 1, 0 을 표시하게 된다. 위 그림의 스위치 on/off 를 보면 될 것 같다. 

SLC 는 이것을 채우고 비우는 것으로 1, 0 을 표시하는 것이고, MLC 는 이 floating gate 가 비어있으면 00, 1/3정도 채운것을 01, 2/3 채우면, 10, 3/3 을 채우면 11 이 된다.

그런데 공정이 미세해지면서 cell 간의 간섭을 줄이기 위해 ’부도체’로 바꿨다. ’부도체’는 말그대로 ’전자’가 흐르지 않는다. 그래서 구멍(trap)이 많은 부도체(실리콘 나이트라이드)를 이용해서 그 구멍에 전하를 저장하게 한다. (치즈를 연상하면 된다.)

from : [플래시메모리, 어디까지 알고 있니] 플래시메모리 No.1 역사의 시작 | 삼성반도체
[그림.2] from: https://www.samsungsemiconstory.com/m/434

이제 이것을 세로로 세운다. 위 그림(그림. 2)과 아래 그림(그림. 3)을 참고하자. 이렇게 세워서 양쪽으로 이 것을 붙이는 구조를 만들었다. 이것이 ‘3D V-NAND’ 이다. 이렇게 양쪽으로 붙이게 되면서 이것을 원형으로 만들어 사용하게 됐다. 그림2 그림을 보면 짐작이 가지만 이것이 요즘 ’구멍’을 얼마나 더 깊게 뚫을 수 있느냐를 이야기하는 것의 핵심일 것이다. 이 구멍을 뚫어야 control gate 안에 반도체Floating Gate 를 집어넣을 수 있을테니 말이다.

[K-반도체, 도전과 응전] ①누가 한국 반도체산업을 위협하나 - 오피니언뉴스 낸드 플래시 기술의 핵심은 저장공간을 높게 쌓은 뒤 전류가 흐르는 구멍(hole)을 한 번에 뚫는 ‘싱글스택(Single stack)’ 기술이다. 삼성전자는 128단 낸드플래시에도 싱글스택을 적용했다. SK하이닉스는 128단 최초 양산시 더블 스택을 적용했다. 100층 빌딩을 지을 때 한 번에 쌓아 올리기 않고 50층씩 두개를 연이어 쌓은 셈이다. 싱글스택은 더블스택보다 회로가 간단해 속도가 빠르다. 생산공정도 간단하다. 성능은 높은데 생산비용은 덜 드는 것이다.

포브스는 마이크론의 176단 낸드 출시를 전하면서 “마이크론 관계자가 176단 낸드가 88단을 두번 쌓은 ’더블스택’이라고 언급했다”고 밝혔다. 영미권 전자 전문지 아난드테크(anandtech), 익스트림 테크(Extreme Tech), 퍼드질라(fudzilla) 등은 모두 마이크론 사의 176단 낸드플래시가 “88단을 두번 쌓은 제품”이라고 분석하며 “새로운 기술이 아니다”고 덧붙였다.

from: https://m.blog.naver.com/shakey7/221416796692

References

[컴] 비트코인의 hash 알고리즘

비트코인 해쉬 알고리즘  / 해시 알고리즘 / bitcoin hash funciton

Bitcoin

비트코인관련 흥미로운 글이 있어서 정리 해 놓는다.

  • Bitcoin 에서 사용하는 hash function 은 SHA-256 이다.
  • Bitcoin 은 security 를 위해 SHA-256 을 두번 사용한다.(double-SHA-256)
  • 어떤 hash 값을 찾느냐 하면, 연속된 0 으로 된 hash 값을 찾아야 한다.
  • hash function 은 원래 예측안되는 random 값을 결과로 주며, 역으로 추측할 수 없는 특성때문에 특정 hash 값을 찾으려면 원하는 hash 값이 나올때까지 input 을 계속 변경하는 수밖에 없다.
  • 현재(2017년 12월)는 연속된 0이 약 17개 정도 있는 hash 값을 발견해야 한다. 이것의 확률은 1/140,000,000,000,000,000,000 정도라고 한다.


  • 비트코인 블럭 샘플
  •  hash function 의 input 으로 주면 연속된 0 을 가진 hash값(block hash) 가 나오게 된다.
  • hash input 은 아래 값들을 모아서 만들어진다.
    • version
    • previous block hash(reversed)
    • Merkle root(reversed)
    • timestamp
    • bits
    • nonce
  • 비트코인 블럭 샘플의 값을 가지고 테스트 해보면 이미 채굴된 값이라서 당연히 연속된 0이 나오지만, 대부분의 경우는 그렇지 않다. 그 경우에는 nonce 나 다른 부분의 값을 변경하면서 연속된 0 이 나오는 input 을 찾아야 한다.


SHA-256 해쉬 알고리즘

  • SHA256 hash algorithm 은 512bits 를 input 으로 받는다.
  • 그래서 결과로 256-bit 을 내어준다.
  • SHA 256 은 간단한 작업을 64번 반복해서 output 을 만든다.
  • 이 간단한 작업을 그림으로 표현하면 아래와 같다.

1 round

잘보면 대충 알 수 있는데, 일단 A,B,C,D,E,F,G,H 는 각 하나당 4byte(32bit) 로서 총 64 byte를 보여준다. 한 round 를 수행하면, ABCDEFG 가 오른쪽으로 움직이고, H 만 여러가지 계산을 거쳐서 A의 자리로 가게 된다. 자세한 설명은 아래 링크들을 참고하자.


scrypt hash algorithm

  • 하드웨어로 구현이 어렵도록 디자인된 hash algorithm
  • Litecoin / Dogecoin 등의 가상화폐가 사용한다.






[컴] 구글 개발자의 보안관련 패치 요청에 대한 리누스 토발즈의 답변



구글 개발자의 보안관련 패치 요청에 대한 리누스 토발즈의 답변


보안이 중요하긴 하지만, 그것이 언제나 우선시 되어야 할 항목은 아니다라는 취지의 글인듯 싶다. 약간 분노를 토하는 느낌이다.

대략의 내용은 보안의 문제가 언제나 버그는 아니이며, 보안의 문제가 process 를 죽이는 것을 합리화할 이유는 아니다라며 비판하고 있다. 오히려 security people 들이 이야기 하는 보안문제라고 부르는 것 자체가 '버그' 다라고 하고 있다.

대체적으로 느낌은 security people 들의 자신들만이 옳다는 시각에 대한 비판을 하는 듯 하다.

ref. 1에서 원본을 확인할 수 있다.


그리고 아래 reddit 의 comment 에서 좀 더 균형잡힌 의견들을 확인할 수 있다.



References

  1. Linux-Kernel Archive: Re: [GIT PULL] usercopy whitelisting for v4.15-rc1
  2. 리눅스 창시자 토발즈 "보안이 프로세스 죽이지 말아야" - 지디넷코리아


[컴] BCDboot 의 동작




BCDboot 의 동작






  1. BCDboot 이 "boot-environment 파일"들을 설치된 Windows 이미지에서 "시스템 파티션"으로 복사한다.
  2. BCDBoot 은 시스템 파티션에 "BCD(Boot Configuration Data) store"를 만드는데, 이 BCD store 는 컴퓨터가 Windows partition 으로 가서 부팅하도록 해준다.
  3. UEFI 기반 컴퓨터에서는 BCDBoot 은 이 boot 파일들을 가리키기 위해 NVRAM 에 firmware entry 를 추가한다.
  4. BCDboot 은 새로운 BCD store 를 만들고, 시스템 파티션에 있는 BCD boot-environment 파일들을 초기화하기 위해서 %WINDIR%\System32\Config\BCD-Template 에 있는 template 파일을 이용한다. 
  5. BCDboot 은 최신버전의 boot-environment files 를 os image 의 %WINDIR%\boot 폴더 에서 시스템 파티션으로 복사해 준다.
  6. 기본적으로 BCDboot 은 현재 Windows partition 에 대한 boot entry 가 있다면 현재 있는 entry 를 지우고 다시 복사한다.





References


  1. BCDboot Command-Line Options

[컴][보안] WPA2 보안 문제 - KRACK



WPA2 공격

KRACK(Key Reinstallation Attacks) 이라는 것으로 WPA2 protocol에서 해킹이 가능하다.

이 공격은 WPA protocol 자체의 취약점을 이용하는 것이다. 그래서 현재 대부분의 기기가 취약점이 노출되어 있다.(linux, openBSD, 맥OS , windows)

현재 windows 나 ios 에서는 4-way handshake 에서의 KRACK 공격은 통하지 않는다. (windows 랑 mac 에서 spec. 을 안따라서 그렇다.[ref. 1]) 그러나 group key refresh handshake 등 다른 handshake 에 대한 공격은 유효하다.

KRACK 은 아래와 같은 handshake 를 공격하는 것이다. 각 공격방법에 대한 이야기는 ref. 1 에 자세히 나와 있다.

  • 4-way handshake
  • PeerKey Handshake
  • ‎group key refresh handshake
  • Fast Basic Service Set (BSS) Transition (FT) handshake


대략적으로 4-way handshake 에 대한 공격 내용을 이야기하면 다음과 같다.
  1. AP 가 4번째 메시지를 받기전에 중간에서 못받게 가로채면
  2. AP 가 client 에게 3번째 메시지를 다시 보낸다.
  3. 그러면 client 가 3번째 메시지를 받고 이미 설정했던 PTK(Pairwise Transient Key) 를 다시 install 한다. 
  4. 그리고 그러면서 nonce 를 초기화 한다. 이 초기화되는 nonce 로 공격자가 replay, decrypt, and/or forge packets 를 할 수 있다고 한다.[ref. 1 6.1 Impact of Nonce Reuse in 802.11]

자세한 내용은 "ref. 1 의 3. ATTACKING THE 4-WAY HANDSHAKE" 를 보자.





Refernces

  1. https://papers.mathyvanhoef.com/ccs2017.pdf
  2. https://arstechnica.com/information-technology/2017/10/severe-flaw-in-wpa2-protocol-leaves-wi-fi-traffic-open-to-eavesdropping/
  3. https://arstechnica.com/information-technology/2017/10/how-the-krack-attack-destroys-nearly-all-wi-fi-security/

[컴] unit test 작성 범위는 ?

유닛테스트 작성방법 / 유닛테스트 작성 범위 / unittest / 유닛 테스트 / 테스트 작성 방법 / how to write unittest / unit test

Unit test 의 작성 범위

reddit 에 재밌는 글이 올라왔다.
대략의 내용은 "굳이 하지 않아도 되는 Unit Test 까지 만드느라고 시간을 낭비하고 있다." 정도일 듯 하다. 


우리가 unit test 를 필요로 하는 이유중 하나는 code가 추가되거나 변경될 때, 기존동작에 영향을 미치지 않는 것에 대한 확신을 가지기 위해서 이다.

개인적으로도 이 관점에서 우리가 만약 계속 확장되고, 다듬어져야 하는 project를 진행한다면 unit test 가 꼭 필요하다고 생각된다.

하지만 그렇다고 해서 모든 code 에 대한 검증을 할 필요는 없다. 그것은 시간이 너무 많이 소요되며, 크게 의미가 있지도 않다.

그럼 어떤 unit test 를 하지말아야 할까?


wrapper function 처럼 명확한 code 는 하지 않아도 된다.

위의 link 글에서도 이야기 하지만, 너무나 명확한 code는 굳이 unit test 를 작성할 필요가 없다.

위의 글에서는 아주 명확한 case 를 보여준다. 아무 일도 하지 않고, wrapper function 의 역할을 하는 method 가 예시로 나온다.


public method 에 대한 unit test 만으로 충분

원래 unit test 가 private method 에 대한 test 를 하지 않도록 되어 있지는 않다. 실제로 구글링을 해봐도 unit test 에서 private method 에 대한 test 방법들이 있다.

그래서 필자도 처음에는 특정 logic 에 대한 검증을 하기 위해 private method 라도 unit test 를 시행했다.

하지만 이런 경우 차라리 새롭게 public method 를 사용하도록 새로운 class 로 refactoring 을 하는 것이 맞는 듯 하다. 그리고 되도록 unit test 는 public method 선에서 처리하는 것이 좋은 듯 하다.

왜냐하면 private method 는 결국 public method 안에서 사용되며, private method 가 제대로 동작하지 않는다면 public method 또한 제대로 동작하지 않기 때문에 굳이 중복해서 test 를 행할 피요가 없기 때문이다.


그리고 우리가 public method 수준의 unit test 를 가지고 있어야, class 내부를 refactoring 하는 경우에 많은 private methods 들이 변경될 때 우리는 그것에 대한 unit test 를 없애고, 또 만들어야 하는 일을 하지 않아도 된다.

그러기에 우리는 public method 수준에서 unit test 를 충실하게 작성하면 충분할 것이라 생각한다.


mock up 이 필요한 경우등은 private method 를 test 해야 하는가?

그런데 간혹 외부 라이브러리 등의 문제등으로 mockup 등을 만들 수 없는 경우가 있을 수 있다. 이 경우 public method 를 검증하지 못할 수 있다. 그래서 private method 를 검증해야 할 것 같다. 하지만 만약 그런 경우의 private method 라면 새로운 class 로 만드는 것을 고려해 봐야 할 것이다. 아래 글들이 도움이 될 듯 하다.


See Also



[컴][네트워크] WannaCrypt ransomware 동작방식

워너크립트 동작방식 / 랜섬웨어 동작방식 / wannacrypt ransomware 동작 방식



WannaCrypt ransomware

요즘 한창 WannaCrypt 라는 ransomware(랜섬웨어) 때문에 뉴스들이 사람들에게 겁주고 있다. 평소에 백업을 잘해놓았다거나, windows update 를 잘해왔다면 겁을 먹을필요까지야 있을까 싶긴한데, 여튼 괜한 공포심을 조장하는 듯 싶어서 일단 이녀석이 어떻게 동작하는지 궁금했다.

그런데 한글 문서로 분석된 녀석이 하나도 없어서, 적어도 백신만드는 회사들에서라도 글을 써줘야 하는 건 아닌가 싶었는데, 안타깝게도 조용하다.

여하튼 일단 MS 에서 분석한 글을 찾았다. ref. 1 으로 가서 확인하면 된다.

여기서 다 번역하기는 귀찮고, 이해를 도울 몇개 글만 가져왔다.


WannaCrypt 동작 방식

아래는 ref. 1 의 내용중 일부를 가져와서 번역했다. 참고하자.

However, in this unique case, the ransomware perpetrators incorporated publicly-available exploit code for the patched SMB EternalBlue vulnerability, CVE-2017-0145, which can be triggered by sending a specially crafted packet to a targeted SMBv1 server, was fixed in security bulletin MS17-010, released on March 14, 2017.
보통은 사용자가 실행을 시켜야 하지만, 이번 케이스는 특별한 packet 을 보내면 SMBv1 server 가 받아서 실행을 시켜주는 케이스이다.
이것에 대한 패치는 2017년 3월 14일에 이루어졌다.

The exploit code used by WannaCrypt was designed to work only against unpatched Windows 7 and Windows Server 2008 (or earlier OS) systems, so Windows 10 PCs are not affected by this attack.
WannaCrypt 의 코드는 패치가 안된 윈도우7과 윈도우서버 2008(또는 이전버전의 os) 시스템에서 동작하게 만들어졌다. 윈도우즈10은 괜찮다.

The dropper tries to connect the following domain using the API InternetOpenUrlA():

hxxp://www[.]iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea[.]com

If connection is successful, the threat does not infect the system further with ransomware or try to exploit other systems to spread; it simply stops execution. However, if the connection fails, the dropper proceeds to drop the ransomware and creates a service on the system.

In other words, blocking the domain with firewall either at ISP or enterprise network level will cause the ransomware to continue spreading and encrypting files.
The threat creates a service named mssecsvc2.0, whose function is to exploit the SMB vulnerability in other computers accessible from the infected system:

dropper 가 특정 url 에 접속을 시도하는데, 이 접속이 성공하면, 동작을 멈춘다. 그런데 이 url 에 대한 접속이 실패하면, dropper 가 ransomware 를 떨구고, 그 컴퓨터에 mssecsvc2.0라는 이름의 service 를 만든다. 이 service 가 다른 컴퓨터(아직 패치되지 않은)들을 감염시키기 시작한다.
그러니까 ISP 나 기업네트워크 단의 firewall 에서 이 도메인에 대한 접속이 막혀있다면, 랜섬웨어가 동작하게 된다.


See Also

  1. [MS-SMB]: Server Message Block (SMB) Protocol
  2. Microsoft SMB Protocol Packet Exchange Scenario (Windows)





References

  1. WannaCrypt ransomware worm targets out-of-date systems – Windows Security, 2017-05-12


[컴][머신러닝] Machine Learning





대략적인 이해를 위해서 여기를 읽어보자.

Machine Learning

machine learning 에서 결론은 어떤 input 들에 대한 model(방정식)을 구하는 것이라고 보면 될 듯 하다. 즉 어떤 input 이 어떤 방정식(모델)을 따른다면, 우리는 새로운 input 이 들어왔을 때 이 input 에 대한 output 이 어떻게 나올지 알 수 있다.

이 어떤 방정식(모델) 이 앞으로 이야기할 hypothesis function 이 된다.

  • Supervised Learning[ref. 1] : data set 이 있고, 이 input 에 대해 어떤 output 이 나오는가 나와야 하는지를 알고 싶을때 사용할 수 있다.
    이 알고리즘은 input 과 output 에 상관관계가 있다고 생각하는 것이다. labeled data 를 가지고 작업한다고 생각하면 된다.
    문제는 다음 2가지로 나눌 수 있다.
    • regression problem : 결과값이 continuous 한 값으로 나오는 문제들, 예를 들어 "집크기와 가격에 대한 data 가 있을 때, 크기가 X 만한 집의 가격은?" 등의 문제가 regression problem 이 될 수 있다.
      결과 값으로 정수처럼 딱 나눠지는(discrete) 한 결과를 찾기보다는 실수 값을 찾는 문제일 가능성이 크다. continuous 값을 찾아야 하니까. 물론 그렇다고 정수를 결과값을 찾는 문제라고 해서 모두다 classification problem 은 아니다.
    • classification problem : 결과값이 discrete 한 값일 때 쓴다. 예를 들어 "집크기가 X 만한 집이 있다면 이 집의 가격은 Y 보다 큰가, 작은가?" 라는 문제는 크다, 작다 의 2가지 결과값중 하나가 output 이 되기 때문에 이녀석은 classification problem 이 된다.
  • Unsupervised Learning[ref. 2]
    • clustering algorithm
      어떤 dataset 에 대해서 grouping 을 알아서 해준다.
    • non-clustering algorithm : cocktail party alogorithm
      여러개가 섞여서 혼란스러운 상황에서 어떤 구조를 찾아준다. 목소리 2개가 섞여 있는 파일에서, 음성을 분리해 내는 등의 일을 한다.
    • octave : machine learning prototype 을 만들기 좋은 tool / Download

Training set
m = training example 의 개수
x = input / feature 라고 부름
y = output / target variable 이라 부름


hypothesis function

우리가 training set 을 통해서 알아낸 "예측 모형(model)" 을 hypothesis function 이라고 부른다.
  • function h : X → Y (h maps from x's` to y's)
이것을 좀 더 직관적으로 생각해보면, 우리는 주어진 어떤 값들(traning set) 을 좌표안에 찍고, 이 점들을 대체로 만족하는 "식"(hypothesis function)을 구하는 것이다.(식은 결국 그 점들을 전부 아우르는 그래프를 그려낼 것이다.)

우리가 이 "식"을 구하면 우리는 training set 이외의 위치의 값도 알 수 있고, 이것으로 예측을 하는 것이다.

그리고 이 hypothesis function 에서 우리가 구해야 하는 것은 θ 이다. 즉 식이 성립하기 위한 '상수' 를 찾아야 한다.(ref. 9 에서 θ 를 '가중치' 라고 이야기한다. ref. 9의 설명이 필자는 이해하기 쉬웠다.)

일단 여기서는 꼭 1차 방정식만을 이야기하는 것이 아니다 x 가 x2 을 뜻할 수도 있다.

예를 들어 아파트값을 구하기 위해 우리가 사용하는 변수가 '평수', "단지크기" 라고 한다면, 과연 "평수"가 아파트값에 미치는 영향이 클지, "단지크기" 가 아파트값에 미치는 영향이 큰지에 따라 가중치를 달리 할 것이다.

  • 아파트 값 = ("평수"x θ1) x ("단지크기" x θ2)



Univariate linear regression

여기서 우리는 이 함수(hypothesis function)가 일차방정식이라고 하자.(즉 어떤 input 과 output 과의 관계가 '일차방정식' 의 모양과 비슷하다고 보면 된다.)

그래서 우리는 y= θ+ θ1x 라는 방정식에서 θ 의 값을 찾아 낼 것이다. 여기서 이 θ 값은 어떤 값이어야 하는가?

당연히 이 θ 값을 넣어서 만든 h(x) 의 y 값이 실제 값과  가장 잘 맞는 θ 값이어야 한다.

from: ref. 2

이렇게 x 가 한개인 hypothesis function 을 단일변량 선형회귀 (Linear regression with one variable / Univariate linear regression) 라고 한다.
  • Linear regression model : hθ(x) = θ0 + θ1x

linear regression 의 목적은 cost function J(θ) 를 최소화 하는 것이다. 즉, 우리가 만든 hypothesis function 과 실제값과의 괴리를 최소화 하는 것이다.





Cost Function

이것을 이용해서 우리가 만든 hypothesis function 이 얼마나 실제값에 잘 맞는지 여부를 알아낼 수 있다.
from: ref. 3
위의 J(theta0, theta1) 이 cost function 이다. 이 함수를 "Squared error function", 또는 "Mean squared error" 라고 부른다.

이 함수를 잘 보면 결국 "예측값 - 실제값" 의 평균을 구하는 것이다. 즉 "우리가 예측한 것이 실제 값들에서 얼마나 멀어져 있는가?" 에 대한 값을 구하는 것이다.

이 cost function 의 최소값이 "괴리"가 가장 적다는 뜻이되고, 결국 predict model 이 정교하다는 것을 이야기 하기에, 우리는 cost function 의 최소값을 찾아야 한다.

hθ(x) 방정식이 y= θ1x 처럼 변수가 1개라면 위의 cost function 은  변수가 2개(θ0, θ1)인 2차방정식(x2)이 된다. 그러면 그래프의 모양은 아래처럼 될 것이다. (J(θ1),  θ1)



그리고 hθ(x) 방정식이 y= θ+ θ1x처럼 변수가 2개(θ0, θ1)라면 위의 cost function 은  변수가 3개인 2차방정식이 된다. 그러면 아래와 같은 모양이 된다.(J(θ1),  θ, θ1)
from: ref. 4

여기서  J(θ0 , θ1) 의 최소값을 선택하면, 우리는 training set 에 가장 가까운 hθ(x) 를 찾게 되는 것이다.


최소값을 찾는데에 도움을 주는 알고리즘


  • Gradient Decent Alogorithm
  • Normal Equation



Gradient Decent Algorithm

그럼 우리는 cost function 의 최소값을 찾아야 하는데, 이걸 찾게 도와주는 알고리즘중 하나가 Gradient Decent Algorithm 이다. 이 Gradient Decent Algorithm 이 Machine learning 알고리즘이다.

Gradient Decent Algorithm 이 있는데, 이녀석의 공식은 아래와 같은 식을 수렴이 될때까지 반복하는 것이다.
위 공식을 수렴(convergence)이 될 때까지 반복
α(alpha): learning rate


이 녀석의 계산은 아래처럼 해야 한다. 즉, θ0 를 계산한 후 그 값을 가지고 θ1 를 계산하는 것이 아니라, θ0 , θ1를 동시에 계산한 후에, 이 값을 update 한다.

이 공식을 풀어서 이야기 해보면, 현재 우리가 가진 hypothesis function, hθ(x) 가 있고, 이 녀석은 θ0 , θ1으로 이뤄져 있는데, 이 θ0 , θ1값을 바꿔가면서 가장 적합한 hθ(x) 를 찾아야 한다.

이 때 가장 적합한 hθ(x)를 찾는 것은 cost function 의 값이 가장 적은 값을 찾는 것이다. 그래서 Gradient Decent 알고리즘의 식은 θ값을 cost function 과 관계있게 만들어 준다.

특정 θ0 , θ1값에서의 cost value 가 있고, 이 cost value 에서의 기울기를 구한다. 이 기울기의 값도 중요하지만, 이 기울기로 방향을 정하게 된다. 이렇게 구한 기울기 방향으로 현재의 θ값을 움직여 나가는 것이다.(그것이 식에서는 현재 θ값에 값을 - 하는 것으로 표현된다.)

그래서 α(alpha) 가 learning rate 라 불린다. α(alpha) 가 크다면 최소값에 빠르게 접근할 수 있고, α(alpha) 가 작다면 느리게 접근하게 된다. 하지만 α(alpha) 가 너무 크면 움직이는 간격이 너무 커서 최소값을 지나쳐 버리고 다시 diverge 될 수 도 있다.


Multivariate Linear Regression

여러개의 변수 Multiple features

2개 이상의 변수가 있는 경우를 보자. 이것도 linear regression 이지만 변수가 여러개인 linear regression 이다. 그래서 multivariate linear regression (다변량 선형회귀)이라 부른다.

이전의 h 함수는 1개의 변수 x 가 있었다.
hθ(x) = θ+ θ1x

이 변수 하나가 대상의 특징(feature) 이라고 보면 된다.
hθ(x) = θ+ θ1x1 + θ2x2 + θ3x3 ... + θnxn

hθ(x) = θ0x0 + θ1x1 + θ2x2 + θ3x3 ... + θnxn


Gradient Descent for multiple variables

아래 모습처럼 feature 수만큼(x1, x2..) 의 식이 있고, 이것이 하나의 iteration 이 된다.




이 feature scaling 은 feature 즉 x0, x1, x2 .. 값들의 범위를(input value의 범위) 조절하는 것이다.(scaling) 이것은

Gradient Descent 를 좀 더 효율적으로 사용하기 위해 필요하다.


Gradient Descent 를 이용해서 우리는 hypothesis function 에 쓸 적절한 θ 값을 얻을 수 있다. 이 때 θ의 범위가 크면 descend 가 천천히 이루어진다. 그리고 x1, x2, ..(input value) 의 범위가 다르면 θ의 진동이 커진다. 그래서 최소값을 구하는데에 오랜 시간이 걸린다.

이때 θ의 범위를 줄이고, x1, x2 의 범위를 비슷하게 맞춰주면 적은 수의 연산으로 빠르게 최소값을 찾을 수 있다.(이부분에 대한 설명은 Gradient Descent in Practice I - Feature Scaling - Coursera 을 참고하자.)

standadization

이 θ의 범위를 줄이고, 변수 x1, x2, ... 의 범위를 비슷하게 하기 위해 x0, x1, x2 의 값들을 표준화, 정상화(normalization) 을 해준다. normalization 에 대한 이야기는 아래 경로를 참고하자.


Learning rate

gradient decent 에서 learning rate 값인 α를 구하는 법을 보자.

위 공식을 수렴(convergence)이 될 때까지 반복
α(alpha): learning rate
위에서 이야기 했지만, α가 너무 크면 수렴이 되지 않고, 너무 작으면 계산이 오래걸린다.
그래서 이 값을 정할 때는 대략적으로 값을 대입해서 아래처럼 그래프를 그려보면서 값이 제대로 수렴하는지를 확인해서 가장 적절한 α값을 선택한다.
from. ref. 6

Polynomial Regression

linear regression 의 그래프는 직선이다. 즉, input data 들이 대략적으로 한줄로 표현될 수 있다는 이야기다. 이때의 식이 다음과 같다.
  • hθ(x) = θ0x0 + θ1x1 + θ2x2 + θ3x3 ... + θnxn

하지만 data 속성이 꼭 linear 하지 않을 수 있다. 이때 polynomial regression 이 가능하다.
  • hθ(x) = θ0 + θ1x1 + θ2x12 ---->  θ0 + θ1x1 + θ2x2 (where x2 = x12 ) 
  • hθ(x) = θ0 + θ1x1 + θ2x12 + θ3x13



Octave 에서 구현한 Gradient Descent





Normal Equation[ref. 7]

Gradient Descent 알고리즘 처럼 J 값(cost function, 우리의 모델이 실제 data 와 얼마나 멀어져있나.) 을 최소화 하는 방법중 하나이다.

우리가 2차방정식에서 최소값 또는 최대값을 구할때 미분을 이용하듯이, vector 에 대해서도 편미분을 통해 구할 수 있다. 그런데 이것을 선형대수를 이용하는 것이다.(?)[ref. 7]
  • θ=(XTX)-1XTy
X, y 에 대해서는 아래 그림을 참고하자.
from : ref. 7


Gradient Descent Normal Equation
Need to choose alpha(learning rate) No need to choose alpha
Needs many iterations No need to iterate
O (kn2) O (n3), need to calculate inverse of XTX
Works well when n is large Slow if n is very large

  • n x n 행렬의 역행렬을 구하는데에는 대체로 O (n3) 이 소요된다.
  • 대체로 feature 의 개수 n 이 10,000 이하라면, 요즘 컴퓨터에서 사용할 만 하다고 한다.

Gradient Descent 알고리즘과 차이점

Gradient Descent 알고리즘은 여러번 수행을 해서 J(θ) 의 최소값을 찾는 방법이라면, Normal Equation 은 계산을 통해 한번에 최소값을 찾는 방법이다.


XTX 의 inverse matrix 가 없다면(not invertable)?

XTX is noninvertible 인 경우는 아래와 같은 이유일 수 있다.
  • 일단 redundant 한 feature 가 없는지 살펴보고, 있다면, 지운다.
    • 만약 집값의 feature 중에, "평수" 가 있고, "m2" 가 같이 있다면, 이것은 지운다.
  • 너무 많은 feature 를 사용하는 경우 라면 (m <= n) (m: the number of example)
  • feature 를 몇개 삭제하던지, regularization 이라는 방법을 이용한다.



References

  1. Supervised Learning | Coursera,
  2. Unsupervised Learning - Stanford University | Coursera
  3. Cost Function - Intuition I | Coursera
  4. Cost Function - Intuition II - Stanford University | Coursera
  5. Gradient Descent For Linear Regression - Stanford University | Coursera
  6. Gradient Descent in Practice I - Feature Scaling - Stanford University | Coursera
  7. Normal Equation | Coursera
  8. Machine Learning
  9. 기계 학습(Machine Learning, 머신 러닝)은 즐겁다! Part 1 – Jongdae Lim – Medium

[컴][아두이노] 아두이노의 bootloader

부트로더 / 붓로더 / 아두이노 부트로더



Arduino Bootloader

bootloader 를 보면 일단 microcontroller 에 의해 나뉜다. (쉽게 cpu 라고 생각하자.) 그래서 크게 2가지 소스가 있다.


여기서 용도에 따라 보드 종류에 따라 여러가지로 소스를 나눠놨다. 확인은 안해봤지만 대략적인 동작은 아래 ATmega8/Atmega168 bootloader 와 비슷할 듯 하다.(추측)

ref. 1 에서는 AVR bootloader 중 3가지에 대해 간략하게 설명을 해준다. 정리하면 다음과 같다.

ATmega8 bootloader(source)

  • 1KB 의 flash memory 만 사용한다.
  • 이것도 invalid data 를 받을 때 timeout 시키지 않는다. 
  • bootloader 가 동작할 때 6-8초 동안 data 가 보드로 전송되지 않게 해야 한다.

ATmega168 bootloader(source)

  • 현재 Arduino 0009 에 들어가 있는 bootloader 는  "Diecimila" 과  "NG" 버전(ATmega168이 장착된)과 거의 같다.
  • ATmega168에서 19200 baud 속도로 동작하고, flash memory 의 2 KB 를 사용한다.
  • Diecimila 과 NG 버전의 차이점
    • 시작시 초반에 새로운 프로그램이 동작하도록 기다려 주는 시간이 있다.(Diecimila 는 시간을 절약하기 위해 1초 미만을 기다리고, NG 는 6~8초를 기다린다.)
    • pin 13 LED 를 번쩍이는 횟수(Diecimila 은 한번 깜빡임, NG 는 3번)
  • NG 버전의 특징
    • Arduino NG 에 들어가있는 bootloader 는 약간 다르다.
    • pin 6 에 있는 internal pullup resistor 를 enable 한다.
    • RX pin 에 있는 internal pullup 는 enable 하지 않는다.
    • invalid data 를 받고 있는 동안에도 timeout 을 하지 않는다. 그래서 만약 reset 하자마자 data 를 upload 하면, 그 sketch 는 절대 동작하지 않을 것이다.

Arduino BT 의 bootloader(source)

  • Arduino BT 의 bootloader 는 블루투스 모듈에 대한 초기설정을 한다.



References

  1. Arduino - Bootloader > Versions of the bootloader


[컴][LLVM] ASTMatcher





Clang 의 AST 를 사용해서 code 등을 analyze 할 때 AST 의 특정부분을 찾는 것은 ASTMatcher 를 이용한다. "AST 에서 이러이러한 특징을 갖는 녀석을 찾고, 그것에 대해 무슨 일을 하고 싶다." 라면 이 ASTMatcher 를 사용하면 된다.

실제 Clang tool 을 작성하는 것에 대해서는 "Visual Studio 에서 Clang tool 빌드 하기" 를 참고하자.


AST Matcher

Clang 은 ASTMatcher library 를 제공하는데, 이녀석으로 Clang AST 를 좀 더 쉽게 이용할 수 있다.

아래문장에 ASTMatcher library 를 이용한 경우인데,

forStmt(hasLoopInit(declStmt(hasSingleDecl(varDecl(  )))))
  • for 문인데 : forStmt
  • loop 에 대한 init 을 가졌고 : hasLoopInit
  • 선언하는 문장이 있고(declare) : declStmt
  • 그 선언은 한개이고, : hasSingleDecl
  • 그 선언은 변수 : varDecl
위의 ASTMatcher 는 아래 for 문에 match 된다. (노란색)
for(int i = 0 ; i<10; i++){}

만약 loop 가 아래처럼 2개를 initialize 한다면 위의 조건에 맞지 않아서 match 되지 않는다.
for(int i = 0, j=0 ; i<10; i++){}



여기에 추가로 " '0'으로 초기화되는 녀석들만 가져오게 하고 싶다 "면,
hasInitializer(integerLiteral(equals(0)))
만 추가하면 된다.
forStmt(hasLoopInit(declStmt(hasSingleDecl(varDecl( hasInitializer(integerLiteral(equals(0))) )))))

실제로 선언을 할 때는 아래처럼 하게 된다. 아래에서 .bind 는 저 Matcher 에 ID 를 주는 것이다. 이 ID 를 이용해서 나중에 저걸 가지고 무엇인가를 작업할 때에 필요하다.
StatementMatcher LoopMatcher =
 forStmt(hasLoopInit(declStmt(hasSingleDecl(varDecl(
  hasInitializer(integerLiteral(equals(0)))))))).bind("forLoop");

이러면 아래같은 for 문이 있으면 match 된다.
for(int i = 0 ; i<10; i++){}


Callback 함수

이렇게 특정 녀석이 matching 됐을 때 callback 함수를 부를 수 있게 설정할 수 있다. 소스는 ref. 1 에서 가져왔다.

StatementMatcher LoopMatcher =
 forStmt(
  hasLoopInit(declStmt(
   hasSingleDecl(varDecl(hasInitializer(integerLiteral(equals(0))))
   .bind("initVarName")))),
  hasIncrement(unaryOperator(
   hasOperatorName("++"),
   hasUnaryOperand(declRefExpr(
    to(varDecl(hasType(isInteger())).bind("incVarName")))))),
  hasCondition(binaryOperator(
   hasOperatorName("<"),
   hasLHS(ignoringParenImpCasts(declRefExpr(
    to(varDecl(hasType(isInteger())).bind("condVarName"))))),
   hasRHS(expr(hasType(isInteger())))))).bind("forLoop");

 
class LoopPrinter : public MatchFinder::MatchCallback {
public:
 virtual void run(const MatchFinder::MatchResult &Result) {
  if (const ForStmt *FS = Result.Nodes.getNodeAs<clang::ForStmt>("forLoop")){
   FS->dump();
   llvm::outs() << "Potential array-based loop discovered.\n";
  }

  
 }
};


int main(int argc, const char **argv) {
  CommonOptionsParser OptionsParser(argc, argv, MyToolCategory);
  ClangTool Tool(OptionsParser.getCompilations(),
                 OptionsParser.getSourcePathList());

  LoopPrinter Printer; // callback
  MatchFinder Finder;
  Finder.addMatcher(LoopMatcher, &Printer);

  return Tool.run(newFrontendActionFactory(&Finder).get());
  // old code
  // return Tool.run(newFrontendActionFactory<clang::SyntaxOnlyAction>().get());
}




See Also

  1. Matching the Clang AST — Clang 5 documentation : Matcher 를 어떻게 사용하는지 알려준다.
  2. AST Matcher Reference : ASTMatcher library references


References

  1. Tutorial for building tools using LibTooling and LibASTMatchers — Clang 5 documentation



[컴][보안] 서버의 key 관리 어느것이 나을까?







DB 의 field 값 암호화

갑자기 서버가 사용하는 DB 에 user 의 어떤 key 를 저장할 때(예를 들면 특정사이트의 password 같은) 어떤 식으로 저장하는 것이 해킹에 안전할까에 대한 궁금증이 들었다.

그래서 조금 찾아보니 아래에 어느정도 잘 답변해준 자료가 있었다.


여기서 이야기 하려고 하는 것은 당연히 crypt/decrypt 가 되는 암호화를 이야기 하려 한다. hash 같은 방식은 여기서 사용하지 않는다. 왜냐하면 암호화 한 내용을 다시 복호화해서 사용해야 하는 상황이기 때문이다.

여하튼 짧은 보안에 대한 지식을 가지고, 어떤 식으로 key 를 관리하는 것이 좋은 지에 대해 한 번 생각해 보자.

키 관리 key management

symmetric / asymmetirc

개인적으로 symmetric 을 쓰려고 한다. asymmetric 은 key 를 주고받아야 하는 경우가 아니라면 굳이 사용할 이유는 없다고 생각된다. 암호화 수준도 대체로 symmetric 이 우수하다고 하며, key 를 따로 관리해야 하는 것도 부담이다.(혹시 이밖의 의견이 있다면 댓글을 부탁합니다.)

key 관리 key management

결국 이 암호화의 핵심은 key 를 hacker 에게 어떻게 노출시키지 않을 수 있는가 인 듯 하다. 그러기 위해서는 일단 key 를 어디에 숨겨야 한다. 그럼 key 를 숨기기위해 또 암호화를 할까? 그것은 그냥 절차(process)만 길게 만드는 것일 뿐, 보안에 더 도움이 되지 않는다.

보통은 이 key 를 hacker 가 알아내기 힘든 어떤 값으로 정하는 쪽을 택하는 듯 하다.

hacker 가 가져가기 힘든 것

hacker 가 알아내기 힘든 곳이란 것은 자신의 컴퓨터가 hacking 되었을 때를 가정하면 파악할 수 있다.

보통 자신의 server 의 admin 계정이 hacking 당하는 등의 일이 발생하면, hacker 는 계속 그 컴퓨터를 사용하기 보다는 서버의 정보를 자신의 local 로 옮기는 행위를 할 가능성이 크다.

왜냐하면 hacking 이 발각되면, admin 비밀번호를 당연히 바꿀 것이고, 다시 hacking 을 해서 정보를 가져오려면 또 오랜 수고가 필요할 테니 말이다.

그래서 결국 여기서 "hacker 가 알아내기 힘든 곳"이란 결국 hacker 가 가져갈 수 없는 어떤 것이 된다. 대부분은 network를 통해 가져갈 수 있으니, 그 이외의 것을 고려하면 된다.

Hardware

그래서 가장 좋은 것은 encryption key 로 h/w 정보를 이용하는 것이다. 물론 쉽게 알아낼 수 있는 것은 좋은 생각이 아니다.(예를 들면 mac address)

그래서 ref. 1 에서 이야기하는 것처럼 외부에 장착된 security module 같은 것은 더없이 좋다. 아니면 TPM chip 같은 것을 이용하는 것도 좋다.

물론 여기에도 한계는 있을 수 있다. hacker 들이 이것을 이미 알고 들어왔다면, security module 을 hacking 하는 시도를 하던지 TPM chip 의 정보등은 해킹을 시도할 수 있다.

login 정보

os 의 login 정보를 key 로 이용할 수 있다. 하지만 이것은 linux 에서 어떤 식으로 가능한지는 잘 모르겠다. windows 는 이런 API 를 제공한다. 이것을 활용한 예는 chrome 에서 볼 수 있다. 아래 글을 참고하자.

server 시작시 key 입력

개인적으로는 이녀석이 쉽게 구현해서 사용할 수 있고 꽤 괜찮은 안전 수준을 제공해 줄 듯 하다. ref. 1 에서 memory dump 로 가져갈 수 있다고 하지만, 입력한 key 의 hash 값을 사용한다면 더욱 안전한 상태가 가능할 듯 싶다.

하지만 관리자가 계속 머리에 key를 가지고 있어야 하는 것은 부담이다.

다른 서버에 저장

개인적으로 이것도 나쁜 생각은 아니라고 생각된다. 하지만, 그 key 를 저장하는 서버가 충분히 secure 할 필요는 있다. 쉽게 뚤리는 곳이라면 hacking 도 쉽게 이뤄질 수 있으니, 순간적으로 key 가 탈취당하지 않았다고 생각할 수 있지만, 바로 hacking 해서 가져갈 수도 있다.

하지만 일단 또다른 hacking 을 해야 한다는 부담감이 있어서 단념하게 될 가능성도 크다.

같은 서버에 다른 곳에 저장

가장 쉽게 생각할 수 있는 부분인 듯 하다. 그저 network 로 정보만 가져가는 경우라면 다행이겠지만, admin 계정등을 해킹해서 들어온 경우라면, 큰 의미없는 보관이 될 듯 하다.

개인적은 결론

일단 개인적으로 자금이나 machine 의 상황으로 h/w 를 이용하는 것은 쉬운일이 아니라고 생각된다. 그런면에서는 조금 번거롭지만, 암호를 잘 기억해서 server 를 띄울때 typing 하는 것이 가장 합리적인 방법이 아닐까 생각된다.

References

  1. key management - Where to store a key for encryption? - Information Security Stack Exchange

[컴][인터넷] warning.or.kr 이 뜨는 원리



Warning page 가 보여지는 원리

처음에 이것은 DNS server 에서 mapping table 을 조작해서 warning 페이지를 보여주는 줄 알았다. 그런데 요새 좀 바뀐 듯 하다. 관련해서 찾아본 내용을 대충 정리해 본다.


누가 차단하나?



누가 차단하는지를 확인하기 위해 "차단절차"를 한번 살펴보자. ref. 1 에 자세한 사항이 나와 있다. 여기서 정리하면 다음과 같다.

차단 절차
  1. 차단결정 : 방심위(방통심의 위원회) 심의를 통해 차단 결정이 나면 
  2. ISP 에 목록 전달 : 결정난 목록을 각 ISP(인터넷사업자)에게 전달
  3. ISP 에서 설정 : ISP들이 가지고 있는 ‘필터링 사이트 목록’에 새로 결정된 사이트 목록을 추가
  4. 사용자 중 누군가 이 목록에 들어가 있는 사이트에 접속 시도를 하면, 
  5. 필터링 장치가 동작해 접속을 끊고 대신 방심위의 warning.or.kr 페이지로 포워딩

결국 차단할 목록을 정하는 것은 "방통심의 위원회"이지만, 실제로 차단을 하는 곳은 "ISP 의 장비"인 것이다.


차단원리

위에서 보았듯이 결국 실질적인 행동은 ISP 에서 하게 된다.

DNS lookup table 수정

ISP 에서 예전에는 DNS server 를 조작해서 warning page 를 보여줬다.

좀 더 자세하게 이야기 하면, 원래 우리가 알고 있는 domain name(http://daum.net 같은 주소라고 보면 된다.) 는 그 domain name 에 대한 "ip 주소" 를 찾아서 그 ip 주소로 packet 을 보낸다. 이 때 domain name 에 대한 'ip 주소' 를 찾아주는 일을 DNS server 가 해준다.

  • Domain name ---> IP address

이 DNS server 이 mapping 해주는 작업은 간단하게 table 을 가지고 한다. 하나의 table 에 어떤 주소는 어떤 ip 주소로 가라고 표시되어 있다.

  • daum.net --> 129.0.2.1 

이 table 을 수정하는 것이다.

그래서 그냥 DNS server 주소를 다른 것으로 바꿔서 사용하는 것으로 warning 을 피할 수 있었다. (참고)


Http header 에서 Host 부분

이것이 이제는 바뀌어서 사용자가 보내는 packet 의 "Http header 에서 Host 부분을 확인"하는 듯 하다. 이것은 ref. 2 / ref. 3 / ref. 4 를 통해 어느정도 확인할 수 있다.

특히 ref. 4에서는 warning page 를 우회시켜주는 프로그램에서 나가는 packet 을 확인해서 좀 더 정확한 이야기를 해준다.

  • Dodge Chrome'(닷지 크롬) 
  • 'Dodge Web & Dodge Browser'(닷지 웹 & 브라우저)

좀 더 자세하게 이야기하면,

packet 의 http header 에는 아래 같이 요청하는 domain 주소가 들어가게 된다. 여기에 적힌 domain 주소를 확인해서 그 주소가 "방심위" 에서 보낸 list 에 있는 주소라면 warning page 로 돌리는 것이다.
Host: daum.net





References


  1. 웹의 자유 옥죄는 ‘방통심의위원회’…Warning.or.kr의 불편한 진실 - 경향신문, 2014-01-18
  2. SSL 페이지는 검열 불가능? - 일간워스트, 2014-05-21
  3. [warning.or.kr] 사이트 차단 및 우회 기법 분석 - 1
  4. [warning.or.kr] 사이트 차단 및 우회 기법 분석 - 2

[컴] Bit torrent - 정리안된 자료



정리안된 자료다. 일단 버리는 것보다 살려두면 혹시나 필요할 일이 있을 듯 하여일단 적어놓는다. 정리는 추후에...^^;;;

Building a BitTorrent client from the ground up in Go | Jesse Li : 이 자료를 보면 좋을 듯 하다.

Bit torrent


bencoding : bencoding - BitTorrentSpecification - Theory.org Wiki
go bencoding library : marksamman/bencode: Bencode implementation in Go


pure python code : Blender3D/torrent: Pure-Python torrent client.



pip install tornado
torrent/file

.torrent 파일을 parsing 해서 meta info 를 얻는다.
meta info 의 announce-list 가 최초 trackers 가 된다.
이 announce-list 의 url 을 보고 http/udp 인지를 판단한다.
아래의 정보를 tacker 에게 보낸다.
{'compact': 1, 'downloaded': 1000000, 'info_hash': '\xd5\xde\x04}\xcc\x...`\x17\xc6', 'left': 10000000, 'peer_id': "-PT0000-Q\x04\x11\x...2\xf9\xf8", 'port': 6881, 'uploaded': 1000000}


test torrent file



Bit torrent specification

비트토렌트에 관련한 스펙은 ref. 2, ref. 5 에 나와 있다. 한번 읽어보자.




main()

torrent = Torrent(options.torrent)

server = Server(
    torrent=torrent,
    download_path=options.path,
    storage_class=DiskStorage,
    peer_id=peer_id(),
    max_peers=options.max_peers
)
server.listen(options.port)
server.start()

...

IOLoop.instance().start()



Server 의 _init__ 에서 .torrent 의 info 정보에 'length' 가 있는데 이 정보를 보고 필요한 disk 용량을 잡아놓는다.(storage_class.from_torrent() )
info['length'] 또는 info['files']['length'] 에 size 정보가 있다.
그리고 info['piece length'] 가 block size 로 사용된다.
이 piece 에 대한 hash 도 .torrent 에 있는데 이녀석도 가지고 있는다.

 이 hash 는 나중에 bitfield 를 보내줄때 내가 어떤 block 을 가지고 있는지 확인한 후에 알려줘야 하는데, 이것을 위해서 요청한 index 의 blcok hash 값과 자신이 가지고 있는 block 의 hash 값을 만들어서 비교한다.

bitfield 도 내가 가진 block 들을 전부보면서 block hash 값을 가지고 있는지를 보고 가지고 있다면 그 block 이 존재하는 것으로 보고 1 을 set 한다.

이 block 하나가 index 하나가 된다. 즉 index 하나는 한개의 block 을 가리킨다.


일단 file 을 만든적이 없으면 file 을 만들어서 사이즈만큼 00 을 채운다. (utils.fill) 그리고 이 녀석을 다시 열어서 이 handle 을 가지고 있는다.


해당 peer 에서 have message 를 받으면, peer 마다 가지고 있는 array 에 have 에서 알려준 index 에 대한 표시를 해둔다. (Client.got_have)


bitfield message

처음 peer 와 접속하게 되면,
server.storage.to_bitfield 를 통해 현재가지고 있는 piece 들을 bitfield 로 만들어서 던져준다.

piece message

받는 경우
내가 piece message 를 받아서 그 piece message 에 있는 index, begin을 보고 file 의 offset 을 찾은후에

  • offset = self.block_size * index + begin

거기다가 piece message 에 있는 block 을 write 한다.
write 가 끝나고 piece 가 전부 다 write 가 됐는지 확인하는 방법으로 hash 를 비교한다.

보내는 경우
상대가 request message 를 보냈고, 해당 piece 가 있다면, 그 piece 를 실어서 이쪽에서는 piece message 를 보내주게 된다.



have message

piece message 를 받고 block 을 전부 write 했다면, 해당 index 에 대한 have message 를 server module 을 통해 다른 peer 에게 날리게 된다.(Client.got_piece)



reqeust message

이렇게 message 들을 주고받다가,
missing_piece / total_piece < 0.05 (endgame mode) 라면
모든 piece를 받았는지 확인해보고, 다 받았으면, IOLoop 을 stop 한다.(client.message_loop)
다 받지 않았다면, missing_piece 부분을 받기위해 Request message 를 만들어 보낸다.

이밖에도 piece message 를 받고나서,
또는 unchoke message 를 받고나서,
request message 를 보낸다.
request message 는 원하는 piece에 대한 index 를 보낸다. 물론 그 block 에 대한 offset, size 도 함께 보낸다.

받은 경우
request message 를 받으면, 해당하는 piece 가 있는지 확인하고 있으면 piece message 를 만들어서 보내게 된다.



Torrent class 를 new 하면서 tracker 들을 new 한다.
tracker 는 HttpTracker 를 new 하고, HttpTracker 는 AsyncHTTPClient 를 new 한다.
AsyncHTTPClient  는 내부에서 TCPClient 를 new 한다.


torrent = Torrent(options.torrent)
class Torrent(object):
    def __init__(self, handle=None):
        self.trackers = self._trackers()

    ...
    def _trackers(self):
        trackers = self.meta.get('announce-list', [[self.meta['announce']]])
        result = []

        for tier, urls in enumerate(trackers):
            for url in urls:
                tracker = Tracker(url, torrent=self, tier=tier)
                result.append(tracker)

        return result

def Tracker(url, torrent, tier=0):
    o = urlsplit(url)

    if o.scheme == 'http':
        return HTTPTracker(url, torrent, tier)
    ...

class HTTPTracker(object):
    def __init__(self, url, torrent, tier=0):
        ...
        self.client = AsyncHTTPClient()

class SimpleAsyncHTTPClient(AsyncHTTPClient):
    ...
    def initialize(self, io_loop, max_clients=10,
                    hostname_mapping=None, max_buffer_size=104857600,
                    resolver=None, defaults=None, max_header_size=None,
                    max_body_size=None):
        ...
        self.tcp_client = TCPClient(resolver=self.resolver, io_loop=io_loop)





Server.start

TCPServer.start 를 한다.
그리고 connect_to_peers() 한다.

connect_to_peers()

  • 여기서 scripe_trackers() 를 하는데
  • scripe_trackers
    • tracker 에 대한 정보를 얻는다.
      •  future = tracker.announce(self.peer_id, self.port, event='started', num_wanted=50)
      • 에서 tracker url 을 fetch 한다. 그러고 나서 response 를 얻는다.
      • 여기서 tornado.AsychHTTPClient를 사용하게 된다.
    • response 가 온 후에 할일을 정해준다. 
      • future.add_done_callback(lambda future: self.tracker_done(future, result))
    • tracker_done
      • response 가 제대로 오지 않으면 log 를 남기고, 제대로 왔으면, 결과를 가지고 peer 정보를 update 한다.
  • Server.connect(self.unconnected_peers.pop())
    • 접속되지 않은 peers 에게 접속한다.


Server.connect

@coroutine
@gen_debuggable
def connect(self, peer):
    logging.info('Connecting to %s', peer)

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
    stream = IOStream(sock)
    yield Task(stream.connect, (peer.address, peer.port))

    self.handle_stream(stream, (peer.address, peer.port), peer)



여기서 이제 peer 에 접속해서 data 를 가져오게 된다.
socket 을 열고 peer.address 에 connect 한다.
tornado 의 IOStream 을 이용한다.
iostream 을 peer.address 에 대해 connect 한다.
그리고 연결이 되면, Server.handle_stream 을 호출한다.

Server.handle_stream

여기서 Client 를 new 한다. Client 는 new 하면, 주기적으로 keepalive message 를 IOStream 을 통해 보내게 된다.
client 를 new 하고 나서 stream(IOStream) 이 close 될 때 쓰일 callback 함수도 등록한다.
그리고 이 Client instance를 Server.connecting_peers 에 add 한다.
그리고  client.handshake 를 한다. 이 handshake 가 잘 되면, Server.connected_peers 에 client 를 등록해 놓는다.


Client.handshake

protocol + info_hash + peer_id 를 IOStream 을 이용해서 보낸다. 그러면 peer 에서도 같은 형식의 message 를 보낸다. 이것이 완료되면, client 는 message_loop 을 시작한다.

message_loop 에서는 peer message 들을 처리한다.(참고 : ref. 2 > peer message)

\x 는 16진수를 나타낸다.
message = chr(len(self.protocol))
message += self.protocol
message += '\0\0\0\0\0\0\0\0'
message += self.server.torrent.info_hash()
message += self.server.peer_id

"\x13BitTorrent protocol\x00\x00\x00\x00\x00\x00\x00\x00?\x19\xb1I\xf5:P\xe1O\xc0\xb7\x99&\xa3\x91\x89n\xab\xabo-PT0000-\x7f\xfc1\xf3\xe9\xdc\x802\x1bE\xbd\xa3"


응답 메시지







server start 를 하면 announce 를 하고, 이 때 future 를 넘긴다. 이 future 는 다른 작업을 하는 녀석에게도 전달되고, 그 다른 작업을 하는 녀석이 이 future 에 결과를 반영할 것이다.
future = tracker.announce(self.peer_id, self.port, event='started', num_wanted=50)
announce 에서 yield self.client.fetch(tracker_url) 한다. 이 fetch 안에서 future 를 만드는데, 자기도 reference 가지고 handle_response 라는 callback 에서 사용한다.(request 에 대한 결과를 여기서 set_result() 한다.)
즉, client.fetch() 가 끝나고 나서 control 을 넘긴다.

그러면, announce 는 @coroutine 이여서,
yield 를 해서 control 을 넘기면, Runner() 를 new하게 된다.(_make_coroutine_wrapper.wrapper)
이 때 Runner 를 run() 할지 안할지를 __init__ 에서 판단한다.(handle_yield)
현재 넘어온 yielded (self.future)가 아직 done() 이 아니면, 나중에 run() 을 하고.
self.io_loop.add_future(self.future, lambda f: self.run())
그렇지 않으면 바로 run() 을 하게 된다.


future = tracker.announce(self.peer_id, self.port, event='started', num_wanted=50)
의 다음 line 으로 넘어간다
future.add_done_callback(lambda future: self.tracker_done(future, result))






Stack trace

server.start()
Server.connect_to_peers() # server.py
Server.connect_to_peers(self) # server.py
Server.scrape_trackers # server.py
Server.tracker.announce # server.py
AsyncHTTPClient.fetch # httpclient.py
SimpleAsyncHTTPClient.fetch_impl #simple_httpclient.py
SimpleAsyncHTTPClient._process_queue #simple_httpclient.py
SimpleAsyncHTTPClient._handle_request #simple_httpclient.py
_HTTPConnection.__init__ #simple_httpclient.py
wrapper #gen.py  
TCPClient.connect # tcpclient.py
Runner(result, future, yielded) # gen.py



server = Server(
    torrent=torrent,
    download_path=options.path,
    storage_class=DiskStorage,
    peer_id=peer_id(),
    max_peers=options.max_peers
)
server.listen(options.port)
server.start()

def start(self, num_processes=1):
    TCPServer.start(self, num_processes)

    self.connect_to_peers()


def connect_to_peers(self):
    self.peer_stats()

    num_active = len(self.connected_peers) + len(self.connecting_peers)

    if num_active != self.max_peers and not self.unconnected_peers:
        logging.info('No peers to choose from. Scraping trackers..')
        yield self.scrape_trackers()

def scrape_trackers(self):
    result = Future()

    for tracker in self.torrent.trackers:
        logging.info('Announcing to tracker %s', tracker.url)

        future = tracker.announce(self.peer_id, self.port, event='started', num_wanted=50)
        future.add_done_callback(lambda future: self.tracker_done(future, result))

    return result

@coroutine
def announce(self, peer_id, port, event=None, num_wanted=None, compact=True, no_peer_id=None):
    params = {
        'info_hash': self.torrent.info_hash(),
        'peer_id': peer_id,
        'port': port,
        'uploaded': self.torrent.uploaded,
        'downloaded': self.torrent.downloaded,
        'left': self.torrent.remaining,
        'compact': int(compact)
    }

    ...
    tracker_url = url_concat(self.url, params)

    response = yield self.client.fetch(tracker_url)

class AsyncHTTPClient(Configurable):
    def fetch(self, request, callback=None, raise_error=True, **kwargs):
        ...
        def handle_response(response):
            if raise_error and response.error:
                future.set_exception(response.error)
            else:
                future.set_result(response)
        self.fetch_impl(request, handle_response)
        return future

class SimpleAsyncHTTPClient(AsyncHTTPClient):
    ...
    def fetch_impl(self, request, callback):
        ...
        self._process_queue()
    ...
    def _process_queue(self):
        with stack_context.NullContext():
            while self.queue and len(self.active) < self.max_clients:
                ...
                self._handle_request(request, release_callback, callback)

    def _connection_class(self):
        return _HTTPConnection

    def _handle_request(self, request, release_callback, final_callback):
        self._connection_class()(
            self.io_loop, self, request, release_callback,
            final_callback, self.max_buffer_size, self.tcp_client,
            self.max_header_size, self.max_body_size)

class _HTTPConnection(httputil.HTTPMessageDelegate):

    def __init__(self, io_loop, client, request, release_callback,
                 final_callback, max_buffer_size, tcp_client,
                 max_header_size, max_body_size):
        ...
        self.tcp_client = tcp_client
        ...
        with stack_context.ExceptionStackContext(self._handle_exception):
            ...
            self.tcp_client.connect(host, port, af=af,
                                    ssl_options=ssl_options,
                                    max_buffer_size=self.max_buffer_size,
                                    callback=self._on_connect)
                                    
class TCPClient(object):
    ...
    @gen.coroutine
    def connect(self, host, port, af=socket.AF_UNSPEC, ssl_options=None,
                max_buffer_size=None):
        """Connect to the given host and port.

        Asynchronously returns an `.IOStream` (or `.SSLIOStream` if
        ``ssl_options`` is not None).
        """
        addrinfo = yield self.resolver.resolve(host, port, af)
        connector = _Connector(
            addrinfo, self.io_loop,
            functools.partial(self._create_stream, max_buffer_size))
        af, addr, stream = yield connector.start()
        # TODO: For better performance we could cache the (af, addr)
        # information here and re-use it on subsequent connections to
        # the same host. (http://tools.ietf.org/html/rfc6555#section-4.2)
        if ssl_options is not None:
            stream = yield stream.start_tls(False, ssl_options=ssl_options,
                                            server_hostname=host)
        raise gen.Return(stream)








torrent = Torrent(options.torrent)
class Torrent(object):
    def __init__(self, handle=None):
        self.trackers = self._trackers()

    ...
    def _trackers(self):
        trackers = self.meta.get('announce-list', [[self.meta['announce']]])
        result = []

        for tier, urls in enumerate(trackers):
            for url in urls:
                tracker = Tracker(url, torrent=self, tier=tier)
                result.append(tracker)

        return result


def Tracker(url, torrent, tier=0):
    o = urlsplit(url)

    if o.scheme == 'http':
        return HTTPTracker(url, torrent, tier)
    ...

class HTTPTracker(object):
    def __init__(self, url, torrent, tier=0):
        ...
        self.client = AsyncHTTPClient()

class SimpleAsyncHTTPClient(AsyncHTTPClient):
    ...
    def initialize(self, io_loop, max_clients=10,
                    hostname_mapping=None, max_buffer_size=104857600,
                    resolver=None, defaults=None, max_header_size=None,
                    max_body_size=None):
        ...
        self.tcp_client = TCPClient(resolver=self.resolver, io_loop=io_loop)


server = Server(
    torrent=torrent,
    download_path=options.path,
    storage_class=DiskStorage,
    peer_id=peer_id(),
    max_peers=options.max_peers
)
server.listen(options.port)
server.start()

def start(self, num_processes=1):
    TCPServer.start(self, num_processes)

    self.connect_to_peers()


def connect_to_peers(self):
    self.peer_stats()

    num_active = len(self.connected_peers) + len(self.connecting_peers)

    if num_active != self.max_peers and not self.unconnected_peers:
        logging.info('No peers to choose from. Scraping trackers..')
        yield self.scrape_trackers()

def scrape_trackers(self):
    result = Future()

    for tracker in self.torrent.trackers:
        logging.info('Announcing to tracker %s', tracker.url)

        future = tracker.announce(self.peer_id, self.port, event='started', num_wanted=50)
        future.add_done_callback(lambda future: self.tracker_done(future, result))

    return result

@coroutine
def announce(self, peer_id, port, event=None, num_wanted=None, compact=True, no_peer_id=None):
    params = {
        'info_hash': self.torrent.info_hash(),
        'peer_id': peer_id,
        'port': port,
        'uploaded': self.torrent.uploaded,
        'downloaded': self.torrent.downloaded,
        'left': self.torrent.remaining,
        'compact': int(compact)
    }

    ...
    tracker_url = url_concat(self.url, params)

    response = yield self.client.fetch(tracker_url)

class AsyncHTTPClient(Configurable):
    def fetch(self, request, callback=None, raise_error=True, **kwargs):
        ...
        def handle_response(response):
            if raise_error and response.error:
                future.set_exception(response.error)
            else:
                future.set_result(response)
        self.fetch_impl(request, handle_response)
        return future

class SimpleAsyncHTTPClient(AsyncHTTPClient):
    ...
    def initialize(self, io_loop, max_clients=10,
                    hostname_mapping=None, max_buffer_size=104857600,
                    resolver=None, defaults=None, max_header_size=None,
                    max_body_size=None):
        ...
        self.tcp_client = TCPClient(resolver=self.resolver, io_loop=io_loop)
 
    ...
    def fetch_impl(self, request, callback):
        ...
        self._process_queue()
    ...
    def _process_queue(self):
        with stack_context.NullContext():
            while self.queue and len(self.active) < self.max_clients:
                ...
                self._handle_request(request, release_callback, callback)

    def _connection_class(self):
        return _HTTPConnection

    def _handle_request(self, request, release_callback, final_callback):
        self._connection_class()(
            self.io_loop, self, request, release_callback,
            final_callback, self.max_buffer_size, self.tcp_client,
            self.max_header_size, self.max_body_size)

class _HTTPConnection(httputil.HTTPMessageDelegate):

    def __init__(self, io_loop, client, request, release_callback,
                 final_callback, max_buffer_size, tcp_client,
                 max_header_size, max_body_size):
        ...
        self.tcp_client = tcp_client
        ...
        with stack_context.ExceptionStackContext(self._handle_exception):
            ...
            self.tcp_client.connect(host, port, af=af,
                                    ssl_options=ssl_options,
                                    max_buffer_size=self.max_buffer_size,
                                    callback=self._on_connect)
                                 
class TCPClient(object):
    ...
    @gen.coroutine
    def connect(self, host, port, af=socket.AF_UNSPEC, ssl_options=None,
                max_buffer_size=None):
        """Connect to the given host and port.

        Asynchronously returns an `.IOStream` (or `.SSLIOStream` if
        ``ssl_options`` is not None).
        """
        addrinfo = yield self.resolver.resolve(host, port, af)
        connector = _Connector(
            addrinfo, self.io_loop,
            functools.partial(self._create_stream, max_buffer_size))
        af, addr, stream = yield connector.start()
        # TODO: For better performance we could cache the (af, addr)
        # information here and re-use it on subsequent connections to
        # the same host. (http://tools.ietf.org/html/rfc6555#section-4.2)
        if ssl_options is not None:
            stream = yield stream.start_tls(False, ssl_options=ssl_options,
                                            server_hostname=host)
        raise gen.Return(stream)







Reference

  1. 쿠...sal: [컴][파이썬] python 의 Future 가 동작하는 방법
  2. The BitTorrent Protocol Specification, www.bittorrent.org/beps/bep_0003.html
  3. BitTorrent 프로토콜의 동작원리 | NETMANIAS
  4. BitTorrent 공유 과정 실전 분석 | AhnLab
  5. BitTorrentSpecification - Theory.org Wiki
  6. JosephSalisbury/python-bittorrent: A simple, clean, and efficient BitTorrent library, written entirely in Python.
  7. jefflovejapan/drench: A simple BitTorrent client in Python
  8. Building a BitTorrent client from the ground up in Go | Jesse Li, 2020-01-04