By in09 | March 11, 2016
문제푸는 입장에서 다시보는 소소한(?) 것들
부제 : 기술이사님 만족하심까
feat. Big Engian & Little Endian, 로컬 환경이면서 socat으로 리모트 환경인 척 하기
이사님이 자세히 쓰라거하셔서 준비해씀다
필요한데 헷갈리고 정리안되는 것들 정리해드려요
2. Byte Ordering
/*endian.c*/
#include <stdio.h>;
void main() {
int num=305419896; // 16진수로 0x12345678
char arr[10]=ABCDEFGH;
}
오 짧죠?
gdb로 어셈블리 코드를 봐보겠슴다.
역시나 짧네요.
하이라이트된 부분을 잘 살펴보면
스택에 num의 값과 input 배열 값이 할당되는 부분임을 알 수 있쬬?
메모리에 값이 어떻게 저장이 되는지 보기 위해 0x0804846a에 breakpoint를 걸고 실행하고,
메모리에 정수
가 어떻게 저장이 되는지 보도록 하겠슴당
숫자는 메모리에 거꾸로 저장이 됨미다
Intel CPU는 바이트의 배열 방식을 사용해 거꾸로 저장한다고 했죠?
1 바이트씩 출력해보면 실제로도 메모리에 거꾸로 저장이 되고 있어요.
( 4바이트씩 출력할 경우에는 똑똑이 gdb가 사람이 보기 편하게 출력해주어서 0x12345678과 같이 출력해주는거예염 )
이번엔 문자열
이 메모리에 어떻게 저장이 되는지 볼겠슴다
문자 A는 hex값으로 41, B는 42, C는 43, D는 44임미다(ASCII 표를 참고하소서)
헌데… 뭐징..?
아까 뭐 컴퓨터는 거꾸로 저장한다면서..
그럼 0x44 0x43 0x42 0x41 이렇게 저장되어야 하는거 아닌감?
기억하십셔 바이트 오더링은 수 표현에만 적용 된답니닼
- x/s [address]
– 해당 주소부터 \x00(널 바이트)가 나올 때까지 출력해주는 명령어
2. 디버거로 봅씨당
괜히 바이트오더링을 설명한게 아니랍니당…
원격 서버에 값을 전달할 때를 생각해봅씨다.
BOF 취약점이 있어서 리턴 어드레스를 덮는거예여!
0x0804a0b0을 리턴 어드레스라고 가정했을 때 어떻게 값을 전송해야할까여? 0804a0b0
? 아니면 b0a00408
?
확인해보댜규요
/*little_endian.c*/
#include <stdio.h>
void main() {
char arr[10];
setvbuf(stdout, 0,1,0);
printf(&amp;amp;quot;input : \n&amp;amp;quot;);
scanf(%s, arr);
printf(%s, arr);
}
arr 배열에 문자열을 받네요.
전 리모트으로 arr의 배열에 0x0804a0b0
의 값을 전송하고,
메모리에 어떻게 저장되는지 볼꺼예여.
일단 위의 소스를 컴파일부터 해주고.
$ gcc -o little_endian little_endian.c
이러한 실행파일을 서버 소켓처럼 열어 리모트로 붙어서 사용할 수 있게 해주는게 있어요.
이름하야 socat
!!!
뭔말이냐면 printf(“input: \n”); 과 같은 소스는 출력 함수져?
근데 리모트에서 이 실행파일로 붙을 경우
클라이언트가 이 출력된 문자열을 recv
할 수 있어여.
서버에서는 input을 send
하는거죠.
그럼 scanf와 같은 입력함수는 뭘까요? 서버 소켓 입장에서는 읽어들이는 거잖슴까?
서버 소켓은 recv로 받고 클라이언트 즉 원격에서 붙은 우리는 send로 arr 배열에 원하는 문자열을 입력할 수 있는검미다.
그래서.. 포너블 문제 풀 때 바이너리는 주는데… 로컬에서 리모트인양 테스트하고 싶을 때 이짜나요?
그때 socat
을 활용하면 됨미다.
$ sudo apt-get install socat
터미널에 이 명령어 한 줄이면 socat을 다운받을 수 있어염.
자 이제 socat도 깔았으니 한번 테스트 해볼까염?
$ socat tcp-listen:9001, reuseaddr, fork, bind=0.0.0.0 exec:/home/in09/little_endian
9001
포트로 서비스를 오픈하고
새로운 시도가 들어올 때마다 프로세스를 fork(복제) 해주겠따 뭐 이런 명령이겠죵?
그렇게 해서 little_endian(서버 소켓)을 열어주는거예요.
socat
으로 열린 little_endian
프로그램에 붙어볼게염!
$ nc 192.168.252.129 9001
nc는 네트워크 연결을 통해 데이터를 읽고 쓸 수 있도록 간편하게 만든 프로그램입니당.
리눅스에서는 기본으로 제공하니 그냥 사용하시면 됩니당.
기본적으로 다음과 같이 사용합니당.
nc [프로그램이 열려있는 서버의 ip주소] [포트번호]
실행해봤더니 어떻게 되심까!
네트워크 통신으로도 little endian의 실행이 가능하져?
socat
으로 printf
와 같은 출력함수를 클라이언트에게 send
해주고
scanf
와 같은 입력함수는 클라이언트로부터 recv
하도록 되어있짜나여
혹시나 뭔말인지 1도 모르겠는 분들은 아래의 짤을 참고해주세여.
다시 본론으로 돌아가서 내가 리모트로 보낸 문자열이 원격 서버의 메모리에 어떻게 저장이 되는지 보꾸예염.
nc 프로그램을 사용해서 했던 것을 파이썬으로 소켓 클라이언트를 한번 짜보께염.
하핳 그대로 되졍?
별거 없어여 구냥 파이썬으로 소켓 클라이언트 코드 몇 줄 짠 거 뿐입니당.
/*test.py*/
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((192.168.252.129, 9001)) # IP, Port
raw_input(:)
print s.recv(1024) # printf(input : \n);
s.send(in09\n) # scanf(%s, arr);
print s.recv(1024) # printf(%s\n, arr);
근데… raw_input(“:”)
이거는 무냐…
파이썬에서 입력함순데.. 여기에선 sleep같이 시간을 벌어주는 용도로 사용되어여.
str = raw_input(“what is your name?”)
과 같이 쓰면
what is your name?
이 출력되고 입력을 받을 때까지 멈춰있슴다
그리고 입력하면 반환값으로 입력값이 리턴되어 str 변수에 저장됨ㅁㅣ다.
이걸 왜 쓰는지 의아하시져?
우리가 gdb로 디버깅할 때 바이너리에 붙을 수도 있지만
실행 중인 프로세스에도 gdb로 붙어서 메모리를 볼 수 있거둔요?
지금 test.py로 little_endian에 붙었다거 가정해봅씨다.
근데 내가 보낸 값이 메모리에 어떻게 저장되는지 보려면 breakpoint
를 걸어서 봐야할거 아녜여
근데… 중간에 브레이크가 없어..
앞만 보고 달리는 작은엔디안아…ㅠ
그래서 잠깐 raw_input으로 gdb로 프로세스에 붙을 시간과 breakpoint를 거는 시간을 버는 거예여
sleep으로 시간을 지배해볼 수도 있지만, 내가 원하는시간 만큼만 sleep할 수 없짜나요
밑에 짤을 보면 알겠지만
test.py를 실행하고 “:” 가 나온 후에 뭐라도 입력해야지만
다음 코드가 실행되는 모습이 재빠르게 지나가져?
하ㅡㅡ 죄송해요.
좀 저도 찬찬히 보여드리고 싶은데 꿀캠 평가판이 10초밖에 안 기다려주네요ㅡㅡ…
이사님 보고계세요?
불편하긴 하지만 구냥 이어붙이면 되니까 전 괜찮아요^^!
뭐 그냥 1시간이면 할꺼 2시간, 3시간 하면 됨미닿ㅎ
각설하거 다음을 계속 봐주십셔!
3번의 클라이언트가 1번에 요청을 하면 똑같은 프로세스가 fork되어 통신할 수 있겠져?
그게 2번 프레임에 있는 터미널에 있는 프로세스와 3번 소켓 클라이언트가 통신을 하는거졍.
그래서 3번의 raw_input(“:”)으로 잠시 통신을 보류하는 동안
2번 터미널에서 새로 fork된 프로세스에 gdb를 붙이고 있졍?
근데 짤을 잘보면 2번 터미널에서 마지막에 operation not permitted again root
어쩌고 이렇게 나와여.
Gdb를 프로세스에 붙여 따라갈 땐 무조건 root 권한으로 실행시켜야함다.
$ sudo -i
[sudo] password for in09: [패스워드를 입력하세염]
#
그래서 이렇게 root 권한으로 바꿔주공! 다시 실행해 봅씨다!
Gdb가 이제 프로세스를 잘 붙들고 있졈?
이제는 제대로 내가 0x0804a0b0와 같은 값을 리모트에서 전송했을 때
서버의 메모리에 어떻게 저장되는지 gdb로 확인해보겠습니답
아까와 동일한 소스인데 이번에는 문자열(in09)이 아니라 0x0804a0b0과 같은 ‘숫자’를 send해볼게염
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((192.168.252.129, 9001))
raw_input(:)
print s.recv(1024)
s.send(\x08\x04\xa0\xb0\n) # 주소 값에 따라 여기만 바꿔주면되염
print s.recv(1024)
gdb로 붙었어염
빨간 점에 breakpoint를 걸고 그때 스택 상황을 보면
아마 이럴거예염 확인해 볼까염?
여기서 잠깐 삼천포 TIP
scanf(“%s”, arr)
의 어셈코드를 보면
0x08048567 <+74>: mov DWORD PTR [esp+0x4],eax ===> 매개변수 2
0x0804856b <+78>: mov DWORD PTR [esp],0x8048639 ====> 매개변수 1
0x08048572 <+85>: call 0x8048410 <__isoc99_scanf@plt>
scanf를 호출하기 전에 스택에 두 번째 매개변수 arr을 먼저 스택에 쌓고
그 다음 “%s”(첫 번째 매개변수)
를 스택에 쌓습니다요.
스택은 높은 주소에서 낮은 주소로 자란다고 하잖아요.
구래서 이렇게 뒤에 것부터 쌓는게 결과적으로는 순서대로 스택에 쌓이게 되는거랍니다!
가릿?
끄잉..? 예상과는 다르게 0xb0a00508
이 들어갔어요ㅠㅠ
내가 원하는 0x0804a0b0
값이 올바르게 들어갔다면
이렇게 출력이 됬어야 겠졈?
컴퓨터는 어떻게 인식한다구염? 거꾸로!
그렇기 때문에 전송할 때는 바이트 오더링을 고려해
0xb0\xa0\x04\x08
으로 전송해야 원하는 값을 입력할 수 있답니당
그럼 다음과 같이 소스를 수정할 수 있게쪄?
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((192.168.252.129, 9001))
raw_input(:)
print s.recv(1024)
s.send(\xb0\xa0\x04\x08\n) # 주소 값에 따라 여기만 바꿔주면되염
print s.recv(1024)
s.send(“\xb0\xa0\x04\x08\n”)
상단의 코드에서 input을 거꾸로 입력해주고 있져?!
어찌 좀 바이트 오더링에 대해 이해가 가셨나요ㅎㅎ
다음에 또 망나요 그럼 20000 뿅!