Assembly Learner
https://github.com/Jinmo/AssemblyLearner 에서 받으실 수 있습니다.
jinmo님께서 제작하신 어셈블리 교육용(?) 프레임워크입니다.
문제 추가도 가능하고 이것저것 건드릴 수 있으니 프레임워크라 할 수 있으려나용 무튼..
뭐 이런식으로 구성되어 있고 왼쪽에서 코드를 입력한 후 실행을 하게 되면 제가 입력한 코드를 실행해줍니다.
사실 C 인라인 어셈블리 외에는 처음 해봐서 음음 첨엔 헷갈렸는데 하다보니 재밌더라구요
아무튼 튜토리얼부터 해봅시다.
지문 그대로 읽어보시고 하면 됩니다. eax를 2 * eax + 1로 만들라고 했으니 위와 같이 해주면 풀렸습니다! 라고 뜹니당
근데 답 보기 버튼은 실행이 안되네용..
아무튼 본 문제로 해봅시다!
2-1. 쉘코드 짜기 - int 0x80!
Instruction 이제 본격적으로 쉘코드라는걸 짜볼거에요. "쉘코드(shell code)"란, 일반적으로 프로그램에 삽입해서 제가 원하는 목적을 실행하는 코드를 말해요! 보통 공격용으로 많이 쓰인답니다. 이번에 소개드릴 명령어는 int 명령어인데요, 운영체제(리눅스)에다가 신호를 전달하는 명령어에요! int <숫자> 하면 그 숫자랑 함께 OS에 지금 실행되는 정보가 전달이 되요. 리눅스 쉘코드를 짤거거든요, 저희가. 이 환경이 리눅스라서. 윈도 쉘코드는 비교적으로 복잡해요. 아주는 아니지만. 리눅스에서 입출력같은 "리눅스의 기능"을 이용하기 위해서는 int를 써야되거든요. 사용법은, mov eax, (기능 번호) int 0x80 이렇게 쓰면 되요. 함수 인자는 ebx, ecx, edx를 통해서 들어가요. 이 문제의 목표는, hello world를 출력하는거에요! 예제를 보면 대충 아실 수 있을 거에요. 아, 참. 그리고 시스템 콜 목록은 http://docs.cs.up.ac.za/programming/asm/derick_tut/syscalls.html 에 있답니다! 참고하세요! p.s. 만약 리눅스를 쓰신다면 /usr/include/asm-generic/unistd.h를 보시면 나와요. __NR_함수이름 이렇게 검색하시면 나와요. 사이트랑 파일이랑 잘 활용해보세요! |
지문까지 있고 친절하네요(..) 무튼 공략을 해봅시다.
“hello world” 를 출력하라고 하네요.. 일단 어셈을 보면
string: .asciz "bye world" # string + "\x00" = asciz
# string = ascii
string이라는 변수에 “bye world”라는 값을 넣어놨네요.. hello world를 출력하는게 목적이니 그걸로 바꿔주고..
별개로 asciz라는 지시자가 뭐하는 지시자인지까지 적어놓으셨네요.
데이터 지시자 중에 문자열 지시자는 ascii랑 asciz가 있는데요. asciz가 저희가 잘 알고있듯이 문자열 맨 끝에 NULL byte 붙여주는거에요. Ascii는 그냥 문자열이구
아무튼 저렇게 입력해줍니다. 그리고..
mov eax, 4 # __NR_write
mov ebx, 1 # stdout
mov ecx, offset notString # 문자열 (뭔가 바꿔줘야됨)
mov edx, 5 # ???????! 문자열 길이
mov ecx, offset [문자열이 저장된 변수 명] 이기 때문에 저 자리에는 string을 입력시켜줍니다. 문자열 길이 또한 12byte이기에 12라고 입력해주구여. 근데 이건 null byte 포함된 값이고.. 11byte만 입력해도 인증되요.
2-2. 쉘코드 짜기 - /bin/sh 실행하기
Instruction 히히, 돌아왔군요! 다행이에요. 이번 시간에는 int 0x80을 써서 /bin/sh를 실행시킬거에요. 음.. 어디까지 예제를 드릴까.. 흠흠. 흠흠. 그래요! 다른 프로그램을 실행해보겠어요. 낄낄낄! 아, 그리고 이 환경에서는 뭘 실행할 때 뭐가 실행되는지 나와요! p.s. http://docs.cs.up.ac.za/programming/asm/derick_tut/syscalls.html |
eax에는 syscall 넘버가 들어가는데 execve를 사용해야하니까 11이고..
bx,ecx,edx에는 각각 인자라고 보면 될것같네요.
execve(const char* filename, char *const argv[], char *const envp[]);이기 떄문에 일단 ebx에는 “/bin/sh”라는 문자열이 있는 주소를 주면 될거고..ecx랑 edx는 딱히 필요하지 않기 때문에 NULL로 줬습니다..
뭐 근데 의도대로 풀면 mov ecx, offset argv를 해줘야할 것 같네요.
2-3. 쉘코드 짜기 - NULL-free 쉘코드 만들기
Instruction 쉘코드는 일반적으로 보면 그냥 프로그램이지만, 따로 쉘코드라 부르는 이유는, 공격용으로 쓰이기 때문이에요! 예를 들어 문자열을 받는 부분에 쉘코드를 넣고, 프로그램 흐름을 어떻게던 그 쪽으로 돌린다던지 하는거죠.
그런데 C언어에서 문자열은 뭘로 끝나죠? \0 (널 바이트)으로 끝나죠!
그래서 쉘코드 안에 00이 있으면 안되요. 아, 출력 밑에 저희가 넣은 어셈블리가 표시가 될거에요. 거기서 "cd 80" 이런식으로 뜰텐데, "00"이 뜨지 않게 해야된답니다!
널 바이트가 코드에 있으면 실행을 아예 안해줄거에요.
그럼 /bin/sh를 다시 한번 실행해보세요! 굿 럭! |
음.. 쉘코드 만들 때 널바이트 있으면 문자열 끝으로 인식해버려서 중간에 끊겨버리죠..
뭐튼 그런 이유에서 쉘코드 안에는 널 바이트가 있으면 안되요. 이번 문제는 그걸 잘 알려주네요..
mov eax, 11 하면 널바이트가 2갠가 3갠가 포함될건데 그냥 xor eax, eax해서 eax를 0으로 만들어주고 add eax, 11 해줘서 추가해주면 되요!
그리고 마지막 edx 값 역시 xor로 0으로 만들어주면 풀립니다. 근데 이게 edx 초기 값이 0이라서 edx 세팅 안해도 풀리더군요..
3-1. 손-코딩을 해-봅시다 - 입력한거 출력해주기
Instruction 쉘코드 짜기 1번을 참조해서 입력한 값(최대 길이 16)을 그대로 출력해주는 쉘코드를 만들어보세요! 힌트: read의 리턴 값(eax): 읽은 길이 |
syscall_write의 argv에 맞게 입력해주고 .data 영역에 문자열을 추가해서 풀었습니다.
당연하겠지만 문자열 영역이 들어가는 ecx는 주소값으로 참조해주셔야하구요.. 음 힌트의 이유를 되짚어보면 아마 mov eax, 4를 하기 전에 mov edx, eax를 해야하는 것 같네요.
esp로 참조하는 방법은 이게 아닌가 싶습니다..
.globl _start _start: push asm # ... (read) mov eax, 3 mov ebx, 0 # stdin mov ecx, esp # buffer mov edx, 16 # length int 0x80
# ... (write) mov edx, eax mov eax, 4 mov ebx, 1 mov ecx, esp int 0x80
# (exit) mov eax, 1 mov ebx, 0 # exit code, but who cares? int 0x80 # ... (exit, not required :)
.data asm: .asciz "assembly!" |
3-2 구구단의 경우엔 제가 메모장에 글 써놓고 블로그 포스팅하는 특성상 -.- 날아갔네요
나중에 기회 되면 다시 풀어보고 코드 올리도록 하겠습니다 힝
무엇보다도 문제들이 크게 어렵지 않기 때문에 초보자 분들도 금방 하실 것 같네요..
다만 구구단의 경우엔 갑자기 난이도가 확 뛰어서 좀 힘들지도 모르겠습니다..